# [SUMMARY] Price ranges (#164)

Discussion in 'Ruby' started by Matthew Moss, Jun 5, 2008.

1. ### Matthew MossGuest

In this quiz, James presented us with a practical problem that is
quite common in everyday life. Sometimes we make such decisions
implicitly and "fuzzily", when we shop at a particular store since
another shop "is too expensive." Other times it is a more explicit
search, such as when filtering online listings for products, rentals,
or other services.

The quiz boils down to determining which providers' price ranges
overlap that of the consumer's desired range. To test whether range A
and B overlap requires that at least one of the following hold:

* At least one endpoint (i.e. low or high price) of range A falls
within range B.
* At least one endpoint of range B falls within range A.

A pretty simple task with a good amount of repetitive behavior, which
suggests that these behaviors should be consolidated.

We'll take a look at _Chris Shea_'s solution: it's simple, well-
documented and easy code to read in use. Chris defines a new class,
`PriceRange`, rather than use the built-in `Range` class. This seems
appropriate, as the class is not intended to be reused in the same
manner as `Range`. Let's first look at the initializer:

def initialize(opts={})
@low = opts[:low] || 0
@high = opts[:high] || 1.0 / 0.0
end

The use of a hash as the initialization parameter allows Chris to make
use of `PriceRange` like this:

customer = PriceRange.newlow => 2_500, :high => 5_000)

And in fact, one or both of `:low` and `:high` could be left out to
create open-ended or empty ranges. Even reordering them is fine, as
they are no longer parameters but rather a single hash.

The initializer remembers the low and high values, providing defaults
if necessary: zero for the low end of the range, and `1.0 / 0.0` --
that is, Infinity, with a capital *I*. Infinity is a constant of class
Float that is greater than all other numbers. So it serves as a valid,
and quite convenient, high value for an open-ended range.

To check for basic overlap, Chris defines these methods:

def includes_price?(price)
price >= @low and price <= @high
end

def includes_edge_of?(other)
includes_price?(other.low) or includes_price?(other.high)
end

Very straightforward. As mentioned above, determining if two ranges
overlap involves two parts, where `includes_edge_of?` satisfies one of
those parts (here, *a* and *b* are `PriceRange` instances):

a.includes_edge_of?(b)

To perform the whole overlap check, then, is simply a matter of also
doing the reverse check, and using the logical-or operator to combine:

a.includes_edge_of?(b) or b.includes_edge_of?(a)

And that is exactly what we see in the last method of the `PriceRange`
class, `select`, but checking over all provided `PriceRange`
arguments:

def select(*ranges)
ranges.select { |range|
self.includes_edge_of?(range) or range.includes_edge_of?
(self)
}
end

I like the simplicity of the code as well as the readability (as shown
by example in the code's comments). My only possible complaint is a
preference to use a named constant for infinity in the code, though it
would seem that inserting `(1.0 / 0.0)` is the easiest way to get such
constant.

Matthew Moss, Jun 5, 2008