Skip to content

Commit

Permalink
Add RangeMap.putCoalescing() in order to support coalesced RangeMap e…
Browse files Browse the repository at this point in the history
…ntries.

#2665

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=143507326
  • Loading branch information
dimo414 authored and cpovirk committed Jan 4, 2017
1 parent 7b53ffd commit 1efc5ce
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 1 deletion.
116 changes: 116 additions & 0 deletions guava-tests/test/com/google/common/collect/TreeRangeMapTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,85 @@ public void testPutTwoAndRemove() {
}
}

// identical to testPutTwoAndRemove,
// verifies that putCoalescing() doesn't cause any mappings to change relative to put()
public void testPutCoalescingTwoAndRemove() {
for (Range<Integer> rangeToPut1 : RANGES) {
for (Range<Integer> rangeToPut2 : RANGES) {
for (Range<Integer> rangeToRemove : RANGES) {
Map<Integer, Integer> model = Maps.newHashMap();
putModel(model, rangeToPut1, 1);
putModel(model, rangeToPut2, 2);
removeModel(model, rangeToRemove);
RangeMap<Integer, Integer> test = TreeRangeMap.create();
test.putCoalescing(rangeToPut1, 1);
test.putCoalescing(rangeToPut2, 2);
test.remove(rangeToRemove);
verify(model, test);
}
}
}
}

public void testPutCoalescing() {
// {[0..1): 1, [1..2): 1, [2..3): 2} -> {[0..2): 1, [2..3): 2}
RangeMap<Integer, Integer> rangeMap = TreeRangeMap.create();
rangeMap.putCoalescing(Range.closedOpen(0, 1), 1);
rangeMap.putCoalescing(Range.closedOpen(1, 2), 1);
rangeMap.putCoalescing(Range.closedOpen(2, 3), 2);
assertEquals(
ImmutableMap.of(Range.closedOpen(0, 2), 1, Range.closedOpen(2, 3), 2),
rangeMap.asMapOfRanges());
}

public void testPutCoalescingEmpty() {
RangeMap<Integer, Integer> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closedOpen(0, 1), 1);
rangeMap.put(Range.closedOpen(1, 2), 1);
assertEquals(
ImmutableMap.of(Range.closedOpen(0, 1), 1, Range.closedOpen(1, 2), 1),
rangeMap.asMapOfRanges());

rangeMap.putCoalescing(Range.closedOpen(1, 1), 1); // empty range coalesces connected ranges
assertEquals(ImmutableMap.of(Range.closedOpen(0, 2), 1), rangeMap.asMapOfRanges());
}

public void testPutCoalescingComplex() {
// {[0..1): 1, [1..3): 1, [3..5): 1, [7..10): 2, [12..15): 2, [18..19): 3}
RangeMap<Integer, Integer> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closedOpen(0, 1), 1);
rangeMap.put(Range.closedOpen(1, 3), 1);
rangeMap.put(Range.closedOpen(3, 5), 1);
rangeMap.put(Range.closedOpen(7, 10), 2);
rangeMap.put(Range.closedOpen(12, 15), 2);
rangeMap.put(Range.closedOpen(18, 19), 3);

rangeMap.putCoalescing(Range.closedOpen(-5, -4), 0); // disconnected
rangeMap.putCoalescing(Range.closedOpen(-6, -5), 0); // lower than minimum

rangeMap.putCoalescing(Range.closedOpen(2, 4), 1); // between
rangeMap.putCoalescing(Range.closedOpen(9, 14), 0); // different value
rangeMap.putCoalescing(Range.closedOpen(17, 20), 3); // enclosing

rangeMap.putCoalescing(Range.closedOpen(22, 23), 4); // disconnected
rangeMap.putCoalescing(Range.closedOpen(23, 25), 4); // greater than minimum

// {[-6..-4): 0, [0..1): 1, [1..5): 1, [7..9): 2,
// [9..14): 0, [14..15): 2, [17..20): 3, [22..25): 4}
assertEquals(
new ImmutableMap.Builder<>()
.put(Range.closedOpen(-6, -4), 0)
.put(Range.closedOpen(0, 1), 1) // not coalesced
.put(Range.closedOpen(1, 5), 1)
.put(Range.closedOpen(7, 9), 2)
.put(Range.closedOpen(9, 14), 0)
.put(Range.closedOpen(14, 15), 2)
.put(Range.closedOpen(17, 20), 3)
.put(Range.closedOpen(22, 25), 4)
.build(),
rangeMap.asMapOfRanges());
}

public void testSubRangeMapExhaustive() {
for (Range<Integer> range1 : RANGES) {
for (Range<Integer> range2 : RANGES) {
Expand Down Expand Up @@ -519,6 +598,43 @@ public void testSubRangeMapPut() {
rangeMap.asMapOfRanges());
}

public void testSubRangeMapPutCoalescing() {
RangeMap<Integer, Integer> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.open(3, 7), 1);
rangeMap.put(Range.closed(9, 10), 2);
rangeMap.put(Range.closed(12, 16), 3);
RangeMap<Integer, Integer> sub = rangeMap.subRangeMap(Range.closed(5, 11));
assertEquals(
ImmutableMap.of(Range.closedOpen(5, 7), 1, Range.closed(9, 10), 2), sub.asMapOfRanges());
sub.putCoalescing(Range.closed(7, 9), 2);
assertEquals(
ImmutableMap.of(Range.closedOpen(5, 7), 1, Range.closed(7, 10), 2), sub.asMapOfRanges());
assertEquals(
ImmutableMap.of(Range.open(3, 7), 1, Range.closed(7, 10), 2, Range.closed(12, 16), 3),
rangeMap.asMapOfRanges());

sub.putCoalescing(Range.singleton(7), 1);
assertEquals(
ImmutableMap.of(Range.closed(5, 7), 1, Range.openClosed(7, 10), 2), sub.asMapOfRanges());
assertEquals(
ImmutableMap.of(
Range.open(3, 5),
1,
Range.closed(5, 7),
1,
Range.openClosed(7, 10),
2,
Range.closed(12, 16),
3),
rangeMap.asMapOfRanges());

try {
sub.putCoalescing(Range.open(9, 12), 5);
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException expected) {
}
}

public void testSubRangeMapRemove() {
RangeMap<Integer, Integer> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.open(3, 7), 1);
Expand Down
13 changes: 12 additions & 1 deletion guava/src/com/google/common/collect/ImmutableRangeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;

import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -213,6 +212,18 @@ public void put(Range<K> range, V value) {
throw new UnsupportedOperationException();
}

/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
* @throws UnsupportedOperationException always
* @deprecated Unsupported operation.
*/
@Deprecated
@Override
public void putCoalescing(Range<K> range, V value) {
throw new UnsupportedOperationException();
}

/**
* Guaranteed to throw an exception and leave the {@code RangeMap} unmodified.
*
Expand Down
19 changes: 19 additions & 0 deletions guava/src/com/google/common/collect/RangeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtIncompatible;
import java.util.Collection;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -71,6 +72,24 @@ public interface RangeMap<K extends Comparable, V> {
*/
void put(Range<K> range, V value);

/**
* Maps a range to a specified value, coalescing this range with any existing ranges with the same
* value that are {@linkplain Range#isConnected connected} to this range.
*
* <p>The behavior of {@link #get(Comparable) get(k)} after calling this method is identical to
* the behavior described in {@link #put(Range, Object) put(range, value)}, however the ranges
* returned from {@link #asMapOfRanges} will be different if there were existing entries which
* connect to the given range and value.
*
* <p>Even if the input range is empty, if it is connected on both sides by ranges mapped to the
* same value those two ranges will be coalesced.
*
* <p><b>Note:</b> coalescing requires calling {@code .equals()} on any connected values, which
* may be expensive depending on the value type. Using this method on range maps with large values
* such as {@link Collection} types is discouraged.
*/
void putCoalescing(Range<K> range, V value);

/**
* Puts all the associations from {@code rangeMap} into this range map (optional operation).
*/
Expand Down
56 changes: 56 additions & 0 deletions guava/src/com/google/common/collect/TreeRangeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,50 @@ public Entry<Range<K>, V> getEntry(K key) {

@Override
public void put(Range<K> range, V value) {
// don't short-circuit if the range is empty - it may be between two ranges we can coalesce.
if (!range.isEmpty()) {
checkNotNull(value);
remove(range);
entriesByLowerBound.put(range.lowerBound, new RangeMapEntry<K, V>(range, value));
}
}

@Override
public void putCoalescing(Range<K> range, V value) {
if (entriesByLowerBound.isEmpty()) {
put(range, value);
return;
}

Range<K> coalescedRange = coalescedRange(range, checkNotNull(value));
put(coalescedRange, value);
}

/** Computes the coalesced range for the given range+value - does not mutate the map. */
private Range<K> coalescedRange(Range<K> range, V value) {
Range<K> coalescedRange = range;
Map.Entry<Cut<K>, RangeMapEntry<K, V>> lowerEntry =
entriesByLowerBound.lowerEntry(range.lowerBound);
coalescedRange = coalesce(coalescedRange, value, lowerEntry);

Map.Entry<Cut<K>, RangeMapEntry<K, V>> higherEntry =
entriesByLowerBound.floorEntry(range.upperBound);
coalescedRange = coalesce(coalescedRange, value, higherEntry);

return coalescedRange;
}

/** Returns the range that spans the given range and entry, if the entry can be coalesced. */
private static <K extends Comparable, V> Range<K> coalesce(
Range<K> range, V value, @Nullable Map.Entry<Cut<K>, RangeMapEntry<K, V>> entry) {
if (entry != null
&& entry.getValue().getKey().isConnected(range)
&& entry.getValue().getValue().equals(value)) {
return range.span(entry.getValue().getKey());
}
return range;
}

@Override
public void putAll(RangeMap<K, V> rangeMap) {
for (Map.Entry<Range<K>, V> entry : rangeMap.asMapOfRanges().entrySet()) {
Expand Down Expand Up @@ -292,6 +329,13 @@ public void put(Range range, Object value) {
"Cannot insert range " + range + " into an empty subRangeMap");
}

@Override
public void putCoalescing(Range range, Object value) {
checkNotNull(range);
throw new IllegalArgumentException(
"Cannot insert range " + range + " into an empty subRangeMap");
}

@Override
public void putAll(RangeMap rangeMap) {
if (!rangeMap.asMapOfRanges().isEmpty()) {
Expand Down Expand Up @@ -386,6 +430,18 @@ public void put(Range<K> range, V value) {
TreeRangeMap.this.put(range, value);
}

@Override
public void putCoalescing(Range<K> range, V value) {
if (entriesByLowerBound.isEmpty() || range.isEmpty() || !subRange.encloses(range)) {
put(range, value);
return;
}

Range<K> coalescedRange = coalescedRange(range, checkNotNull(value));
// only coalesce ranges within the subRange
put(coalescedRange.intersection(subRange), value);
}

@Override
public void putAll(RangeMap<K, V> rangeMap) {
if (rangeMap.asMapOfRanges().isEmpty()) {
Expand Down

0 comments on commit 1efc5ce

Please sign in to comment.