package org.limewire.collection; import java.util.AbstractSet; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Set; import java.util.SortedSet; /** * Represents a set of distinct integers. * Like {@link Set}, <code>SparseIntSet</code> is <b>not synchronized</b>. * <p> * The integers in this set are sorted in ascending order. It would be nice for it * to implement the SortedSet interface eventually. * <p> * Optimized to have compact representation when the set is "sparse". (For "dense" sets * you should use IntSet.) Integers are stored as primitives, so you're guaranteed 4*N bytes * of memory used after calling the compact() method. * <p> * All retrieval and insertion operations run in O(log n) time, where n is the size of the set. * <p> * This class is not thread-safe. */ public class SparseIntSet extends AbstractSet<Integer> { private int[] list = new int[0]; private int size; private int modCount; /** * Creates an empty set with capacity = 8. * (similar to ArrayList) */ public SparseIntSet() { this(8); } /** * Creates an empty set with the provided initial capacity. * @param initialCapacity the initial capacity desired. */ public SparseIntSet(int initialCapacity) { ensureCapacity(initialCapacity); } /** * Creates a set containing all the elements of the provided * collection. */ public SparseIntSet(Collection<? extends Integer> c) { addAll(c); } /** * Compacts this set to occupy 4*size() bytes of memory. */ public void compact() { int oldCapacity = list.length; if (size < oldCapacity) { int [] copy = new int[size]; System.arraycopy(list,0,copy,0,size); list = copy; } } /** * @return the actual memory used, in bytes. */ public int getActualMemoryUsed() { return list.length * 4; } /** * @return the next element that is larger than the provided element */ public int nextSetBit(int fromIndex) { int position = binarySearch(fromIndex); if (position < 0) position = -(position + 1); if (position == size) return -1; return list[position]; } @Override public boolean add(Integer i) { int point = binarySearch(i); if (point >= 0) return false; point = -(point + 1); ensureCapacity(size + 1); size++; System.arraycopy(list,point,list,point+1,size-point-1); list[point] = i; modCount++; return true; } @Override public boolean addAll(Collection<? extends Integer> c) { if (c.isEmpty()) return false; // if we know the other collection is sorted, we can save a lot of time if (c instanceof SparseIntSet || c instanceof SortedSet) { if (isEmpty()) { fillFromSorted(c); return true; } int [] newList = new int[size() + c.size()]; Iterator<Integer> us = iterator(); Iterator<? extends Integer> them = c.iterator(); int index = 0; boolean modified = false; // slightly tweaked mergesort int biggest = Integer.MIN_VALUE; boolean lastUs = true; boolean lastThem = true; while (us.hasNext() || them.hasNext()) { int a = biggest; int b = biggest; if (lastUs && us.hasNext()) a = us.next(); if (lastThem && them.hasNext()) b = them.next(); biggest = Math.max(a, b); // this is where we differ from merge sort since this is a Set if (index > 0 && newList[index - 1] == Math.min(a,b)) continue; if (a < b) { newList[index] = a; lastUs = true; lastThem = false; } else if ( a > b) { modified = true; newList[index] = b; lastUs = false; lastThem = true; } else { newList[index] = a; lastUs = true; lastThem = true; } index++; } if (newList[index - 1] != biggest) newList[index++] = biggest; list = newList; size = index; compact(); return modified; } else { // use regular addAll ensureCapacity(size() + c.size()); boolean ret = super.addAll(c); compact(); return ret; } } private void fillFromSorted(Collection<? extends Integer> c) { list = new int[c.size()]; for (int i : c) list[++size - 1] = i; } private void ensureCapacity(int minCapacity) { int oldCapacity = list.length; if (minCapacity > oldCapacity) { int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: int [] copy = new int[newCapacity]; System.arraycopy(list,0,copy,0,size); list = copy; } } private int binarySearch(int key) { int low = 0; int high = size - 1; while (low <= high) { int mid = (low + high) >> 1; int midVal = list[mid]; if (midVal < key) low = mid + 1; else if (midVal > key) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. } @Override public boolean remove(Object o) { if (! (o instanceof Integer)) return false; int i = (Integer)o; int point = binarySearch(i); if (point < 0) return false; int numMoved = size - point - 1; if (numMoved > 0) System.arraycopy(list, point+1, list, point, numMoved); size--; modCount++; return true; } @Override public boolean contains(Object o) { if (! (o instanceof Integer)) return false; int i = (Integer) o; int point = binarySearch(i); return point >= 0; } @Override public Iterator<Integer> iterator() { return new ArrayIterator(); } @Override public int size() { return size; } @SuppressWarnings({"SuspiciousMethodCalls"}) @Override public boolean retainAll(Collection<?> o) { SparseIntSet toRemove = new SparseIntSet(); for (int contained : this) { if (! o.contains(contained)) toRemove.add(contained); } return removeAll(toRemove); } private class ArrayIterator extends UnmodifiableIterator<Integer> { private int index; private final int mod = modCount; private void checkModification() { if (modCount != mod) throw new ConcurrentModificationException(); } public boolean hasNext() { return index < size; } public Integer next() { checkModification(); if (!hasNext()) throw new NoSuchElementException(); return list[index++]; } } }