org.datavec.dataframe.util.collections.IntTreeRangeSet Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of datavec-dataframe Show documentation
Show all versions of datavec-dataframe Show documentation
High-performance Java Dataframe with integrated columnar storage (fork of tablesaw)
package org.datavec.dataframe.util.collections;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.base.MoreObjects;
import com.google.common.collect.AbstractIterator;
import com.google.common.collect.BoundType;
import com.google.common.collect.ForwardingCollection;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.Iterators;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.google.common.collect.PeekingIterator;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import javax.annotation.Nullable;
/**
* An implementation of {@link IntRangeSet} backed by a {@link TreeMap}.
*/
public class IntTreeRangeSet extends AbstractIntRangeSet {
final NavigableMap rangesByLowerBound;
/**
* Creates an empty {@code IntTreeRangeSet} instance.
*/
public static IntTreeRangeSet create() {
return new IntTreeRangeSet(new TreeMap<>());
}
/**
* Returns a {@code IntTreeRangeSet} initialized with the ranges in the specified range set.
*/
public static IntTreeRangeSet create(IntRangeSet rangeSet) {
IntTreeRangeSet result = create();
result.addAll(rangeSet);
return result;
}
private IntTreeRangeSet(NavigableMap rangesByLowerCut) {
this.rangesByLowerBound = rangesByLowerCut;
}
private transient Set asRanges;
@Override
public Set asRanges() {
Set result = asRanges;
return (result == null) ? asRanges = new AsRanges() : result;
}
final class AsRanges extends ForwardingCollection implements Set {
@Override
protected Collection delegate() {
return rangesByLowerBound.values();
}
}
@Override
@Nullable
public IntRange rangeContaining(int value) {
checkNotNull(value);
Entry floorEntry = rangesByLowerBound.floorEntry(IntCut.belowValue(value));
if (floorEntry != null && floorEntry.getValue().contains(value)) {
return floorEntry.getValue();
} else {
return null;
}
}
@Override
public boolean encloses(IntRange range) {
checkNotNull(range);
Entry floorEntry = rangesByLowerBound.floorEntry(range.lowerBound);
return floorEntry != null && floorEntry.getValue().encloses(range);
}
@Nullable
private IntRange rangeEnclosing(IntRange range) {
checkNotNull(range);
Entry floorEntry = rangesByLowerBound.floorEntry(range.lowerBound);
return (floorEntry != null && floorEntry.getValue().encloses(range))
? floorEntry.getValue()
: null;
}
@Override
public IntRange span() {
Entry firstEntry = rangesByLowerBound.firstEntry();
Entry lastEntry = rangesByLowerBound.lastEntry();
if (firstEntry == null) {
throw new NoSuchElementException();
}
return IntRange.create(firstEntry.getValue().lowerBound, lastEntry.getValue().upperBound);
}
@Override
public void add(IntRange rangeToAdd) {
checkNotNull(rangeToAdd);
if (rangeToAdd.isEmpty()) {
return;
}
// We will use { } to illustrate ranges currently in the range set, and < >
// to illustrate rangeToAdd.
IntCut lbToAdd = rangeToAdd.lowerBound;
IntCut ubToAdd = rangeToAdd.upperBound;
Entry entryBelowLB = rangesByLowerBound.lowerEntry(lbToAdd);
if (entryBelowLB != null) {
// { <
IntRange rangeBelowLB = entryBelowLB.getValue();
if (rangeBelowLB.upperBound.compareTo(lbToAdd) >= 0) {
// { < }, and we will need to coalesce
if (rangeBelowLB.upperBound.compareTo(ubToAdd) >= 0) {
// { < > }
ubToAdd = rangeBelowLB.upperBound;
/*
* TODO(cpovirk): can we just "return;" here? Or, can we remove this if() entirely? If
* not, add tests to demonstrate the problem with each approach
*/
}
lbToAdd = rangeBelowLB.lowerBound;
}
}
Entry entryBelowUB = rangesByLowerBound.floorEntry(ubToAdd);
if (entryBelowUB != null) {
// { >
IntRange rangeBelowUB = entryBelowUB.getValue();
if (rangeBelowUB.upperBound.compareTo(ubToAdd) >= 0) {
// { > }, and we need to coalesce
ubToAdd = rangeBelowUB.upperBound;
}
}
// Remove ranges which are strictly enclosed.
rangesByLowerBound.subMap(lbToAdd, ubToAdd).clear();
replaceRangeWithSameLowerBound(IntRange.create(lbToAdd, ubToAdd));
}
@Override
public void remove(IntRange rangeToRemove) {
checkNotNull(rangeToRemove);
if (rangeToRemove.isEmpty()) {
return;
}
// We will use { } to illustrate ranges currently in the range set, and < >
// to illustrate rangeToRemove.
Entry entryBelowLB = rangesByLowerBound.lowerEntry(rangeToRemove.lowerBound);
if (entryBelowLB != null) {
// { <
IntRange rangeBelowLB = entryBelowLB.getValue();
if (rangeBelowLB.upperBound.compareTo(rangeToRemove.lowerBound) >= 0) {
// { < }, and we will need to subdivide
if (rangeToRemove.hasUpperBound()
&& rangeBelowLB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) {
// { < > }
replaceRangeWithSameLowerBound(
IntRange.create(rangeToRemove.upperBound, rangeBelowLB.upperBound));
}
replaceRangeWithSameLowerBound(
IntRange.create(rangeBelowLB.lowerBound, rangeToRemove.lowerBound));
}
}
Entry entryBelowUB = rangesByLowerBound.floorEntry(rangeToRemove.upperBound);
if (entryBelowUB != null) {
// { >
IntRange rangeBelowUB = entryBelowUB.getValue();
if (rangeToRemove.hasUpperBound()
&& rangeBelowUB.upperBound.compareTo(rangeToRemove.upperBound) >= 0) {
// { > }
replaceRangeWithSameLowerBound(
IntRange.create(rangeToRemove.upperBound, rangeBelowUB.upperBound));
}
}
rangesByLowerBound.subMap(rangeToRemove.lowerBound, rangeToRemove.upperBound).clear();
}
private void replaceRangeWithSameLowerBound(IntRange range) {
if (range.isEmpty()) {
rangesByLowerBound.remove(range.lowerBound);
} else {
rangesByLowerBound.put(range.lowerBound, range);
}
}
private transient IntRangeSet complement;
@Override
public IntRangeSet complement() {
IntRangeSet result = complement;
return (result == null) ? complement = new Complement() : result;
}
static final class RangesByUpperBound
extends TreeMap {
private final NavigableMap rangesByLowerBound;
/**
* upperBoundWindow represents the headMap/subMap/tailMap view of the entire "ranges by upper
* bound" map; it's a constraint on the *keys*, and does not affect the values.
*/
private final IntRange upperBoundWindow;
RangesByUpperBound(NavigableMap rangesByLowerBound) {
this.rangesByLowerBound = rangesByLowerBound;
this.upperBoundWindow = IntRange.all();
}
private RangesByUpperBound(
NavigableMap rangesByLowerBound, IntRange upperBoundWindow) {
this.rangesByLowerBound = rangesByLowerBound;
this.upperBoundWindow = upperBoundWindow;
}
private NavigableMap subMap(IntRange window) {
if (window.isConnected(upperBoundWindow)) {
return new RangesByUpperBound(rangesByLowerBound, window.intersection(upperBoundWindow));
} else {
return ImmutableSortedMap.of();
}
}
@Override
public NavigableMap subMap(
IntCut fromKey, boolean fromInclusive, IntCut toKey, boolean toInclusive) {
return subMap(IntRange.range(
fromKey.endpoint(), boundTypeForBoolean(fromInclusive),
toKey.endpoint(), boundTypeForBoolean(toInclusive)));
}
@Override
public NavigableMap headMap(IntCut toKey, boolean inclusive) {
return subMap(IntRange.upTo(toKey.endpoint(), boundTypeForBoolean(inclusive)));
}
@Override
public NavigableMap tailMap(IntCut fromKey, boolean inclusive) {
return subMap(IntRange.downTo(fromKey.endpoint(), boundTypeForBoolean(inclusive)));
}
@Override
public Comparator super IntCut> comparator() {
return Ordering.natural();
}
@Override
public boolean containsKey(@Nullable Object key) {
return get(key) != null;
}
@Override
public IntRange get(@Nullable Object key) {
if (key instanceof IntCut) {
try {
@SuppressWarnings("unchecked") // we catch CCEs
IntCut cut = (IntCut) key;
if (!upperBoundWindow.contains(cut.endpoint())) {
return null;
}
Entry candidate = rangesByLowerBound.lowerEntry(cut);
if (candidate != null && candidate.getValue().upperBound.equals(cut)) {
return candidate.getValue();
}
} catch (ClassCastException e) {
return null;
}
}
return null;
}
Iterator> entryIterator() {
/*
* We want to start the iteration at the first range where the upper bound is in
* upperBoundWindow.
*/
final Iterator backingItr;
if (!upperBoundWindow.hasLowerBound()) {
backingItr = rangesByLowerBound.values().iterator();
} else {
Entry lowerEntry =
rangesByLowerBound.lowerEntry(upperBoundWindow.lowerBound());
if (lowerEntry == null) {
backingItr = rangesByLowerBound.values().iterator();
} else if (upperBoundWindow.lowerBound.isLessThan(lowerEntry.getValue().upperEndpoint())) {
backingItr = rangesByLowerBound.tailMap(lowerEntry.getKey(), true).values().iterator();
} else {
backingItr = rangesByLowerBound.tailMap(upperBoundWindow.lowerBound(), true)
.values().iterator();
}
}
return new AbstractIterator>() {
@Override
protected Entry computeNext() {
if (!backingItr.hasNext()) {
return endOfData();
}
IntRange range = backingItr.next();
if (upperBoundWindow.upperBound.isLessThan(range.upperEndpoint())) {
return endOfData();
} else {
return Maps.immutableEntry(range.upperBound, range);
}
}
};
}
Iterator> descendingEntryIterator() {
Collection candidates;
if (upperBoundWindow.hasUpperBound()) {
candidates = rangesByLowerBound.headMap(upperBoundWindow.upperBound(), false)
.descendingMap().values();
} else {
candidates = rangesByLowerBound.descendingMap().values();
}
final PeekingIterator backingItr = Iterators.peekingIterator(candidates.iterator());
if (backingItr.hasNext()
&& upperBoundWindow.upperBound.isLessThan(backingItr.peek().upperEndpoint())) {
backingItr.next();
}
return new AbstractIterator>() {
@Override
protected Entry computeNext() {
if (!backingItr.hasNext()) {
return endOfData();
}
IntRange range = backingItr.next();
return upperBoundWindow.lowerBound.isLessThan(range.upperEndpoint())
? Maps.immutableEntry(range.upperBound, range)
: endOfData();
}
};
}
@Override
public int size() {
if (upperBoundWindow.equals(IntRange.all())) {
return rangesByLowerBound.size();
}
return Iterators.size(entryIterator());
}
@Override
public boolean isEmpty() {
return upperBoundWindow.equals(IntRange.all())
? rangesByLowerBound.isEmpty()
: !entryIterator().hasNext();
}
}
private static final class ComplementRangesByLowerBound
extends TreeMap {
private final NavigableMap positiveRangesByLowerBound;
private final NavigableMap positiveRangesByUpperBound;
/**
* complementLowerBoundWindow represents the headMap/subMap/tailMap view of the entire
* "complement ranges by lower bound" map; it's a constraint on the *keys*, and does not affect
* the values.
*/
private final IntRange complementLowerBoundWindow;
ComplementRangesByLowerBound(NavigableMap positiveRangesByLowerBound) {
this(positiveRangesByLowerBound, IntRange.all());
}
private ComplementRangesByLowerBound(
NavigableMap positiveRangesByLowerBound, IntRange window) {
this.positiveRangesByLowerBound = positiveRangesByLowerBound;
this.positiveRangesByUpperBound = new RangesByUpperBound(positiveRangesByLowerBound);
this.complementLowerBoundWindow = window;
}
private NavigableMap subMap(IntRange subWindow) {
if (!complementLowerBoundWindow.isConnected(subWindow)) {
return ImmutableSortedMap.of();
} else {
subWindow = subWindow.intersection(complementLowerBoundWindow);
return new ComplementRangesByLowerBound(positiveRangesByLowerBound, subWindow);
}
}
@Override
public NavigableMap subMap(
IntCut fromKey, boolean fromInclusive, IntCut toKey, boolean toInclusive) {
return subMap(
IntRange.range(
fromKey.endpoint(), boundTypeForBoolean(fromInclusive),
toKey.endpoint(), boundTypeForBoolean(toInclusive)));
}
@Override
public NavigableMap headMap(IntCut toKey, boolean inclusive) {
return subMap(IntRange.upTo(toKey.endpoint(), boundTypeForBoolean(inclusive)));
}
@Override
public NavigableMap tailMap(IntCut fromKey, boolean inclusive) {
return subMap(IntRange.downTo(fromKey.endpoint(), boundTypeForBoolean(inclusive)));
}
@Override
public Comparator super IntCut> comparator() {
return Ordering.natural();
}
Iterator> entryIterator() {
/*
* firstComplementRangeLowerBound is the first complement range lower bound inside
* complementLowerBoundWindow. Complement range lower bounds are either positive range upper
* bounds, or IntCut.belowAll().
*
* positiveItr starts at the first positive range with lower bound greater than
* firstComplementRangeLowerBound. (Positive range lower bounds correspond to complement range
* upper bounds.)
*/
Collection positiveRanges;
if (complementLowerBoundWindow.hasLowerBound()) {
positiveRanges =
positiveRangesByUpperBound
.tailMap(
complementLowerBoundWindow.lowerBound(),
complementLowerBoundWindow.lowerBoundType() == BoundType.CLOSED).values();
} else {
positiveRanges = positiveRangesByUpperBound.values();
}
final PeekingIterator positiveItr =
Iterators.peekingIterator(positiveRanges.iterator());
final IntCut firstComplementRangeLowerBound;
if (complementLowerBoundWindow.contains(IntCut.belowAll().endpoint())
&& (!positiveItr.hasNext() || positiveItr.peek().lowerBound != IntCut.belowAll())) {
firstComplementRangeLowerBound = IntCut.belowAll();
} else if (positiveItr.hasNext()) {
firstComplementRangeLowerBound = positiveItr.next().upperBound;
} else {
return Iterators.emptyIterator();
}
return new AbstractIterator>() {
IntCut nextComplementRangeLowerBound = firstComplementRangeLowerBound;
@Override
protected Entry computeNext() {
if (complementLowerBoundWindow.upperBound.isLessThan(nextComplementRangeLowerBound.endpoint())
|| nextComplementRangeLowerBound == IntCut.aboveAll()) {
return endOfData();
}
IntRange negativeRange;
if (positiveItr.hasNext()) {
IntRange positiveRange = positiveItr.next();
negativeRange = IntRange.create(nextComplementRangeLowerBound, positiveRange.lowerBound);
nextComplementRangeLowerBound = positiveRange.upperBound;
} else {
negativeRange = IntRange.create(nextComplementRangeLowerBound, IntCut.aboveAll());
nextComplementRangeLowerBound = IntCut.aboveAll();
}
return Maps.immutableEntry(negativeRange.lowerBound, negativeRange);
}
};
}
Iterator> descendingEntryIterator() {
/*
* firstComplementRangeUpperBound is the upper bound of the last complement range with lower
* bound inside complementLowerBoundWindow.
*
* positiveItr starts at the first positive range with upper bound less than
* firstComplementRangeUpperBound. (Positive range upper bounds correspond to complement range
* lower bounds.)
*/
IntCut startingPoint = complementLowerBoundWindow.hasUpperBound()
? complementLowerBoundWindow.upperBound()
: IntCut.aboveAll();
boolean inclusive = complementLowerBoundWindow.hasUpperBound()
&& complementLowerBoundWindow.upperBoundType() == BoundType.CLOSED;
final PeekingIterator positiveItr =
Iterators.peekingIterator(
positiveRangesByUpperBound
.headMap(startingPoint, inclusive)
.descendingMap()
.values()
.iterator());
IntCut cut;
if (positiveItr.hasNext()) {
cut =
(positiveItr.peek().upperBound == IntCut.aboveAll())
? positiveItr.next().lowerBound
: positiveRangesByLowerBound.higherKey(positiveItr.peek().upperBound);
} else if (!complementLowerBoundWindow.contains(IntCut.belowAll().endpoint())
|| positiveRangesByLowerBound.containsKey(IntCut.belowAll())) {
return Iterators.emptyIterator();
} else {
cut = positiveRangesByLowerBound.higherKey(IntCut.belowAll());
}
final IntCut firstComplementRangeUpperBound =
MoreObjects.firstNonNull(cut, IntCut.aboveAll());
return new AbstractIterator>() {
IntCut nextComplementRangeUpperBound = firstComplementRangeUpperBound;
@Override
protected Entry computeNext() {
if (nextComplementRangeUpperBound == IntCut.belowAll()) {
return endOfData();
} else if (positiveItr.hasNext()) {
IntRange positiveRange = positiveItr.next();
IntRange negativeRange =
IntRange.create(positiveRange.upperBound, nextComplementRangeUpperBound);
nextComplementRangeUpperBound = positiveRange.lowerBound;
if (complementLowerBoundWindow.lowerBound.isLessThan(negativeRange.lowerEndpoint())) {
return Maps.immutableEntry(negativeRange.lowerBound, negativeRange);
}
} else if (complementLowerBoundWindow.lowerBound.isLessThan(IntCut.belowAll().endpoint())) {
IntRange negativeRange = IntRange.create(IntCut.belowAll(), nextComplementRangeUpperBound);
nextComplementRangeUpperBound = IntCut.belowAll();
return Maps.immutableEntry(IntCut.belowAll(), negativeRange);
}
return endOfData();
}
};
}
@Override
public int size() {
return Iterators.size(entryIterator());
}
@Override
@Nullable
public IntRange get(Object key) {
if (key instanceof IntCut) {
try {
@SuppressWarnings("unchecked")
IntCut cut = (IntCut) key;
// tailMap respects the current window
Entry firstEntry = tailMap(cut, true).firstEntry();
if (firstEntry != null && firstEntry.getKey().equals(cut)) {
return firstEntry.getValue();
}
} catch (ClassCastException e) {
return null;
}
}
return null;
}
@Override
public boolean containsKey(Object key) {
return get(key) != null;
}
}
private final class Complement extends IntTreeRangeSet {
Complement() {
super(new ComplementRangesByLowerBound(IntTreeRangeSet.this.rangesByLowerBound));
}
@Override
public void add(IntRange rangeToAdd) {
IntTreeRangeSet.this.remove(rangeToAdd);
}
@Override
public void remove(IntRange rangeToRemove) {
IntTreeRangeSet.this.add(rangeToRemove);
}
@Override
public boolean contains(int value) {
return !IntTreeRangeSet.this.contains(value);
}
@Override
public IntRangeSet complement() {
return IntTreeRangeSet.this;
}
}
private static final class SubRangeSetRangesByLowerBound
extends TreeMap {
/**
* lowerBoundWindow is the headMap/subMap/tailMap view; it only restricts the keys, and does not
* affect the values.
*/
private final IntRange lowerBoundWindow;
/**
* restriction is the subRangeSet view; ranges are truncated to their intersection with
* restriction.
*/
private final IntRange restriction;
private final NavigableMap rangesByLowerBound;
private final NavigableMap rangesByUpperBound;
private SubRangeSetRangesByLowerBound(IntRange lowerBoundWindow, IntRange restriction,
NavigableMap rangesByLowerBound) {
this.lowerBoundWindow = checkNotNull(lowerBoundWindow);
this.restriction = checkNotNull(restriction);
this.rangesByLowerBound = checkNotNull(rangesByLowerBound);
this.rangesByUpperBound = new RangesByUpperBound(rangesByLowerBound);
}
private NavigableMap subMap(IntRange window) {
if (!window.isConnected(lowerBoundWindow)) {
return ImmutableSortedMap.of();
} else {
return new SubRangeSetRangesByLowerBound(
lowerBoundWindow.intersection(window), restriction, rangesByLowerBound);
}
}
@Override
public NavigableMap subMap(
IntCut fromKey, boolean fromInclusive, IntCut toKey, boolean toInclusive) {
return subMap(IntRange.range(
fromKey.endpoint(), boundTypeForBoolean(fromInclusive), toKey.endpoint(), boundTypeForBoolean(toInclusive)));
}
@Override
public NavigableMap headMap(IntCut toKey, boolean inclusive) {
return subMap(IntRange.upTo(toKey.endpoint(), boundTypeForBoolean(inclusive)));
}
@Override
public NavigableMap tailMap(IntCut fromKey, boolean inclusive) {
return subMap(IntRange.downTo(fromKey.endpoint(), boundTypeForBoolean(inclusive)));
}
@Override
public Comparator super IntCut> comparator() {
return Ordering.natural();
}
@Override
public boolean containsKey(@Nullable Object key) {
return get(key) != null;
}
@Override
@Nullable
public IntRange get(@Nullable Object key) {
if (key instanceof IntCut) {
try {
@SuppressWarnings("unchecked") // we catch CCE's
IntCut cut = (IntCut) key;
if (!lowerBoundWindow.contains(cut.endpoint()) || cut.compareTo(restriction.lowerBound) < 0
|| cut.compareTo(restriction.upperBound) >= 0) {
return null;
} else if (cut.equals(restriction.lowerBound)) {
// it might be present, truncated on the left
Map.Entry entry = rangesByLowerBound.floorEntry(cut);
IntRange candidate = valueOrNull(entry);
if (candidate != null && candidate.upperBound.compareTo(restriction.lowerBound) > 0) {
return candidate.intersection(restriction);
}
} else {
IntRange result = rangesByLowerBound.get(cut);
if (result != null) {
return result.intersection(restriction);
}
}
} catch (ClassCastException e) {
return null;
}
}
return null;
}
Iterator> entryIterator() {
if (restriction.isEmpty()) {
return Iterators.emptyIterator();
}
final Iterator completeRangeItr;
if (lowerBoundWindow.upperBound.isLessThan(restriction.lowerEndpoint())) {
return Iterators.emptyIterator();
} else if (lowerBoundWindow.lowerBound.isLessThan(restriction.lowerEndpoint())) {
// starts at the first range with upper bound strictly greater than restriction.lowerBound
completeRangeItr =
rangesByUpperBound.tailMap(restriction.lowerBound, false).values().iterator();
} else {
// starts at the first range with lower bound above lowerBoundWindow.lowerBound
completeRangeItr = rangesByLowerBound.tailMap(lowerBoundWindow.lowerBound(),
lowerBoundWindow.lowerBoundType() == BoundType.CLOSED).values().iterator();
}
final IntCut upperBoundOnLowerBounds = Ordering.natural()
.min(lowerBoundWindow.upperBound, IntCut.belowValue(restriction.upperEndpoint()));
return new AbstractIterator>() {
@Override
protected Entry computeNext() {
if (!completeRangeItr.hasNext()) {
return endOfData();
}
IntRange nextRange = completeRangeItr.next();
if (upperBoundOnLowerBounds.isLessThan(nextRange.lowerEndpoint())) {
return endOfData();
} else {
nextRange = nextRange.intersection(restriction);
return Maps.immutableEntry(nextRange.lowerBound, nextRange);
}
}
};
}
Iterator> descendingEntryIterator() {
if (restriction.isEmpty()) {
return Iterators.emptyIterator();
}
IntCut upperBoundOnLowerBounds = Ordering.natural()
.min(lowerBoundWindow.upperBound, IntCut.belowValue(restriction.upperEndpoint()));
final Iterator completeRangeItr = rangesByLowerBound.headMap(
upperBoundOnLowerBounds,
upperBoundOnLowerBounds.typeAsUpperBound() == BoundType.CLOSED)
.descendingMap().values().iterator();
return new AbstractIterator>() {
@Override
protected Entry computeNext() {
if (!completeRangeItr.hasNext()) {
return endOfData();
}
IntRange nextRange = completeRangeItr.next();
if (restriction.lowerBound.compareTo(nextRange.upperBound) >= 0) {
return endOfData();
}
nextRange = nextRange.intersection(restriction);
if (lowerBoundWindow.contains(nextRange.lowerEndpoint())) {
return Maps.immutableEntry(nextRange.lowerBound, nextRange);
} else {
return endOfData();
}
}
};
}
@Override
public int size() {
return Iterators.size(entryIterator());
}
}
@Override
public IntRangeSet subRangeSet(IntRange view) {
return view.equals(IntRange.all()) ? this : new SubRangeSetInt(view);
}
private final class SubRangeSetInt extends IntTreeRangeSet {
private final IntRange restriction;
SubRangeSetInt(IntRange restriction) {
super(new SubRangeSetRangesByLowerBound(
IntRange.all(), restriction, IntTreeRangeSet.this.rangesByLowerBound));
this.restriction = restriction;
}
@Override
public boolean encloses(IntRange range) {
if (!restriction.isEmpty() && restriction.encloses(range)) {
IntRange enclosing = IntTreeRangeSet.this.rangeEnclosing(range);
return enclosing != null && !enclosing.intersection(restriction).isEmpty();
}
return false;
}
@Override
@Nullable
public IntRange rangeContaining(int value) {
if (!restriction.contains(value)) {
return null;
}
IntRange result = IntTreeRangeSet.this.rangeContaining(value);
return (result == null) ? null : result.intersection(restriction);
}
@Override
public void add(IntRange rangeToAdd) {
checkArgument(restriction.encloses(rangeToAdd), "Cannot add range %s to subRangeSet(%s)",
rangeToAdd, restriction);
super.add(rangeToAdd);
}
@Override
public void remove(IntRange rangeToRemove) {
if (rangeToRemove.isConnected(restriction)) {
IntTreeRangeSet.this.remove(rangeToRemove.intersection(restriction));
}
}
@Override
public boolean contains(int value) {
return restriction.contains(value) && IntTreeRangeSet.this.contains(value);
}
@Override
public void clear() {
IntTreeRangeSet.this.remove(restriction);
}
@Override
public IntRangeSet subRangeSet(IntRange view) {
if (view.encloses(restriction)) {
return this;
} else if (view.isConnected(restriction)) {
return new SubRangeSetInt(restriction.intersection(view));
} else {
return new IntTreeRangeSet(new TreeMap<>());
}
}
}
private static BoundType boundTypeForBoolean(boolean inclusive) {
return inclusive ? BoundType.CLOSED : BoundType.OPEN;
}
private static IntRange valueOrNull(@Nullable Map.Entry entry) {
return entry == null ? null : entry.getValue();
}
}