/*
* Primitive Collections for Java.
* Copyright (C) 2002 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.ShortIterator;
import bak.pcj.ShortCollection;
import bak.pcj.util.Exceptions;
import java.util.ArrayList;
import java.util.NoSuchElementException;
import java.io.Serializable;
/**
* This class represents range based sets of short values.
* The implementation is optimized for cases where most set elements
* fall into ranges of consecutive short values.
*
* <p>Implementation of
* ShortSortedSet is supported from PCJ 1.2. Prior to 1.2, only ShortSet
* was implemented.
*
* @see ShortRange
*
* @author Søren Bak
* @version 1.3 20-08-2003 22:24
* @since 1.0
*/
public class ShortRangeSet extends AbstractShortSet implements ShortSortedSet, Cloneable, Serializable {
/**
* The ranges of this set. Must always be sorted and normalized (non-adjacent and non-overlapping).
* @serial
*/
private ArrayList ranges;
/**
* The size of this set.
* @serial
*/
private int size;
/**
* Creates a new empty range set.
*/
public ShortRangeSet() {
ranges = new ArrayList();
size = 0;
}
/**
* Creates a new empty range set containing specified values.
*
* @param a
* the values that the new set should contain.
*
* @throws NullPointerException
* if <tt>a</tt> is <tt>null</tt>.
*/
public ShortRangeSet(short[] a) {
this();
addAll(a);
}
/**
* Creates a new range set with the same elements as a specified
* collection.
*
* @param c
* the collection whose elements to add to the new
* set.
*
* @throws NullPointerException
* if <tt>c</tt> is <tt>null</tt>.
*/
public ShortRangeSet(ShortCollection c) {
this();
addAll(c);
}
// ---------------------------------------------------------------
// Range management
// ---------------------------------------------------------------
/**
* Returns a specified range.
*
* @param index
* the index of the range to return.
*
* @throws IndexOutOfBoundsException
* if <tt>index/tt> does not denote a valid range
* number.
*/
private ShortRange range(int index) {
return (ShortRange)ranges.get(index);
}
/**
* Returns the range of a specified value.
*
* @param v
* the value to search for.
*
* @return the range containing the specified value; returns
* <tt>null</tt> if no range contains the specified
* value.
*/
private ShortRange getRangeOf(short v) {
int index = getRangeIndexOf(v);
return index >= 0 ? range(index) : null;
}
/**
* Returns the range index of a specified value.
*
* @param v
* the value to search for.
*
* @return the index of the range containing the specified
* value; returns <tt>(-(<i>insertion point</i>) - 1)</tt>
* if no range contains the specified value.
*/
private int getRangeIndexOf(short v) {
if (size == 0)
return -1;
// Binary search
ShortRange r;
int lo = 0;
int hi = ranges.size()-1;
int mid;
while (lo <= hi) {
mid = (lo+hi)/2;
r = (ShortRange)ranges.get(mid);
if (r.contains(v))
return mid;
if (v < r.first()) {
hi = mid-1;
} else { // v > r.last()
lo = mid+1;
}
}
return -(lo+1);
}
/**
* Inserts a range at the sorted position in the ranges.
*
* @param range
* the range to insert.
*
* @return the insertion index; returns <tt>-1</tt> if an
* equal range existed in the ranges.
*/
private int insertRange(ShortRange range) {
// Binary search
ShortRange r;
int lo = 0;
int hi = ranges.size()-1;
int mid;
while (lo <= hi) {
mid = (lo+hi)/2;
r = range(mid);
int compare = range.compareTo(r);
if (compare == 0)
return -1;
if (compare < 0) {
hi = mid-1;
} else { // compare > 0
lo = mid+1;
}
}
ranges.add(lo, range);
return lo;
}
/**
* Normalizes the ranges after the insertion of a new range and
* recalculates the size of this set. The range list must be
* sorted when this method is invoked.
*
* @param index
* the index at which to start the normalization.
* Usually the index before a new range was inserted.
*/
private void normalize(int index) {
while (index < ranges.size()-1) {
ShortRange r1 = range(index);
ShortRange r2 = range(index+1);
ShortRange r3 = r1.tryMergeWith(r2);
if (r3 == null)
break;
ranges.set(index, r3);
ranges.remove(index+1);
size -= r1.intersectionLength(r2);
}
}
/**
* Normalizes all ranges and recalculates the size of this set.
* The method is usually called when the whole range list has
* changed. The range list must be sorted when this method is
* invoked.
*/
private void normalize() {
int index = 0;
size = 0;
ShortRange r1, r2, r3;
while (index < ranges.size()-1) {
r1 = range(index);
r2 = range(index+1);
r3 = r1.tryMergeWith(r2);
if (r3 != null) {
ranges.set(index, r3);
ranges.remove(index+1);
} else {
size += r1.length();
index++;
}
}
r3 = range(ranges.size()-1);
size += r3.length();
}
// ---------------------------------------------------------------
// Operations not supported by abstract implementation
// ---------------------------------------------------------------
public boolean add(short v) {
int index = getRangeIndexOf(v);
if (index >= 0)
return false;
int insertionIndex = -index-1;
ranges.add(insertionIndex, new ShortRange(v, v));
if (insertionIndex > 0)
insertionIndex--;
size++;
normalize(insertionIndex);
return true;
}
public ShortIterator iterator() {
return new ShortIterator() {
int nextIndex = 0;
int lastIndex = -1;
int currRange = 0;
int currOffset = 0;
short lastValue;
public boolean hasNext() {
return nextIndex < size;
}
public short next() {
if (nextIndex >= size)
Exceptions.endOfIterator();
lastIndex = nextIndex;
lastValue = curr();
nextIndex++;
if (nextIndex < size) {
if (currOffset == range(currRange).length()-1) {
currRange++;
currOffset = 0;
} else {
currOffset++;
}
}
return lastValue;
}
public void remove() {
if (lastIndex == -1)
Exceptions.noElementToRemove();
ShortRangeSet.this.remove(lastValue);
nextIndex--;
if (nextIndex < size)
recalc();
lastIndex = -1;
}
private short curr() {
return (short)(range(currRange).first() + currOffset);
}
private void recalc() {
currRange = 0;
currOffset = nextIndex;
for (;;) {
int rs = range(currRange).length();
if (currOffset < rs)
break;
currOffset -= rs;
currRange++;
}
}
};
}
/**
* @since 1.2
*/
public short first() {
if (size == 0)
Exceptions.setNoFirst();
return range(0).first();
}
private short firstFrom(short v) {
int index = getRangeIndexOf(v);
if (index >= 0)
return v;
// Get first range after calculated insertion point.
// index is now (-(insertion point)-1), so the insertion point
// is -index-1
index = -index - 1;
if (index >= ranges.size())
Exceptions.setNoFirst();
return range(index).first();
}
/**
* @since 1.2
*/
public short last() {
if (size == 0)
Exceptions.setNoLast();
return range(ranges.size()-1).last();
}
private short lastFrom(short v) {
int index = getRangeIndexOf(v);
if (index >= 0)
return v;
// Get first range before calculated insertion point.
// index is now (-(insertion point)-1), so the insertion point
// is -index-1
index = -index - 1;
index--;
if (index < 0 || index >= ranges.size())
Exceptions.setNoLast();
return range(index).last();
}
/**
* @since 1.2
*/
public ShortSortedSet headSet(short to) {
return new SubSet(false, (short)0, true, to);
}
/**
* @since 1.2
*/
public ShortSortedSet tailSet(short from) {
return new SubSet(true, from, false, (short)0);
}
/**
* @since 1.2
*/
public ShortSortedSet subSet(short from, short to) {
return new SubSet(true, from, true, to);
}
private class SubSet extends AbstractShortSet implements ShortSortedSet, java.io.Serializable {
private boolean hasLowerBound;
private boolean hasUpperBound;
private short lowerBound;
private short upperBound;
SubSet(boolean hasLowerBound, short lowerBound, boolean hasUpperBound, short 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(short v) {
if (!inSubRange(v))
Exceptions.valueNotInSubRange(String.valueOf(v));
return ShortRangeSet.this.add(v);
}
public boolean remove(short v) {
if (!inSubRange(v))
Exceptions.valueNotInSubRange(String.valueOf(v));
return ShortRangeSet.this.remove(v);
}
public boolean contains(short v) {
return inSubRange(v) && ShortRangeSet.this.contains(v);
}
class EmptySubSetIterator implements ShortIterator {
public boolean hasNext()
{ return false; }
public short next()
{ Exceptions.endOfIterator(); throw new RuntimeException(); }
public void remove()
{ Exceptions.noElementToRemove(); }
}
class SimpleSubSetIterator implements ShortIterator {
int nextIndex;
int size;
int lastIndex;
short lastValue;
short from;
short to;
SimpleSubSetIterator(short from, short to) {
size = (int)(to-from+1);
nextIndex = 0;
lastIndex = -1;
this.from = from;
this.to = to;
}
public boolean hasNext() {
return nextIndex < size;
}
public short next() {
if (!hasNext())
Exceptions.endOfIterator();
lastValue = (short)(from + nextIndex);
lastIndex = nextIndex;
nextIndex++;
return lastValue;
}
public void remove() {
if (lastIndex == -1)
Exceptions.noElementToRemove();
ShortRangeSet.this.remove(lastValue);
lastIndex = -1;
}
}
class NonEmptySubSetIterator implements ShortIterator {
short first;
short last;
int rangeIndexLow;
int rangeIndexHigh;
ShortRange rangeLow;
ShortRange rangeHigh;
short previousValue;
ShortRange currRange;
int currRangeIndex;
int currOffset;
boolean valueAvailable;
int nextIndex;
NonEmptySubSetIterator(short first, short last, int rangeIndexLow, int rangeIndexHigh) {
if (rangeIndexLow == rangeIndexHigh)
throw new RuntimeException("Internal error");
this.first = first;
this.last = last;
this.rangeIndexLow = rangeIndexLow;
this.rangeIndexHigh = rangeIndexHigh;
rangeLow = new ShortRange(first, range(rangeIndexLow).last());
rangeHigh = new ShortRange(range(rangeIndexHigh).first(), last);
currRangeIndex = rangeIndexLow;
currRange = rangeLow;
currOffset = 0;
previousValue = first;
valueAvailable = false;
nextIndex = 0;
}
private ShortRange getRange(int rangeIndex) {
if (rangeIndex == rangeIndexLow)
return rangeLow;
if (rangeIndex == rangeIndexHigh)
return rangeHigh;
return range(rangeIndex);
}
private void recalc() {
first = first();
last = last();
rangeIndexLow = getRangeIndexOf(first);
rangeIndexHigh = getRangeIndexOf(last);
if (rangeIndexLow == rangeIndexHigh)
rangeLow = rangeHigh = new ShortRange(first, last);
else {
rangeLow = new ShortRange(first, range(rangeIndexLow).last());
rangeHigh = new ShortRange(range(rangeIndexHigh).first(), last);
}
currOffset = nextIndex;
currRangeIndex = rangeIndexLow;
currRange = rangeLow;
for (;;) {
int rs = currRange.length();
if (currOffset < rs)
break;
currOffset -= rs;
currRange = getRange(++currRangeIndex);
}
}
public boolean hasNext() {
return previousValue < last;
}
public short next() {
if (!hasNext())
Exceptions.endOfIterator();
previousValue = (short)(currRange.first() + currOffset++);
if (currOffset == currRange.length() && previousValue < last) {
currOffset = 0;
currRange = getRange(++currRangeIndex);
}
nextIndex++;
valueAvailable = true;
return previousValue;
}
public void remove() {
if (!valueAvailable)
Exceptions.noElementToRemove();
ShortRangeSet.this.remove(previousValue);
nextIndex--;
recalc();
valueAvailable = false;
}
}
public ShortIterator iterator() {
short first;
short last;
int rangeIndexLow;
int rangeIndexHigh;
try {
first = first();
} catch (NoSuchElementException e) {
return new EmptySubSetIterator();
}
last = last();
rangeIndexLow = getRangeIndexOf(first);
rangeIndexHigh = getRangeIndexOf(last);
if (rangeIndexLow == rangeIndexHigh)
return new SimpleSubSetIterator(first, last);
return new NonEmptySubSetIterator(first, last, rangeIndexLow, rangeIndexHigh);
}
public int size() {
if (ShortRangeSet.this.size() == 0)
return 0;
int size;
short first;
int rangeIndexLow;
try {
first = first();
rangeIndexLow = getRangeIndexOf(first);
} catch (NoSuchElementException e) {
return 0;
}
short last = last();
int rangeIndexHigh = getRangeIndexOf(last);
if (rangeIndexLow == rangeIndexHigh) {
size = (int)(last-first+1);
} else {
ShortRange rangeLow = range(rangeIndexLow);
ShortRange rangeHigh = range(rangeIndexHigh);
int sizeLow = (int)(rangeLow.last()-first+1);
int sizeHigh = (int)(last-rangeHigh.first()+1);
size = sizeLow + sizeHigh;
for (int i = rangeIndexLow+1; i < rangeIndexHigh; i++)
size += range(i).length();
}
return size;
}
public short first() {
short first = firstFrom(hasLowerBound ? lowerBound : 0);
if (hasUpperBound && first >= upperBound)
Exceptions.setNoFirst();
return first;
}
public short last() {
short last = lastFrom(hasUpperBound ? (short)(upperBound-1) : ShortRangeSet.this.last());
if (hasLowerBound && last < lowerBound)
Exceptions.setNoLast();
return last;
}
public ShortSortedSet headSet(short to) {
if (!inSubRange(to))
Exceptions.invalidUpperBound(String.valueOf(to));
return new SubSet(hasLowerBound, lowerBound, true, to);
}
public ShortSortedSet tailSet(short from) {
if (!inSubRange(from))
Exceptions.invalidLowerBound(String.valueOf(from));
return new SubSet(true, from, hasUpperBound, upperBound);
}
public ShortSortedSet subSet(short from, short 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(short v) {
if (hasLowerBound && v < lowerBound)
return false;
if (hasUpperBound && v >= upperBound)
return false;
return true;
}
}
public String toString() {
StringBuffer s = new StringBuffer();
s.append('[');
for (int i = 0, rsize = ranges.size(); i < rsize; i++) {
if (i > 0)
s.append(',');
s.append(range(i));
}
s.append(']');
return s.toString();
}
public void trimToSize() {
//ranges.trimToSize();
}
/**
* Returns a clone of this range set.
*
* @return a clone of this range set.
*
* @since 1.1
*/
public Object clone() {
try {
ShortRangeSet c = (ShortRangeSet)super.clone();
c.ranges = (ArrayList)ranges.clone();
return c;
} catch (CloneNotSupportedException e) {
Exceptions.cloning(); throw new RuntimeException();
}
}
// ---------------------------------------------------------------
// Operations overwritten for efficiency
// ---------------------------------------------------------------
public void clear() {
ranges.clear();
size = 0;
}
public boolean contains(short v)
{ return getRangeIndexOf(v) >= 0; }
public int hashCode() {
int h = 0;
for (int i = 0, index = 0, rsize = ranges.size(); i < rsize; i++) {
ShortRange r = range(i);
for (short c = r.first(), last = r.last(); c <= last; c++)
h += c;
}
return h;
}
public boolean isEmpty()
{ return size == 0; }
public int size()
{ return size; }
public boolean remove(short v) {
int index = getRangeIndexOf(v);
if (index < 0)
return false;
// Treat end points special since we can avoid splitting a range
ShortRange r = range(index);
if (v == r.first()) {
if (r.length() == 1)
ranges.remove(index);
else
ranges.set(index, new ShortRange((short)(r.first()+1), r.last()));
} else if (v == r.last()) {
// r.length() > 1
ranges.set(index, new ShortRange(r.first(), (short)(r.last()-1)));
} else {
// Split the range
ShortRange r1 = new ShortRange(r.first(), (short)(v-1));
ShortRange r2 = new ShortRange((short)(v+1), r.last());
ranges.set(index, r1);
ranges.add(index+1, r2);
}
size--;
return true;
}
public short[] toArray(short[] a) {
if (a == null || a.length < size)
a = new short[size];
for (int i = 0, index = 0, rsize = ranges.size(); i < rsize; i++) {
ShortRange r = range(i);
for (short c = r.first(), last = r.last(); c <= last; c++)
a[index++] = c;
}
return a;
}
// ---------------------------------------------------------------
// Extra operations
// ---------------------------------------------------------------
/**
* Indicates whether all elements of a specified
* range is contained in this set.
*
* @param range
* the range whose elements to test for
* containment.
*
* @return <tt>true</tt> if all the elements of <tt>range</tt>
* are contained in this collection; returns
* <tt>false</tt> otherwise.
*
* @throws NullPointerException
* if <tt>range</tt> is <tt>null</tt>.
*
* @see #containsAll(ShortCollection)
*/
public boolean containsAll(ShortRange range) {
/*
In order for the set to contain the whole range
the two range ends must be represented by the same
range in the range list.
*/
ShortRange r = getRangeOf(range.first());
return r != null ? r.contains(range.last()) : false;
}
/**
* Adds all the elements of a specified range set to
* this set.
*
* @param c
* the set whose elements to add to this
* set.
*
* @return <tt>true</tt> if this set was modified
* as a result of adding the elements of <tt>c</tt>;
* returns <tt>false</tt> otherwise.
*
* @throws NullPointerException
* if <tt>c</tt> is <tt>null</tt>.
*
* @see #add(short)
* @see #addAll(ShortRange)
* @see #addAll(ShortCollection)
* @see #addAll(short, short)
* @see #addAll(short[])
*/
public boolean addAll(ShortRangeSet c) {
int oldSize = size;
for (int i = 0, rsize = c.ranges.size(); i < rsize; i++)
addAll(c.range(i));
return size != oldSize;
}
/**
* Adds a specified range to this set.
*
* @param range
* the range to add to this set.
*
* @return <tt>true</tt> if this set was modified
* as a result of adding the elements of
* <tt>range</tt>; returns <tt>false</tt> otherwise.
*
* @throws NullPointerException
* if <tt>range</tt> is <tt>null</tt>.
*
* @see #add(short)
* @see #addAll(ShortRangeSet)
* @see #addAll(ShortCollection)
* @see #addAll(short, short)
* @see #addAll(short[])
*/
public boolean addAll(ShortRange range) {
int oldSize = size;
int index = insertRange(range);
if (index != -1) {
int nindex = index;
if (nindex > 0)
nindex--;
size += range.length();
normalize(nindex);
}
return size != oldSize;
}
/**
* Adds a specified range to this set.
*
* @param first
* the first value of the range to add to this set.
*
* @param last
* the last value of the range to add to this set.
*
* @return <tt>true</tt> if this set was modified
* as a result of adding the values <tt>first</tt>
* to <tt>last</tt>; returns <tt>false</tt>
* otherwise.
*
* @throws IllegalArgumentException
* if <tt>first > last</tt>.
*/
public boolean addAll(short first, short last) {
return addAll(new ShortRange(first, last));
}
/**
* Adds an array of short values to this set.
*
* @param a
* the array of short values to add to this set.
*
* @throws NullPointerException
* if <tt>a</tt> is <tt>null</tt>.
*
* @see #add(short)
* @see #addAll(ShortRange)
* @see #addAll(ShortRangeSet)
* @see #addAll(ShortCollection)
* @see #addAll(short, short)
*/
public boolean addAll(short[] a) {
if (a.length == 0)
return false;
// Sort a
/*
We can decide if the array is sorted in at most n steps
(n being the length of chars).
If it is not sorted, it is probably much less than n steps,
and if it is sorted, we can skip the sorting operation
and cloning of chars (thus effectively having sorted in
linear time).
*/
int oldSize = size;
short[] sa;
if (!isSorted(a)) {
sa = (short[])a.clone();
java.util.Arrays.sort(sa);
} else {
sa = a;
}
// Add ranges of a to range list
int index = 0;
short c0, c1;
while (index < sa.length) {
c0 = sa[index];
index = range(sa, index);
c1 = sa[index];
ranges.add(new ShortRange(c0, c1));
index++;
}
// Sort and normalize range list
/*
Is it better to sort and normalize once instead
of inserting sorted and performing normalization at each step?
*/
java.util.Collections.sort(ranges);
normalize();
return size != oldSize;
}
/**
* Finds a range in an ordered array which may
* contain duplicates.
*
* @param a
* the array of values to search.
*
* @param index
* the index from which to start the search.
*
* @return the index of the last value in the found
* range.
*/
private int range(short[] a, int index) {
short c0 = a[index++];
// Skip duplicates
while (index < a.length && a[index] == c0)
index++;
// While in sequence
while (index < a.length && a[index] == (short)(c0+1)) {
c0 = a[index++];
// Skip duplicates
while (index < a.length && a[index] == c0)
index++;
}
return index-1;
}
/**
* Indicates whether the specified array is sorted in
* ascending order.
*
* @param a
* the array to examine.
*
* @return <tt>true</tt> if <tt>s</tt> is sorted; returns
* <tt>false</tt> otherwise.
*
* @throws NullPointerException
* if <tt>a</tt> is <tt>null</tt>.
*/
private boolean isSorted(short[] a) {
for (int i = 1; i < a.length; i++)
if (a[i] < a[i-1])
return false;
return true;
}
/**
* Returns the ranges of this set. None of the ranges returned
* will overlap or be adjacent.
*
* @return the ranges of this set. The returned array is
* a fresh copy that can be modified without
* modifying this set.
*/
public ShortRange[] ranges() {
ShortRange[] a = new ShortRange[ranges.size()];
ranges.toArray(a);
return a;
}
}