/*
* Primitive Collections for Java.
* Copyright (C) 2002, 2003 S�ren Bak
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package bak.pcj.set;
import bak.pcj.IntIterator;
import bak.pcj.IntCollection;
import bak.pcj.util.Exceptions;
import java.util.NoSuchElementException;
import java.io.Serializable;
/**
* This class represents bit array based sets of int values. When a
* bit in the underlying array is set, the value having the same
* number as the bit is contained in the array. This implies that
* bit sets cannot contain negative values.
*
* <p>Implementation of
* IntSortedSet is supported from PCJ 1.2. Prior to 1.2, only IntSet
* was implemented.
*
* <p>Note: There is no growth policy involved with bit sets. The number
* of bits to use depends on the value of the largest element and not
* the size of the set. While sizes are predictable (they grow), a
* new maximum element is generally not predictable making it
* meaningless to grow the array at a specific rate.
*
* @author Søren Bak
* @version 1.3 21-08-2003 19:54
* @since 1.0
*/
public class IntBitSet extends AbstractIntSet implements IntSortedSet, Cloneable, Serializable {
private static final int BITS_PER_LONG = 64;
private static final int BIT_MASK = 0x0000003F;
private static final int BIT_MASK_BITS = 6;
private static final int DEFAULT_CAPACITY = BITS_PER_LONG;
/**
* The array of bits backing up this set.
* @serial
*/
private long[] data;
/**
* The size of this set.
* @serial
*/
private int size;
/**
* Creates a new bit set with a specified maximum value.
*
* @param maximum
* the maximum value representable by the new bitset.
*
* @throws IllegalArgumentException
* if <tt>capacity</tt> is negative.
*/
public IntBitSet(int maximum) {
if (maximum < 0)
Exceptions.negativeArgument("maximum", String.valueOf(maximum));
data = new long[1+longIndex(maximum)];
size = 0;
}
/**
* Creates a new empty bit set with a capacity of 64.
*/
public IntBitSet() {
this((int)DEFAULT_CAPACITY);
}
/**
* Creates a new bit set with the same elements as the specified
* collection.
*
* @param c
* the collection whose elements to add to the new
* bit set.
*
* @throws NullPointerException
* if <tt>c</tt> is <tt>null</tt>.
*
* @throws IllegalArgumentException
* if any of the elements of the specified collection
* is negative.
*/
public IntBitSet(IntCollection c) {
this();
addAll(c);
}
/**
* Creates a new bit set with the same elements as the specified
* array.
*
* @param a
* the array whose elements to add to the new
* bit set.
*
* @throws NullPointerException
* if <tt>a</tt> is <tt>null</tt>.
*
* @throws IllegalArgumentException
* if any of the elements of the specified array
* is negative.
*
* @since 1.1
*/
public IntBitSet(int[] a) {
// Find max element n order to avoid repeated capacity increases
this(amax(a));
// Add all elements
for (int i = 0; i < a.length; i++)
add(a[i]);
}
private static int amax(int[] a) {
int max = (int)0;
for (int i = 0; i < a.length; i++)
if (a[i] > max) max = a[i];
return max;
}
// ---------------------------------------------------------------
// Bit management
// ---------------------------------------------------------------
private static int longIndex(int index)
{ return index >> BIT_MASK_BITS; }
private static int bitIndex(int index)
{ return index & BIT_MASK; }
private static long bit(int bitno)
{ return 1L << bitno; }
private static int largestBitIndexOf(long v) {
if (v == 0L)
throw new IllegalArgumentException("No elements left");
int bitIndex = BITS_PER_LONG-1;
long bit = 1L << bitIndex;
while ((v & bit) == 0L) {
bitIndex--;
bit >>= 1;
}
return bitIndex;
}
private static int smallestBitIndexOf(long v) {
if (v == 0L)
throw new IllegalArgumentException("No elements left");
int bitIndex = 0;
long bit = 1L;
while ((v & bit) == 0L) {
bitIndex++;
bit <<= 1;
}
return bitIndex;
}
private static int countBits(long v) {
int count = 0;
int bitIndex = 0;
long bit = 1L;
do {
if ((v & bit) != 0L)
count++;
bitIndex++;
bit <<= 1;
} while (bitIndex < BITS_PER_LONG);
return count;
}
private static long lowMask(int n) {
long v = 0L;
for (int i = 0; i < n; i++)
v = (v << 1) | 1L;
return v;
}
private static long highMask(int n) {
return ~lowMask(n);
}
/**
* Ensures that this bit set can contain a specified maximum
* element without increasing the capacity. If many elements are
* added, and the maximum element among those is known before
* they are added, this method may improve performance.
*
* @param maximum
* the maximum element that this set should be able
* to contain without increasing the capacity.
*
* @throws IllegalArgumentException
* if <tt>maximum</tt> is negative.
*/
public void ensureCapacity(int maximum) {
if (maximum < 0)
Exceptions.negativeArgument("maximum", String.valueOf(maximum));
int newcapacity = 1+longIndex(maximum);
if (data.length < newcapacity) {
long[] newdata = new long[newcapacity];
System.arraycopy(data, 0, newdata, 0, data.length);
data = newdata;
}
}
// ---------------------------------------------------------------
// Operations not supported by abstract implementation
// ---------------------------------------------------------------
/**
* @throws IllegalArgumentException
* if <tt>value</tt> is negative.
*/
public boolean add(int value) {
if (value < 0)
Exceptions.negativeArgument("value", String.valueOf(value));
int longIndex = longIndex(value);
if (data.length < 1+longIndex)
ensureCapacity(value);
long bit = bit(bitIndex(value));
boolean result = (data[longIndex] & bit) == 0;
if (result)
size++;
data[longIndex] |= bit;
return result;
}
public IntIterator iterator() {
if (size == 0)
return new IntIterator() {
public boolean hasNext()
{ return false; }
public int next()
{ Exceptions.endOfIterator(); throw new RuntimeException(); }
public void remove()
{ Exceptions.noElementToRemove(); }
};
return new IntIterator() {
int nextLongIndex = nextLongIndex(0);
int nextBitIndex = nextLongIndex < data.length ? nextBitIndex(nextLongIndex, 0) : 0;
int lastValue = -1;
int nextLongIndex(int index) {
while (index < data.length && data[index] == 0)
index++;
return index;
}
int nextBitIndex(int longIndex, int bitIndex) {
long v = data[longIndex];
long bit = 1L << bitIndex;
while (bitIndex < BITS_PER_LONG && (v & bit) == 0) {
bitIndex++;
bit <<= 1;
}
return bitIndex;
}
public boolean hasNext() {
return nextLongIndex < data.length;
}
public int next() {
if (!hasNext())
Exceptions.endOfIterator();
lastValue = (int)(nextLongIndex*BITS_PER_LONG + nextBitIndex);
// Advance pointers
nextBitIndex = nextBitIndex(nextLongIndex, nextBitIndex+1);
if (nextBitIndex == BITS_PER_LONG) {
nextLongIndex = nextLongIndex(nextLongIndex+1);
if (nextLongIndex < data.length)
nextBitIndex = nextBitIndex(nextLongIndex, 0);
}
return (int)lastValue;
}
public void remove() {
if (lastValue < 0)
Exceptions.noElementToRemove();
IntBitSet.this.remove((int)lastValue);
lastValue = -1;
}
};
}
/**
* Minimizes the memory used by this bit set. The underlying
* array is replaced by an array whose size corresponds to
* the maximum elements in this bit set. The method can be used to
* free up memory after many removals.
*/
public void trimToSize() {
// Find maximum element
int n = data.length-1;
while (n >= 0 && data[n] == 0L)
n--;
// Trim
if (n < data.length-1) {
long[] newdata = new long[1+n];
System.arraycopy(data, 0, newdata, 0, newdata.length);
data = newdata;
}
}
/**
* Returns a clone of this bit set.
*
* @return a clone of this bit set.
*
* @since 1.1
*/
public Object clone() {
try {
IntBitSet c = (IntBitSet)super.clone();
c.data = new long[data.length];
System.arraycopy(data, 0, c.data, 0, data.length);
return c;
} catch (CloneNotSupportedException e) {
Exceptions.cloning(); throw new RuntimeException();
}
}
// ---------------------------------------------------------------
// Operations overwritten for efficiency
// ---------------------------------------------------------------
public void clear() {
for (int i = 0; i < data.length; i++)
data[i] = 0;
size = 0;
}
public boolean contains(int value) {
if (value < 0)
return false;
int longIndex = longIndex(value);
if (longIndex >= data.length)
return false;
long bit = bit(bitIndex(value));
return (data[longIndex] & bit) != 0;
}
public boolean isEmpty()
{ return size == 0; }
public boolean remove(int value) {
if (value < 0)
return false;
int longIndex = longIndex(value);
if (longIndex >= data.length)
return false;
long bit = bit(bitIndex(value));
boolean result = (data[longIndex] & bit) != 0;
if (result)
size--;
data[longIndex] &= ~bit;
return result;
}
public int size()
{ return size; }
// ---------------------------------------------------------------
// Sorted set operations
// ---------------------------------------------------------------
private int firstFrom(int from) {
if (size == 0)
Exceptions.setNoFirst();
int longIndex = longIndex(from);
if (longIndex >= data.length)
Exceptions.setNoFirst();
long v = data[longIndex];
// Mask out all bits less than from
v &= highMask(bitIndex(from));
try {
for (;;) {
if (v != 0L) {
int bitIndex = smallestBitIndexOf(v);
return (int)(BITS_PER_LONG*longIndex + bitIndex);
}
v = data[++longIndex];
}
} catch (IndexOutOfBoundsException e) {
Exceptions.setNoFirst(); throw new RuntimeException();
}
}
/**
* @since 1.2
*/
public int first() {
return firstFrom((int)0);
}
private int lastFrom(int from) {
if (size == 0)
Exceptions.setNoLast();
int longIndex = Math.min(longIndex(from), data.length-1);
long v = data[longIndex];
// Mask out all bits greater than from
v &= lowMask(bitIndex(from)+1);
try {
for (;;) {
if (v != 0L) {
int bitIndex = largestBitIndexOf(v);
return (int)(BITS_PER_LONG*longIndex + bitIndex);
}
v = data[--longIndex];
}
} catch (IndexOutOfBoundsException e) {
Exceptions.setNoLast(); throw new RuntimeException();
}
}
/**
* @since 1.2
*/
public int last() {
if (size == 0)
Exceptions.setNoLast();
int longIndex = data.length-1;
// Find last non-zero long
while (data[longIndex] == 0)
longIndex--;
long v = data[longIndex];
int bitIndex = BITS_PER_LONG-1;
long bit = 1L << bitIndex;
while ((v & bit) == 0) {
bitIndex--;
bit >>= 1;
}
return (int)(BITS_PER_LONG*longIndex + bitIndex);
}
/**
* @since 1.2
*/
public IntSortedSet headSet(int to) {
return new SubSet(false, (int)0, true, to);
}
/**
* @since 1.2
*/
public IntSortedSet tailSet(int from) {
return new SubSet(true, from, false, (int)0);
}
/**
* @since 1.2
*/
public IntSortedSet subSet(int from, int to) {
return new SubSet(true, from, true, to);
}
private class SubSet extends AbstractIntSet implements IntSortedSet, java.io.Serializable {
private boolean hasLowerBound;
private boolean hasUpperBound;
private int lowerBound;
private int upperBound;
SubSet(boolean hasLowerBound, int lowerBound, boolean hasUpperBound, int upperBound) {
if (hasLowerBound) {
if (lowerBound < 0)
Exceptions.negativeArgument("lower bound", String.valueOf(lowerBound));
if (hasUpperBound)
if (upperBound < lowerBound)
Exceptions.invalidSetBounds(String.valueOf(lowerBound), String.valueOf(upperBound));
}
this.hasLowerBound = hasLowerBound;
this.lowerBound = lowerBound;
this.hasUpperBound = hasUpperBound;
this.upperBound = upperBound;
}
public boolean add(int v) {
if (!inSubRange(v))
Exceptions.valueNotInSubRange(String.valueOf(v));
return IntBitSet.this.add(v);
}
public boolean remove(int v) {
if (!inSubRange(v))
Exceptions.valueNotInSubRange(String.valueOf(v));
return IntBitSet.this.remove(v);
}
public boolean contains(int v) {
return inSubRange(v) && IntBitSet.this.contains(v);
}
class SubSetIterator implements IntIterator {
int longIndexLow;
int longIndexHigh;
long vLow;
long vHigh;
boolean isEmpty;
int nextLongIndex;
int nextBitIndex;
int lastValue;
SubSetIterator() {
lastValue = -1;
isEmpty = false;
try {
longIndexLow = longIndex(first());
} catch (NoSuchElementException e) {
isEmpty = true;
}
if (!isEmpty) {
longIndexHigh = longIndex(last());
if (longIndexLow == longIndexHigh) {
long v = data[longIndexLow];
// Mask out all bits less than the lower bound
if (hasLowerBound)
v &= highMask(bitIndex(lowerBound));
// Mask out all bits greater than or equal to the upper bound
if (hasUpperBound)
v &= lowMask(bitIndex(upperBound));
size = countBits(v);
vLow = vHigh = v;
} else {
// Mask out all bits less than the lower bound
vLow = data[longIndexLow];
if (hasLowerBound)
vLow &= highMask(bitIndex(lowerBound));
// Mask out all bits greater than or equal to the upper bound
vHigh = data[longIndexHigh];
if (hasUpperBound)
vHigh &= lowMask(bitIndex(upperBound));
}
nextLongIndex = longIndexLow;
nextBitIndex = smallestBitIndexOf(vLow);
}
}
long data(int longIndex) {
if (longIndex == longIndexLow)
return vLow;
if (longIndex == longIndexHigh)
return vHigh;
return data[longIndex];
}
int nextLongIndex(int index) {
while (index <= longIndexHigh && data(index) == 0)
index++;
return index;
}
int nextBitIndex(int longIndex, int bitIndex) {
long v = data(longIndex);
long bit = 1L << bitIndex;
while (bitIndex < BITS_PER_LONG && (v & bit) == 0) {
bitIndex++;
bit <<= 1;
}
return bitIndex;
}
public boolean hasNext() {
return (!isEmpty) && (nextLongIndex <= longIndexHigh);
}
public int next() {
if (!hasNext())
Exceptions.endOfIterator();
lastValue = (int)(nextLongIndex*BITS_PER_LONG + nextBitIndex);
// Advance pointers
nextBitIndex = nextBitIndex(nextLongIndex, nextBitIndex+1);
if (nextBitIndex == BITS_PER_LONG) {
nextLongIndex = nextLongIndex(nextLongIndex+1);
if (nextLongIndex < data.length)
nextBitIndex = nextBitIndex(nextLongIndex, 0);
}
return (int)lastValue;
}
public void remove() {
if (lastValue < 0)
Exceptions.noElementToRemove();
IntBitSet.this.remove((int)lastValue);
lastValue = -1;
}
}
public IntIterator iterator() {
return new SubSetIterator();
}
public int size() {
if (IntBitSet.this.size() == 0)
return 0;
int size;
int longIndexLow;
try {
longIndexLow = longIndex(first());
} catch (NoSuchElementException e) {
return 0;
}
int longIndexHigh = longIndex(last());
if (longIndexLow == longIndexHigh) {
long v = data[longIndexLow];
// Mask out all bits less than the lower bound
if (hasLowerBound)
v &= highMask(bitIndex(lowerBound));
// Mask out all bits greater than or equal to the upper bound
if (hasUpperBound)
v &= lowMask(bitIndex(upperBound));
size = countBits(v);
} else {
// Mask out all bits less than the lower bound
long vLow = data[longIndexLow];
if (hasLowerBound)
vLow &= highMask(bitIndex(lowerBound));
// Mask out all bits greater than or equal to the upper bound
long vHigh = data[longIndexHigh];
if (hasUpperBound)
vHigh &= lowMask(bitIndex(upperBound));
size = countBits(vLow) + countBits(vHigh);
for (int i = longIndexLow+1; i < longIndexHigh; i++)
size += countBits(data[i]);
}
return size;
}
public int first() {
int first = firstFrom(hasLowerBound ? lowerBound : 0);
if (hasUpperBound && first >= upperBound)
Exceptions.setNoFirst();
return first;
}
public int last() {
int last = lastFrom(hasUpperBound ? (int)(upperBound-1) : IntBitSet.this.last());
if (hasLowerBound && last < lowerBound)
Exceptions.setNoLast();
return last;
}
public IntSortedSet headSet(int to) {
if (!inSubRange(to))
Exceptions.invalidUpperBound(String.valueOf(to));
return new SubSet(hasLowerBound, lowerBound, true, to);
}
public IntSortedSet tailSet(int from) {
if (!inSubRange(from))
Exceptions.invalidLowerBound(String.valueOf(from));
return new SubSet(true, from, hasUpperBound, upperBound);
}
public IntSortedSet subSet(int from, int to) {
if (!inSubRange(from))
Exceptions.invalidLowerBound(String.valueOf(from));
if (!inSubRange(to))
Exceptions.invalidUpperBound(String.valueOf(to));
return new SubSet(true, from, true, to);
}
private boolean inSubRange(int v) {
if (hasLowerBound && v < lowerBound)
return false;
if (hasUpperBound && v >= upperBound)
return false;
return true;
}
}
}