/*
* Copyright (C) 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.facebook.collections;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
/**
* Set of Longs that is memory optimized for data sets containing mostly contiguous blocks of longs.
* This collection is NOT thread-safe.
*/
public class RangeSet extends AbstractSet<Long> implements Set<Long> {
/**
* Map that indexes each LongSegment with the smallest value in its range
*/
private final NavigableMap<Long, LongSegment> map = new TreeMap<Long, LongSegment>();
private int size = 0;
@Override
public int size() {
return size;
}
@Override
public boolean contains(Object o) {
Long value = ((Number) o).longValue();
Map.Entry<Long, LongSegment> entry = map.floorEntry(value);
return entry != null && entry.getValue().contains(value);
}
/**
* Iterator returns the longs in increasing order
*
* @return iterator
*/
@Override
public Iterator<Long> iterator() {
return new Iterator<Long>() {
// Get Segments in ascending order
private final Iterator<LongSegment> segmentIterator = map.values().iterator();
private Iterator<Long> longIterator =
segmentIterator.hasNext() ? segmentIterator.next().iterator() : null;
@Override
public boolean hasNext() {
return longIterator != null && longIterator.hasNext();
}
@Override
public Long next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
Long ret = longIterator.next();
if (!longIterator.hasNext()) {
longIterator = segmentIterator.hasNext() ? segmentIterator.next().iterator() : null;
}
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Iterator does not support remove");
}
};
}
@Override
public boolean add(Long aLong) {
// Find the largest LongSegment with a min value of less than or equal to aLong
Map.Entry<Long, LongSegment> lowerEntry = map.floorEntry(aLong);
// This value is already added
if (lowerEntry != null && lowerEntry.getValue().contains(aLong)) {
return false;
}
// Get the LongSegment with a min value directly after aLong
LongSegment upperSegment = map.get(aLong + 1);
// Determine possibly adjacencies with lower and upper bound LongSegments
boolean lowerAdjacent = lowerEntry != null && lowerEntry.getValue().getMax() + 1 == aLong;
boolean upperAdjacent = upperSegment != null && upperSegment.getMin() - 1 == aLong;
if (lowerAdjacent && upperAdjacent) {
// Overwrite the lower adjacent to encompass the whole range
map.put(
lowerEntry.getValue().getMin(),
new LongSegment(lowerEntry.getValue().getMin(), upperSegment.getMax())
);
// Remove the upper adjacent b/c now included in the merged LongSegment
map.remove(upperSegment.getMin());
} else if (lowerAdjacent) {
// Overwrite the lower adjacent max to include aLong
map.put(
lowerEntry.getValue().getMin(), new LongSegment(lowerEntry.getValue().getMin(), aLong)
);
} else if (upperAdjacent) {
// Insert new LongSegment starting aLong and encompassing upper adjacent
map.put(aLong, new LongSegment(aLong, upperSegment.getMax()));
// Remove the upper adjacent b/c now included in new LongSegment
map.remove(upperSegment.getMin());
} else {
// No adjacents, so just insert singular element
map.put(aLong, new LongSegment(aLong));
}
size++;
return true;
}
@Override
public boolean remove(Object o) {
Long value = ((Number) o).longValue();
Map.Entry<Long, LongSegment> entry = map.floorEntry(value);
// Entry does not exist
if (entry == null) {
return false;
}
// Entry does not contain the value
if (!entry.getValue().contains(value)) {
return false;
}
// Update lower segment if necessary
if (entry.getValue().getMin() < value) {
map.put(entry.getValue().getMin(), new LongSegment(entry.getValue().getMin(), value - 1));
} else {
map.remove(entry.getValue().getMin()); // Otherwise, remove existing segment/index
}
// Make an upper segment if necessary
if (entry.getValue().getMax() > value) {
map.put(value + 1, new LongSegment(value + 1, entry.getValue().getMax()));
}
size--;
return true;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean changed = false;
for (Object o : c) {
changed |= remove(o);
}
return changed;
}
@Override
public void clear() {
map.clear();
size = 0;
}
/**
* Represents a range of long values from min to max (inclusive)
*/
private static class LongSegment implements Iterable<Long> {
private final long min;
private final long max;
private LongSegment(long min, long max) {
this.min = min;
this.max = max;
if (max < min) {
throw new IllegalArgumentException();
}
}
private LongSegment(long value) {
this(value, value);
}
public long getMin() {
return min;
}
public long getMax() {
return max;
}
public boolean contains(long value) {
return value >= min && value <= max;
}
@Override
public Iterator<Long> iterator() {
return new Iterator<Long>() {
private long currentValue = min;
@Override
public boolean hasNext() {
return currentValue <= max;
}
@Override
public Long next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
long ret = currentValue;
currentValue++;
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Iterator does not support remove");
}
};
}
}
}