Skip to content

Commit

Permalink
Add a gap method to Range which computes the range that lies between …
Browse files Browse the repository at this point in the history
…two ranges. This operation is particularly useful as a replacement for Joda Time's Interval.gap when migrating to Java Time which has no Interval class.

Joda Time:
Interval interval = ...;
Interval gap = interval.gap(interval);

Java Time (after this CL):
Range<Instant> interval = ...;
Range<Instant> gap = interval.gap(otherInterval);

RELNOTES=Adds a gap() method to Range that computes the Range that lies between them.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=208259360
  • Loading branch information
mdiamond authored and ronshapiro committed Aug 16, 2018
1 parent f264e96 commit a9dd709
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 0 deletions.
48 changes: 48 additions & 0 deletions android/guava-tests/test/com/google/common/collect/RangeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,54 @@ public void testIntersection_general() {
}
}

public void testGap_overlapping() {
Range<Integer> range = Range.closedOpen(3, 5);

try {
range.gap(Range.closed(4, 6));
fail();
} catch (IllegalArgumentException expected) {
}
try {
range.gap(Range.closed(2, 4));
fail();
} catch (IllegalArgumentException expected) {
}
try {
range.gap(Range.closed(2, 3));
fail();
} catch (IllegalArgumentException expected) {
}
}

public void testGap_connectedAdjacentYieldsEmpty() {
Range<Integer> range = Range.open(3, 4);

assertEquals(Range.closedOpen(4, 4), range.gap(Range.atLeast(4)));
assertEquals(Range.openClosed(3, 3), range.gap(Range.atMost(3)));
}

public void testGap_general() {
Range<Integer> openRange = Range.open(4, 8);
Range<Integer> closedRange = Range.closed(4, 8);

// first range open end, second range open start
assertEquals(Range.closed(2, 4), Range.lessThan(2).gap(openRange));
assertEquals(Range.closed(2, 4), openRange.gap(Range.lessThan(2)));

// first range closed end, second range open start
assertEquals(Range.openClosed(2, 4), Range.atMost(2).gap(openRange));
assertEquals(Range.openClosed(2, 4), openRange.gap(Range.atMost(2)));

// first range open end, second range closed start
assertEquals(Range.closedOpen(2, 4), Range.lessThan(2).gap(closedRange));
assertEquals(Range.closedOpen(2, 4), closedRange.gap(Range.lessThan(2)));

// first range closed end, second range closed start
assertEquals(Range.open(2, 4), Range.atMost(2).gap(closedRange));
assertEquals(Range.open(2, 4), closedRange.gap(Range.atMost(2)));
}

public void testSpan_general() {
Range<Integer> range = Range.closed(4, 8);

Expand Down
24 changes: 24 additions & 0 deletions android/guava/src/com/google/common/collect/Range.java
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,30 @@ public Range<C> intersection(Range<C> connectedRange) {
}
}

/**
* Returns the maximal range lying between this range and {@code otherRange}, if such a range
* exists. The resulting range may be empty if the two ranges are adjacent but non-overlapping.
*
* <p>For example, the gap of {@code [1..5]} and {@code (7..10)} is {@code (5..7]}. The resulting
* range may be empty; for example, the gap between {@code [1..5)} {@code [5..7)} yields the empty
* range {@code [5..5)}.
*
* <p>The gap exists if and only if the two ranges are either disconnected or immediately adjacent
* (any intersection must be an empty range).
*
* <p>The gap operation is commutative.
*
* @throws IllegalArgumentException if this range and {@code otherRange} have a nonempty
* intersection
* @since NEXT
*/
public Range<C> gap(Range<C> otherRange) {
boolean isThisFirst = this.lowerBound.compareTo(otherRange.lowerBound) < 0;
Range<C> firstRange = isThisFirst ? this : otherRange;
Range<C> secondRange = isThisFirst ? otherRange : this;
return create(firstRange.upperBound, secondRange.lowerBound);
}

/**
* Returns the minimal range that {@linkplain #encloses encloses} both this range and {@code
* other}. For example, the span of {@code [1..3]} and {@code (5..7)} is {@code [1..7)}.
Expand Down
15 changes: 15 additions & 0 deletions guava-gwt/test/com/google/common/collect/RangeTest_gwt.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ public void testEquivalentFactories() throws Exception {
testCase.testEquivalentFactories();
}

public void testGap_connectedAdjacentYieldsEmpty() throws Exception {
com.google.common.collect.RangeTest testCase = new com.google.common.collect.RangeTest();
testCase.testGap_connectedAdjacentYieldsEmpty();
}

public void testGap_general() throws Exception {
com.google.common.collect.RangeTest testCase = new com.google.common.collect.RangeTest();
testCase.testGap_general();
}

public void testGap_overlapping() throws Exception {
com.google.common.collect.RangeTest testCase = new com.google.common.collect.RangeTest();
testCase.testGap_overlapping();
}

public void testGreaterThan() throws Exception {
com.google.common.collect.RangeTest testCase = new com.google.common.collect.RangeTest();
testCase.testGreaterThan();
Expand Down
48 changes: 48 additions & 0 deletions guava-tests/test/com/google/common/collect/RangeTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,54 @@ public void testIntersection_general() {
}
}

public void testGap_overlapping() {
Range<Integer> range = Range.closedOpen(3, 5);

try {
range.gap(Range.closed(4, 6));
fail();
} catch (IllegalArgumentException expected) {
}
try {
range.gap(Range.closed(2, 4));
fail();
} catch (IllegalArgumentException expected) {
}
try {
range.gap(Range.closed(2, 3));
fail();
} catch (IllegalArgumentException expected) {
}
}

public void testGap_connectedAdjacentYieldsEmpty() {
Range<Integer> range = Range.open(3, 4);

assertEquals(Range.closedOpen(4, 4), range.gap(Range.atLeast(4)));
assertEquals(Range.openClosed(3, 3), range.gap(Range.atMost(3)));
}

public void testGap_general() {
Range<Integer> openRange = Range.open(4, 8);
Range<Integer> closedRange = Range.closed(4, 8);

// first range open end, second range open start
assertEquals(Range.closed(2, 4), Range.lessThan(2).gap(openRange));
assertEquals(Range.closed(2, 4), openRange.gap(Range.lessThan(2)));

// first range closed end, second range open start
assertEquals(Range.openClosed(2, 4), Range.atMost(2).gap(openRange));
assertEquals(Range.openClosed(2, 4), openRange.gap(Range.atMost(2)));

// first range open end, second range closed start
assertEquals(Range.closedOpen(2, 4), Range.lessThan(2).gap(closedRange));
assertEquals(Range.closedOpen(2, 4), closedRange.gap(Range.lessThan(2)));

// first range closed end, second range closed start
assertEquals(Range.open(2, 4), Range.atMost(2).gap(closedRange));
assertEquals(Range.open(2, 4), closedRange.gap(Range.atMost(2)));
}

public void testSpan_general() {
Range<Integer> range = Range.closed(4, 8);

Expand Down
24 changes: 24 additions & 0 deletions guava/src/com/google/common/collect/Range.java
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,30 @@ public Range<C> intersection(Range<C> connectedRange) {
}
}

/**
* Returns the maximal range lying between this range and {@code otherRange}, if such a range
* exists. The resulting range may be empty if the two ranges are adjacent but non-overlapping.
*
* <p>For example, the gap of {@code [1..5]} and {@code (7..10)} is {@code (5..7]}. The resulting
* range may be empty; for example, the gap between {@code [1..5)} {@code [5..7)} yields the empty
* range {@code [5..5)}.
*
* <p>The gap exists if and only if the two ranges are either disconnected or immediately adjacent
* (any intersection must be an empty range).
*
* <p>The gap operation is commutative.
*
* @throws IllegalArgumentException if this range and {@code otherRange} have a nonempty
* intersection
* @since NEXT
*/
public Range<C> gap(Range<C> otherRange) {
boolean isThisFirst = this.lowerBound.compareTo(otherRange.lowerBound) < 0;
Range<C> firstRange = isThisFirst ? this : otherRange;
Range<C> secondRange = isThisFirst ? otherRange : this;
return create(firstRange.upperBound, secondRange.lowerBound);
}

/**
* Returns the minimal range that {@linkplain #encloses encloses} both this range and {@code
* other}. For example, the span of {@code [1..3]} and {@code (5..7)} is {@code [1..7)}.
Expand Down

2 comments on commit a9dd709

@zhanhb
Copy link

@zhanhb zhanhb commented on a9dd709 Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, we should got IAE insteadof AssertionError.

Range.atLeast(1).gap(Range.atLeast(2));
java.lang.AssertionError
	at com.google.common.collect.Cut$AboveAll.describeAsLowerBound(Cut.java:259)
	at com.google.common.collect.Range.toString(Range.java:674)
	at com.google.common.collect.Range.<init>(Range.java:357)
	at com.google.common.collect.Range.create(Range.java:155)
	at com.google.common.collect.Range.gap(Range.java:582)

@cpovirk
Copy link
Member

@cpovirk cpovirk commented on a9dd709 Sep 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, thank you. #4004

Please sign in to comment.