- Notifications
You must be signed in to change notification settings - Fork 11.1k
RangesExplained
List<Double> scores; Iterable<Double> belowMedianScores = Iterables.filter(scores, Range.lessThan(median)); ... Range<Integer> validGrades = Range.closed(1, 12); for(int grade : ContiguousSet.create(validGrades, DiscreteDomain.integers())) { ... }A range, sometimes known as an interval, is a convex (informally, "contiguous" or "unbroken") portion of a particular domain. Formally, convexity means that for any a <= b <= c, range.contains(a) && range.contains(c) implies that range.contains(b).
Ranges may "extend to infinity" -- for example, the range x > 3 contains arbitrarily large values -- or may be finitely constrained, for example 2 <= x < 5. We will use the more compact notation, familiar to programmers with a math background:
(a..b) = {x | a < x < b}[a..b] = {x | a <= x <= b}[a..b) = {x | a <= x < b}(a..b] = {x | a < x <= b}(a..+∞) = {x | x > a}[a..+∞) = {x | x >= a}(-∞..b) = {x | x < b}(-∞..b] = {x | x <= b}(-∞..+∞) = all values
The values a and b used above are called endpoints. To improve consistency, Guava's notion of Range requires that the upper endpoint may not be less than the lower endpoint. The endpoints may be equal only if at least one of the bounds is closed:
-
[a..a]: singleton range -
[a..a); (a..a]: empty, but valid -
(a..a): invalid
A range in Guava has the type Range<C>. All ranges are immutable.
Ranges can be obtained from the static methods on Range:
| Range type | Method |
|---|---|
(a..b) | open(C, C) |
[a..b] | closed(C, C) |
[a..b) | closedOpen(C, C) |
(a..b] | openClosed(C, C) |
(a..+∞) | greaterThan(C) |
[a..+∞) | atLeast(C) |
(-∞..b) | lessThan(C) |
(-∞..b] | atMost(C) |
(-∞..+∞) | all() |
Range.closed("left", "right"); // all strings lexographically between "left" and "right" inclusive Range.lessThan(4.0); // double values strictly less than 4Additionally, Range instances can be constructed by passing the bound types explicitly:
| Range type | Method |
|---|---|
| Bounded on both ends | range(C, BoundType, C, BoundType) |
Unbounded on top ((a..+∞) or [a..+∞)) | downTo(C, BoundType) |
Unbounded on bottom ((-∞..b) or (-∞..b]) | upTo(C, BoundType) |
Here, BoundType is an enum containing the values CLOSED and OPEN.
Range.downTo(4, boundType); // allows you to decide whether or not you want to include 4 Range.range(1, CLOSED, 4, OPEN); // another way of writing Range.closedOpen(1, 4)The fundamental operation of a Range is its contains(C) methods, which behaves exactly as you might expect. Additionally, a Range may be used as a Predicate, and used in functional idioms. Any Range also supports containsAll(Iterable<? extends C>).
Range.closed(1, 3).contains(2); // returns true Range.closed(1, 3).contains(4); // returns false Range.lessThan(5).contains(5); // returns false Range.closed(1, 4).containsAll(Ints.asList(1, 2, 3)); // returns trueTo look at the endpoints of a range, Range exposes the following methods:
-
hasLowerBound()andhasUpperBound(), which check if the range has the specified endpoints, or goes on "through infinity." -
lowerBoundType()andupperBoundType()return theBoundTypefor the corresponding endpoint, which can be eitherCLOSEDorOPEN. If this range does not have the specified endpoint, the method throws anIllegalStateException. -
lowerEndpoint()andupperEndpoint()return the endpoints on the specified end, or throw anIllegalStateExceptionif the range does not have the specified endpoint. -
isEmpty()tests if the range is empty, that is, it has the form[a,a)or(a,a].
Range.closedOpen(4, 4).isEmpty(); // returns true Range.openClosed(4, 4).isEmpty(); // returns true Range.closed(4, 4).isEmpty(); // returns false Range.open(4, 4).isEmpty(); // Range.open throws IllegalArgumentException Range.closed(3, 10).lowerEndpoint(); // returns 3 Range.open(3, 10).lowerEndpoint(); // returns 3 Range.closed(3, 10).lowerBoundType(); // returns CLOSED Range.open(3, 10).upperBoundType(); // returns OPENThe most basic relation on ranges is encloses(Range), which is true if the bounds of the inner range do not extend outside the bounds of the outer range. This is solely dependent on comparisons between the endpoints!
-
[3..6]encloses[4..5] -
(3..6)encloses(3..6) -
[3..6]encloses[4..4)(even though the latter is empty) -
(3..6]does not enclose[3..6] -
[4..5]does not enclose(3..6)even though it contains every value contained by the latter range, although use of discrete domains can address this (see below) -
[3..6]does not enclose(1..1]even though it contains every value contained by the latter range
encloses is a partial ordering.
Given this, Range provides the following operations:
Range.isConnected(Range), which tests if these ranges are connected. Specifically, isConnected tests if there is some range enclosed by both of these ranges, but this is equivalent to the mathematical definition that the union of the ranges must form a connected set (except in the special case of empty ranges).
isConnected is a reflexive, symmetric relation.
Range.closed(3, 5).isConnected(Range.open(5, 10)); // returns true Range.closed(0, 9).isConnected(Range.closed(3, 4)); // returns true Range.closed(0, 5).isConnected(Range.closed(3, 9)); // returns true Range.open(3, 5).isConnected(Range.open(5, 10)); // returns false Range.closed(1, 5).isConnected(Range.closed(6, 10)); // returns falseRange.intersection(Range) returns the maximal range enclosed by both this range and other (which exists iff these ranges are connected), or if no such range exists, throws an IllegalArgumentException.
intersection is a commutative, associative [operation][binary-operation].
Range.closed(3, 5).intersection(Range.open(5, 10)); // returns (5, 5] Range.closed(0, 9).intersection(Range.closed(3, 4)); // returns [3, 4] Range.closed(0, 5).intersection(Range.closed(3, 9)); // returns [3, 5] Range.open(3, 5).intersection(Range.open(5, 10)); // throws IAE Range.closed(1, 5).intersection(Range.closed(6, 10)); // throws IAERange.span(Range) returns the minimal range that encloses both this range and other. If the ranges are both connected, this is their union.
span is a commutative, associative and closed [operation][binary-operation].
Range.closed(3, 5).span(Range.open(5, 10)); // returns [3, 10) Range.closed(0, 9).span(Range.closed(3, 4)); // returns [0, 9] Range.closed(0, 5).span(Range.closed(3, 9)); // returns [0, 9] Range.open(3, 5).span(Range.open(5, 10)); // returns (3, 10) Range.closed(1, 5).span(Range.closed(6, 10)); // returns [1, 10]Some types, but not all Comparable types, are discrete, meaning that ranges bounded on both sides can be enumerated.
In Guava, a DiscreteDomain<C> implements discrete operations for type C. A discrete domain always represents the entire set of values of its type; it cannot represent partial domains such as "prime integers", "strings of length 5," or "timestamps at midnight."
The DiscreteDomain class provides DiscreteDomain instances:
| Type | DiscreteDomain |
|---|---|
Integer | integers() |
Long | longs() |
Once you have a DiscreteDomain, you can use the following Range operations:
-
ContiguousSet.create(range, domain): view aRange<C>as anImmutableSortedSet<C>, with a few extra operations thrown in. (Does not work for unbounded ranges, unless the type itself is bounded.) -
canonical(domain): put ranges in a "canonical form." IfContiguousSet.create(a, domain).equals(ContiguousSet.create(b, domain))and!a.isEmpty(), thena.canonical(domain).equals(b.canonical(domain)). (This does not, however, implya.equals(b).)
ImmutableSortedSet<Integer> set = ContiguousSet.create(Range.open(1, 5), DiscreteDomain.integers()); // set contains [2, 3, 4] ContiguousSet.create(Range.greaterThan(0), DiscreteDomain.integers()); // set contains [1, 2, ..., Integer.MAX_VALUE]Note that ContiguousSet.create does not actually construct the entire range, but instead returns a view of the range as a set.
You can make your own DiscreteDomain objects, but there are several important aspects of the DiscreteDomain contract that you must remember.
- A discrete domain always represents the entire set of values of its type; it cannot represent partial domains such as "prime integers" or "strings of length 5." So you should not, for example, construct a
DiscreteDomain<LocalDateTime>with only minute precision, since this would not contain all possible elements of the type (it has nanosecond precision). - A
DiscreteDomainmay be infinite -- aBigIntegerDiscreteDomain, for example. In this case, you should use the default implementation ofminValue()andmaxValue(), which throw aNoSuchElementException. This forbids you from using theContiguousSet.createmethod on an infinite range, however!
We wanted to strike a very specific balance in Range between power and API complexity, and part of that involved not providing a Comparator-based interface: we don't need to worry about how ranges based on different comparators interact; the API signatures are all significantly simplified; things are just nicer.
On the other hand, if you think you want an arbitrary Comparator, you can do one of the following:
- Use a general
Predicateand notRange. (SinceRangeimplements thePredicateinterface, you can usePredicates.compose(range, function)to get aPredicate.) - Use a wrapper class around your objects that defines the desired ordering.
- Introduction
- Basic Utilities
- Collections
- Graphs
- Caches
- Functional Idioms
- Concurrency
- Strings
- Networking
- Primitives
- Ranges
- I/O
- Hashing
- EventBus
- Math
- Reflection
- Releases
- Tips
- Glossary
- Mailing List
- Stack Overflow
- Android Overview
- Footprint of JDK/Guava data structures