-
Notifications
You must be signed in to change notification settings - Fork 10.9k
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 4
Additionally, 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 true
To 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 theBoundType
for the corresponding endpoint, which can be eitherCLOSED
orOPEN
. If this range does not have the specified endpoint, the method throws anIllegalStateException
. -
lowerEndpoint()
andupperEndpoint()
return the endpoints on the specified end, or throw anIllegalStateException
if 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 OPEN
The 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 false
Range.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 IAE
Range.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 cannot, for example, construct a
DiscreteDomain
to view a set of days in a range, with a JODADateTime
that includes times up to the second: because this would not contain all elements of the type. - A
DiscreteDomain
may be infinite -- aBigInteger
DiscreteDomain
, for example. In this case, you should use the default implementation ofminValue()
andmaxValue()
, which throw aNoSuchElementException
. This forbids you from using theContiguousSet.create
method 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
Predicate
and notRange
. (SinceRange
implements thePredicate
interface, 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