Skip to main content

Generic Range Class

Posted by mkarg on January 1, 2011 at 7:14 AM PST

Often code has a bad smell, then it gets time to replace custom lines by common patterns. Sometimes it even makes sense to even replace a single line of code by a class just wrapping that single line (which actually increases code size), if that makes readers better understand what the code does. Unfortunately often such patterns are publicly known but do not exist as ready to use classes in the JRE, so one needs to write them again and again. To not make people having to type again and again, I typically upload mine to the web, so everybody is free to share it. One example is the Range pattern (see Martin FOWLER's web site for a deeper introduction to that pattern).

The Range Pattern

In short, a Range is something that is described by an upper and lower bound. Sometimes, a range is open, i. e. it only has one bound or even none at all. Typically, ranges are used to check single values against them, like "Is my birthday while I am on holidays?" (the holidays are a range described by the first and last date, the birthday is a single date). Or, "Is this car in the wanted price range (neither too crappy, i. e. cheap, nor too expensive)?" (the price range is described by the lowest sensible price and the maximum amount you want to pay, the actual price of a specific car is a single price). So, a range is a pattern, and it is independent of the value's type. It fits nicely to implement it as a generic class, as it hell to write that if...&&...||...&&... again and again, particularly with possibly open ranges (open ranges make things rather complex). Sad but true, there is no such class in the JRE, and Oracle did not pick up my RFE to add one so far.

The Generic Range Class

So I just wrote a class that fills this gap. Since it is generic, you can safely use it with any information type you like - Integers, Dates, Prices, anything that implements the Comparable maker interface to indicate that it can be compared against a boundary. The class is able to deal with open ranges by passing null as a limit, but it doesn't allow to check a null value against its borders (since the result wouldn't be intuitive). If you need a Range class for your GPL'ed project, just download it from my web site: http://www.headcrashing.eu.

What Next?

If you have any ideas what the Range class should be able to do (like being Comparable on its own, intersect or merge other Range instances, etc.), just add a comment. If you have a finished algorithm or test case, this would be even better. I will update the class, so everybody can share the benefit.

Regards
Markus

Updates

  1. Version 1.0.1: Improved java docs regarding open ranges.
  2. Version 1.0.2: Fixed bug with Range<T>.contains(T) returning wrong results due to assumption that Comparable<T>.compareTo(T) would return only -1 and 1, while it actually can be -N and N.
  3. Version 1.1.0: Merged anonymous author's contribution Range<T>.contains(Range<T>) to check whether a range is within another range. This is useful e. g. if your like to know whether a meeting is within someone's free time in a calendar (the meeting entry in the calendar has a Range<Date, Date>, also has the free time entry). Also uploaded unit test for Range<T> class to support more contributions.
  4. Version 1.2.0: Merged anonymous author's contribution Range<T>.overlaps(Range<T>) to check whether a range overlaps (intersects, touches) another range. This is useful e. g. if you like to know whether a meeting will run over the start of another meeting in a calendar (the meeting entries in the calendar have Range<Date, Date>s).
  5. Version 1.2.1: Fixed bug with Range<T>(T, T) not reporting interchanged limits due to assumption that Comparable<T>.compareTo(T) would return only -1 and 1, while it actually can be -N and N.
  6. Version 1.2.2: Uploaded to Maven Central, so using the class is as simple as adding the following dependency to a project's POM: <dependency><groupId>eu.headcrashing.treasure-chest</groupId><artifactId>RangeClass</artifactId><version>[1.2.2, 2)</version></dependency>

An overview of all my publications, presentations, code snippets or complete products, can be found on my personal web site Head Crashing Informatics.

 

Comments

Generic Range Class

You may want to add a Comporator in the constructor, so you can handle objects that do not have a natural ordering.
But the main problem is that a Range can have a distance ([2,10] the distance is 8, [Monday, Friday] distance is 5 days, ...), which is not always defined, but when it is it's actually important to have (i.e. I find having a time range that does not give back the time interval it covers of somewhat limited use and so on). So, it's kind of missing something important. The type of the distance is not the type on which the range is defined. It cannot be deduced by the comparator, but it has to agree with it.
I don't know whether is better to have it generalized into the Range class, or have specific implementation for each type that have a distance, ...

Generic Range Class

(1) Comparator
From my understanding of Comparable and Comparator, they do cover quite different uses. A Comparable is a class which allows its instances to compare against each other. This is a typical long level language feature, and therefore it is located in the java.lang package and implemented by many of the JRE's core classes. The unwritten but obvious rule is, that a class that shall allow its instances to be compared against each other, shall implement that interface (why shouldn't they, as there is no harm in doing it), just as any class should have equals and hashCode. It fits perfectly for the need of the Range<T> class. The Comparator instead is located in the java.util package, so it is not really part of the core language, but "just" is an additional utility, mainly for two quite special needs: (a) Classes not having a natural order (i. e. it wouldn't be obvious what compareTo would work like, just like it is natural to sort numbers, but not natural to sort colors as you could sort them by either hue, saturation, brightness, R-value, G-value, B-value, etc. so you must provide a synthetical ordering using an external Comparator) and (b) Providing a different sort order, like sorting descending (which is not natural in the mathematical sense). So, a Comparator serves for sorting mainly, not for "just" comparing, hence I don't share the real benefit of providing one to the constructor (I don't see why it would be better than making comparable objects just implement Comparable instead). Maybe you can provide a valid real world use case that showcases that it makes no sense to make a class Comparable, while it makes sense to use it in a Range (please no hypthetical use case but stick with the real world to be pragmatical)?
(2) Distance
Not every comparable objects necessarily is measurable in distances. For example, it makes sense with integers, but it makes no sense in colors. While a color range could exist by using the hue value as the range, which would effectively describe a color gradient, the question is, what the distance shall describe in the real world (since hue is only known as absolute hue in the real world, AFAIK, but not as a relative hue difference - which might be interesting in a mathematical or physical sense, but could be easily described with an integer then). So I doubt that it would make sense to have a distance provided in the constructor or return it as a subtraction method's result. For virtually every use case I can image, distance would be described as an integer, so it makes no big benefit to have support for that in the Range class (as I could just calculate it easily outside), on the other hand I hardly can imagine any use case which would be covered with something different from integer. Maybe you can find a real world use case? A different problem is that there is nothing in Java which would fit as a suitable class for distance, i. e. one cannot derive it from Comparable or find any other relationship to comparable. As a result, there would be the need in invent such an interface which makes things rather complex. But for justifying this complexity, there is the question of the actual benefit then. Maybe you like to serve some finished code example to proof the actual value?

Generic Range Class

(1) Comparator
Points: natural ordering makes no sense on points, and you may have different ranges along different coordinates. So:

Range&lt;Point&gt; xRange = new Range(lowerLeftPoint, upperRightPoint, xComparator);
Range&lt;Point&gt; yRange = new Range(lowerLeftPoint, upperRightPoint, yComparator);

for (Point point: points) {
   if (xRange.contains(point) &amp;&amp; yRange.contains(point)) {
      plot(point);
   }
}

Would implement a poor's man scatter plot. (Basically: whenever you have an object that has more than one property, you can define different ordering and therefore ranges in that ordering.)
The other reason you need a Comparator in the constructor is "code mechanics": you cannot always modify the source of the object and make it comparable. So, if you want to introduce a MyInstant class defined to the nanosec precision, you can create ranges that can be defined or use also java.util.Date (which can't know about MyInstant).
(2) Distance
>on the other hand I hardly can imagine any use case which would be covered with something different from integer. Maybe you can find a real world use case?
Correct: a distance is always a number (that's the mathematical definition), but not alwyas an Integer. The problem is how that number is implemented, what unit it has, ... So, for example, you might have Duration class for a time duration (like joda/javax.time). Again, maybe you will have a TimeRange extends Range<Instant> that also implements TimeRange.getDuration(). Not claiming that it's clear to me how to address it... but however you do it, it should be clear.

Generic Range Class

(1) Comparator
Your source code looks rather synthetical. In the real world, if one wants to describe the actual drawing range in two descrete ranges (the question is why one would do that?), he would just use two Range&lt;<strong>Integer</strong>&gt;, which is much easier to understand than using two Range&lt;<strong>Point</strong>&gt; with the same point found in two mostly identical ranges. But, to stay in the analysis level, actually what one really would do here is to not ask whether a point is located in two ranges, but whether a point is contained in one rectangle (i. e. Rectangle.contains(Point)) as it looks rather obvious to use a DSL for geometry here instead of dealing with the internals of the elements. The range pattern as I understand it, is always single dimensioned: It describes the first and last element on a sequence of elements. But when you pass in Point, then it would be two dimensional, so your trouble is not about missing Comparable (which is an implementation issue) but about dealing with dimensions (which is an analysis issue). Thinking to the end, what you express with Range&lt;Point&gt;.contains(Point) semantically in fact would more be like "Is this point located on the line described by the range?" and not "Is this point located within a rectangle described by the two points provided to the range constructor?" (what you like to reach), due to the single/dual dimension semantical difference. In fact, Range&lt;Point&gt; interpreted in the single dimension way (i. e. the way the range pattern targets) would lead to the conclusion that Point must implement Comparable (which will check both dimensions), while interpreted in a dual dimension would lead to Range&lt;Point, Point&gt; for handling two-dimensional ranges (which is, what Rectangle does due to good reason, and why nobody actually has two separated ranges in the real world for that).
About code mechanics: If you have a class that is natually comparable (others just don't make sense as I think) but which does not implement Comparable, that class is just incomplete (like it would be incomplete when using inside of a Collection but not providing equals and hashCode). It is the sense of Comparable to be implemented for using a class in Range, in analogy. It makes no sense to not implement it. So, that would be a design fault and must get fixed by the author. If he doesn't do it, you might be better off writing your own class instead of relying a buggy or unfinished class provided by an author not having understood what Comparable is good for and mis-using the Comparator class, which is intended for a different semantic of comparison (and hence is not located in java.lang by intention).
(2) Distance
You could only solve this by defining a generic interface for differences (unless you restrict use of ranges to work with ordinals only, where the difference is integer implicitly). Good luck at defining one and convincing the world to implemented it with any class.

Generic Range Class

In Range.java, "greater than" and "less than" is queried by comparing the return value of "compareTo" against 1 and -1, respectively.
The specification of java.lang.Comparable, however, only differentiates the cases "positive integer", "negative integer" and 0. In other words: Wouldn't it be perfectly legal for a class implementing Comparable to return -23 for a.compareTo(b) and 23 for b.compareTo(a)? If so, this Range class would not work with objects of this class.

Generic Range Class

Thank you for reporting this bug, which was just fixed. The corrected class is available for download.

Generic Range Class

java.lang.String does this.
Test case:
// String.compareTo "compares two strings lexicographically".
Range<String> range = new Range<String>("c", "e");
System.out.println(range.contains("a"));
System.out.println(range.contains("b"));
System.out.println(range.contains("c"));
System.out.println(range.contains("d"));
System.out.println(range.contains("e"));
System.out.println(range.contains("f"));
System.out.println(range.contains("g"));
// Expected output:
// false (a), false (b), true (c), true (d), true (e), false (f), false (g)
// Actual output:
// true, false, true, true, true, false, true

Generic Range Class

Thank you for reporting this bug, which was just fixed. The corrected class is available for download.

Hi Mark, The lower to upper

Hi Mark,
The lower to upper bound comparison in the constructor also needs to be changed to consider all positive values a Comparator may return, instead of the following code.

lowerBound.compareTo(upperBound) == 1

Thoughts!!!
~Linu Raj.

Hi Mark, The lower to upper

Oops... Seems I forgot one place when fixing the compareTo-Bug. Thanks for telling me. Corrected class can be downloaded now.