Added a HashIntSet class that uses hashing with linear probing to get most of
the memory benefits of ArrayIntSet and better performance than a HashSet<Integer> (presumably because of less boxing and better spatial locality). The downside is that you need to pick a sentinel value that can't be stored in the set, but that's not a problem in any application for which I've ever used an IntSet. In some (admittedly simplistic) Google Caliper testing, HashIntSet was 22% faster than ArrayIntSet and 73% faster than HashSet for arrays of size N=10, 100% faster than ArrayIntSet and 69% faster than HashSet for N=100, and 357% faster than ArrayIntSet and 74% faster than HashSet for N=1000. git-svn-id: https://samskivert.googlecode.com/svn/trunk@2801 6335cc39-0255-0410-8fd6-9bcaacd3b74c
This commit is contained in:
@@ -37,6 +37,42 @@ import java.util.Iterator;
|
|||||||
public abstract class AbstractIntSet extends AbstractSet<Integer>
|
public abstract class AbstractIntSet extends AbstractSet<Integer>
|
||||||
implements IntSet
|
implements IntSet
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Add all of the values in the supplied array to the set.
|
||||||
|
*
|
||||||
|
* @param values elements to be added to this set.
|
||||||
|
*
|
||||||
|
* @return <tt>true</tt> if this set did not already contain all of the specified elements.
|
||||||
|
*/
|
||||||
|
public boolean add (int[] values)
|
||||||
|
{
|
||||||
|
boolean modified = false;
|
||||||
|
int vlength = values.length;
|
||||||
|
for (int i = 0; i < vlength; i++) {
|
||||||
|
modified = (add(values[i]) || modified);
|
||||||
|
}
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes all values in the supplied array from the set. Any values that are in the array but
|
||||||
|
* not in the set are simply ignored.
|
||||||
|
*
|
||||||
|
* @param values elements to be removed from the set.
|
||||||
|
*
|
||||||
|
* @return <tt>true</tt> if this set contained any of the specified elements (which will have
|
||||||
|
* been removed).
|
||||||
|
*/
|
||||||
|
public boolean remove (int[] values)
|
||||||
|
{
|
||||||
|
boolean modified = false;
|
||||||
|
int vcount = values.length;
|
||||||
|
for (int i = 0; i < vcount; i++) {
|
||||||
|
modified = (remove(values[i]) || modified);
|
||||||
|
}
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -87,42 +87,6 @@ public class ArrayIntSet extends AbstractIntSet
|
|||||||
this(DEFAULT_CAPACITY);
|
this(DEFAULT_CAPACITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Add all of the values in the supplied array to the set.
|
|
||||||
*
|
|
||||||
* @param values elements to be added to this set.
|
|
||||||
*
|
|
||||||
* @return <tt>true</tt> if this set did not already contain all of the specified elements.
|
|
||||||
*/
|
|
||||||
public boolean add (int[] values)
|
|
||||||
{
|
|
||||||
boolean modified = false;
|
|
||||||
int vlength = values.length;
|
|
||||||
for (int i = 0; i < vlength; i++) {
|
|
||||||
modified = (add(values[i]) || modified);
|
|
||||||
}
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all values in the supplied array from the set. Any values that are in the array but
|
|
||||||
* not in the set are simply ignored.
|
|
||||||
*
|
|
||||||
* @param values elements to be removed from the set.
|
|
||||||
*
|
|
||||||
* @return <tt>true</tt> if this set contained any of the specified elements (which will have
|
|
||||||
* been removed).
|
|
||||||
*/
|
|
||||||
public boolean remove (int[] values)
|
|
||||||
{
|
|
||||||
boolean modified = false;
|
|
||||||
int vcount = values.length;
|
|
||||||
for (int i = 0; i < vcount; i++) {
|
|
||||||
modified = (remove(values[i]) || modified);
|
|
||||||
}
|
|
||||||
return modified;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the element at the specified index. Note that the elements in the set are unordered
|
* Returns the element at the specified index. Note that the elements in the set are unordered
|
||||||
* and could change order after insertion or removal. This method is useful only for accessing
|
* and could change order after insertion or removal. This method is useful only for accessing
|
||||||
|
|||||||
@@ -0,0 +1,450 @@
|
|||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
//
|
||||||
|
// samskivert library - useful routines for java programs
|
||||||
|
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||||
|
//
|
||||||
|
// 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 com.samskivert.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.ObjectInputStream;
|
||||||
|
import java.io.ObjectOutputStream;
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.ConcurrentModificationException;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A set of integers that uses hashing with linear probing to provide most of the memory usage
|
||||||
|
* and garbage creation benefits of {@link ArrayIntSet} and the performance benefits of
|
||||||
|
* a {@link java.util.HashSet} of {@link Integer}s (and then some, because of better spatial
|
||||||
|
* locality). The downside is that it requires a sentinel value ({@link Integer#MIN_VALUE} by
|
||||||
|
* default) that cannot be stored in the set because it is used internally to represent unused
|
||||||
|
* locations.
|
||||||
|
*/
|
||||||
|
public class HashIntSet extends AbstractIntSet
|
||||||
|
implements Cloneable, Serializable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Construct a HashIntSet with the specified starting values.
|
||||||
|
*/
|
||||||
|
public HashIntSet (int[] values)
|
||||||
|
{
|
||||||
|
this(values.length);
|
||||||
|
add(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a HashIntSet with the specified starting values.
|
||||||
|
*
|
||||||
|
* @throws NullPointerException if the collection contains any null values.
|
||||||
|
*/
|
||||||
|
public HashIntSet (Collection<Integer> values)
|
||||||
|
{
|
||||||
|
this(values.size());
|
||||||
|
addAll(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new set with the default capacity.
|
||||||
|
*/
|
||||||
|
public HashIntSet ()
|
||||||
|
{
|
||||||
|
this(DEFAULT_CAPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new set with the specified initial capacity.
|
||||||
|
*/
|
||||||
|
public HashIntSet (int capacity)
|
||||||
|
{
|
||||||
|
this(capacity, Integer.MIN_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new set with the specified initial capacity and sentinel value.
|
||||||
|
*/
|
||||||
|
public HashIntSet (int capacity, int sentinel)
|
||||||
|
{
|
||||||
|
_sentinel = sentinel;
|
||||||
|
createBuckets(getBucketCount(capacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the sentinel value, which cannot itself be stored in the set because it is used
|
||||||
|
* internally to represent an unused location.
|
||||||
|
*
|
||||||
|
* @exception IllegalArgumentException if the set currently contains the requested sentinel.
|
||||||
|
*/
|
||||||
|
public void setSentinel (int sentinel)
|
||||||
|
{
|
||||||
|
if (_sentinel == sentinel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (contains(sentinel)) {
|
||||||
|
throw new IllegalArgumentException("Set contains sentinel value " + sentinel);
|
||||||
|
}
|
||||||
|
// replace every instance of the old sentinel with the new
|
||||||
|
for (int ii = 0; ii < _buckets.length; ii++) {
|
||||||
|
if (_buckets[ii] == _sentinel) {
|
||||||
|
_buckets[ii] = sentinel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_sentinel = sentinel;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the sentinel value.
|
||||||
|
*/
|
||||||
|
public int getSentinel ()
|
||||||
|
{
|
||||||
|
return _sentinel;
|
||||||
|
}
|
||||||
|
|
||||||
|
// documentation inherited from interface IntSet
|
||||||
|
public Interator interator ()
|
||||||
|
{
|
||||||
|
return new AbstractInterator() {
|
||||||
|
public boolean hasNext () {
|
||||||
|
checkConcurrentModification();
|
||||||
|
return _pos < _size;
|
||||||
|
}
|
||||||
|
public int nextInt () {
|
||||||
|
checkConcurrentModification();
|
||||||
|
if (_pos >= _size) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
if (_idx == 0) {
|
||||||
|
// start after a sentinel
|
||||||
|
while (_buckets[_idx++] != _sentinel);
|
||||||
|
}
|
||||||
|
int mask = _buckets.length - 1;
|
||||||
|
for (; _pos < _size; _idx++) {
|
||||||
|
int value = _buckets[_idx & mask];
|
||||||
|
if (value != _sentinel) {
|
||||||
|
_pos++;
|
||||||
|
_idx++;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we shouldn't get here
|
||||||
|
throw new RuntimeException("Ran out of elements getting next");
|
||||||
|
}
|
||||||
|
public void remove () {
|
||||||
|
checkConcurrentModification();
|
||||||
|
if (_idx == 0) {
|
||||||
|
throw new IllegalStateException("Next method not yet called");
|
||||||
|
}
|
||||||
|
int pidx = (--_idx) & (_buckets.length - 1);
|
||||||
|
if (_buckets[pidx] == _sentinel) {
|
||||||
|
throw new IllegalStateException("No element to remove");
|
||||||
|
}
|
||||||
|
_buckets[pidx] = _sentinel;
|
||||||
|
_pos--;
|
||||||
|
_size--;
|
||||||
|
_omodcount = ++_modcount;
|
||||||
|
shift(pidx);
|
||||||
|
}
|
||||||
|
protected void checkConcurrentModification () {
|
||||||
|
if (_modcount != _omodcount) {
|
||||||
|
throw new ConcurrentModificationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected int _pos, _idx;
|
||||||
|
protected int _omodcount = _modcount;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public boolean contains (int value)
|
||||||
|
{
|
||||||
|
if (value == _sentinel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int mask = _buckets.length - 1;
|
||||||
|
int start = hash(value) & mask, idx = start;
|
||||||
|
do {
|
||||||
|
int bvalue = _buckets[idx];
|
||||||
|
if (bvalue == value) {
|
||||||
|
return true;
|
||||||
|
} else if (bvalue == _sentinel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} while ((idx = idx + 1 & mask) != start);
|
||||||
|
|
||||||
|
// we shouldn't get here
|
||||||
|
throw new RuntimeException("Ran out of buckets looking for value " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public int size ()
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public boolean isEmpty ()
|
||||||
|
{
|
||||||
|
return _size == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public boolean add (int value)
|
||||||
|
{
|
||||||
|
if (value == _sentinel) {
|
||||||
|
throw new IllegalArgumentException("Can't add sentinel value " + value);
|
||||||
|
}
|
||||||
|
int mask = _buckets.length - 1;
|
||||||
|
int start = hash(value) & mask, idx = start;
|
||||||
|
do {
|
||||||
|
int bvalue = _buckets[idx];
|
||||||
|
if (bvalue == value) {
|
||||||
|
return false;
|
||||||
|
|
||||||
|
} else if (bvalue == _sentinel) {
|
||||||
|
_buckets[idx] = value;
|
||||||
|
_size++;
|
||||||
|
_modcount++;
|
||||||
|
int ncount = getBucketCount(_size, MAX_LOAD_FACTOR);
|
||||||
|
if (ncount > _buckets.length) {
|
||||||
|
rehash(ncount);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} while ((idx = idx + 1 & mask) != start);
|
||||||
|
|
||||||
|
// we shouldn't get here
|
||||||
|
throw new RuntimeException("Ran out of buckets adding value " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public boolean remove (int value)
|
||||||
|
{
|
||||||
|
if (value == _sentinel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int mask = _buckets.length - 1;
|
||||||
|
int start = hash(value) & mask, idx = start;
|
||||||
|
do {
|
||||||
|
int bvalue = _buckets[idx];
|
||||||
|
if (bvalue == value) {
|
||||||
|
_buckets[idx] = _sentinel;
|
||||||
|
_size--;
|
||||||
|
_modcount++;
|
||||||
|
int ncount = getBucketCount(_size, MIN_LOAD_FACTOR);
|
||||||
|
if (ncount < _buckets.length) {
|
||||||
|
rehash(ncount);
|
||||||
|
} else {
|
||||||
|
shift(idx);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else if (bvalue == _sentinel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} while ((idx = idx + 1 & mask) != start);
|
||||||
|
|
||||||
|
// we shouldn't get here
|
||||||
|
throw new RuntimeException("Ran out of buckets removing value " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public void clear ()
|
||||||
|
{
|
||||||
|
createBuckets(MIN_BUCKET_COUNT);
|
||||||
|
_size = 0;
|
||||||
|
_modcount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override // documentation inherited
|
||||||
|
public HashIntSet clone ()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
HashIntSet nset = (HashIntSet)super.clone();
|
||||||
|
nset._buckets = _buckets.clone();
|
||||||
|
return nset;
|
||||||
|
|
||||||
|
} catch (CloneNotSupportedException cnse) {
|
||||||
|
throw new AssertionError(cnse); // won't happen; we're Cloneable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreates the bucket array with the specified new count.
|
||||||
|
*/
|
||||||
|
protected void rehash (int ncount)
|
||||||
|
{
|
||||||
|
int[] obuckets = _buckets;
|
||||||
|
createBuckets(ncount);
|
||||||
|
|
||||||
|
for (int idx = 0, pos = 0; pos < _size; idx++) {
|
||||||
|
int value = obuckets[idx];
|
||||||
|
if (value != _sentinel) {
|
||||||
|
readd(value);
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (Re)creates and initializes the bucket array.
|
||||||
|
*/
|
||||||
|
protected void createBuckets (int count)
|
||||||
|
{
|
||||||
|
Arrays.fill(_buckets = new int[count], _sentinel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a value that we know is neither equal to the sentinel nor already in the set.
|
||||||
|
*/
|
||||||
|
protected void readd (int value)
|
||||||
|
{
|
||||||
|
int mask = _buckets.length - 1;
|
||||||
|
int start = hash(value) & mask, idx = start;
|
||||||
|
do {
|
||||||
|
if (_buckets[idx] == _sentinel) {
|
||||||
|
_buckets[idx] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} while ((idx = idx + 1 & mask) != start);
|
||||||
|
|
||||||
|
// we shouldn't get here
|
||||||
|
throw new RuntimeException("Ran out of buckets readding value " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shifts elements over to fill a newly empty slot.
|
||||||
|
*/
|
||||||
|
protected void shift (int start)
|
||||||
|
{
|
||||||
|
// first, scan backwards to find the previous sentinel
|
||||||
|
int mask = _buckets.length - 1;
|
||||||
|
int sidx = start;
|
||||||
|
while ((sidx = sidx + mask & mask) != start) {
|
||||||
|
if (_buckets[sidx] == _sentinel) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// then forwards to shift elements into place
|
||||||
|
int idx = start, pidx = start;
|
||||||
|
while ((idx = idx + 1 & mask) != start) {
|
||||||
|
int bvalue = _buckets[idx];
|
||||||
|
if (bvalue == _sentinel) {
|
||||||
|
_buckets[pidx] = _sentinel;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int bidx = hash(bvalue) & mask;
|
||||||
|
if (pidx > sidx ? (bidx > sidx && bidx <= pidx) : (bidx > sidx || bidx <= pidx)) {
|
||||||
|
_buckets[pidx] = bvalue;
|
||||||
|
pidx = idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we shouldn't get here
|
||||||
|
throw new RuntimeException("Ran out of buckets fixing empty location at " + start);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom serializer.
|
||||||
|
*/
|
||||||
|
private void writeObject (ObjectOutputStream out)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
out.defaultWriteObject();
|
||||||
|
for (int idx = 0, pos = 0; pos < _size; idx++) {
|
||||||
|
int value = _buckets[idx];
|
||||||
|
if (value != _sentinel) {
|
||||||
|
out.writeInt(value);
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom deserializer.
|
||||||
|
*/
|
||||||
|
private void readObject (ObjectInputStream in)
|
||||||
|
throws IOException, ClassNotFoundException
|
||||||
|
{
|
||||||
|
in.defaultReadObject();
|
||||||
|
createBuckets(getBucketCount(_size));
|
||||||
|
for (int ii = 0; ii < _size; ii++) {
|
||||||
|
readd(in.readInt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the hash of the specified value. This is copied straight from
|
||||||
|
* the source of {@link java.util.HashMap} and ensures that we don't rely
|
||||||
|
* solely on the bits of lower significance.
|
||||||
|
*/
|
||||||
|
protected static int hash (int h)
|
||||||
|
{
|
||||||
|
h ^= (h >>> 20) ^ (h >>> 12);
|
||||||
|
return h ^ (h >>> 7) ^ (h >>> 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the number of buckets needed to provide the given capacity with a load factor
|
||||||
|
* halfway between the minimum and the maximum.
|
||||||
|
*/
|
||||||
|
protected static int getBucketCount (int capacity)
|
||||||
|
{
|
||||||
|
return getBucketCount(capacity, (MIN_LOAD_FACTOR + MAX_LOAD_FACTOR) * 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the number of buckets needed to provide the given capacity with the specified load
|
||||||
|
* factor.
|
||||||
|
*/
|
||||||
|
protected static int getBucketCount (int capacity, float loadFactor)
|
||||||
|
{
|
||||||
|
int size = (int)(capacity / loadFactor);
|
||||||
|
int highest = Integer.highestOneBit(size);
|
||||||
|
return Math.max((size == highest) ? highest : (highest << 1), MIN_BUCKET_COUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The buckets containing the contents of the set. */
|
||||||
|
protected transient int[] _buckets;
|
||||||
|
|
||||||
|
/** The number of elements in the set. */
|
||||||
|
protected int _size;
|
||||||
|
|
||||||
|
/** The value that indicates an empty location in the contents. */
|
||||||
|
protected int _sentinel;
|
||||||
|
|
||||||
|
/** Incremented on each set modification, used to track concurrent changes. */
|
||||||
|
protected transient int _modcount;
|
||||||
|
|
||||||
|
/** The default initial capacity of this set. */
|
||||||
|
protected static final int DEFAULT_CAPACITY = 16;
|
||||||
|
|
||||||
|
/** The minimum number of buckets to provide. */
|
||||||
|
protected static final int MIN_BUCKET_COUNT = 8;
|
||||||
|
|
||||||
|
/** The maximum load factor (ratio of size to length of contents array). */
|
||||||
|
protected static final float MAX_LOAD_FACTOR = 0.7f;
|
||||||
|
|
||||||
|
/** The minimum load factor. */
|
||||||
|
protected static final float MIN_LOAD_FACTOR = 0.3f;
|
||||||
|
|
||||||
|
/** Change this if the fields or inheritance hierarchy ever changes (extremely unlikely). */
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
}
|
||||||
@@ -20,92 +20,18 @@
|
|||||||
|
|
||||||
package com.samskivert.util.tests;
|
package com.samskivert.util.tests;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import com.samskivert.util.AbstractIntSet;
|
||||||
import java.util.Random;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import org.junit.*;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
import com.samskivert.util.ArrayIntSet;
|
import com.samskivert.util.ArrayIntSet;
|
||||||
|
|
||||||
public class ArrayIntSetTest
|
public class ArrayIntSetTest extends IntSetTestBase
|
||||||
{
|
{
|
||||||
@Test
|
protected AbstractIntSet createSet ()
|
||||||
public void testAdd ()
|
|
||||||
{
|
{
|
||||||
ArrayIntSet set = new ArrayIntSet();
|
return new ArrayIntSet();
|
||||||
set.add(new int[] { 3, 5, 5, 9, 5, 7, 1 });
|
|
||||||
int[] values = { 1, 3, 5, 7, 9 };
|
|
||||||
assertTrue("values equal", Arrays.equals(values, set.toIntArray()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
protected AbstractIntSet createSet (int[] values)
|
||||||
public void testConstruct ()
|
|
||||||
{
|
{
|
||||||
int[] values = { 11, 3, 20, 6, 16, 15, 24, 23, 21, 10, 4, 19, 13, 25, 22, 18 };
|
return new ArrayIntSet(values);
|
||||||
ArrayIntSet set1 = new ArrayIntSet(values);
|
|
||||||
ArrayIntSet set2 = new ArrayIntSet();
|
|
||||||
set2.add(values);
|
|
||||||
assertTrue(set1.equals(set2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testIterate ()
|
|
||||||
{
|
|
||||||
ArrayIntSet set = new ArrayIntSet(new int[] { 3, 5, 5, 9, 5, 7, 1 });
|
|
||||||
Set<Integer> jset = new TreeSet<Integer>();
|
|
||||||
jset.addAll(set);
|
|
||||||
assertTrue(jset.equals(set));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOps ()
|
|
||||||
{
|
|
||||||
ArrayIntSet set1 = new ArrayIntSet();
|
|
||||||
set1.add(new int[] { 1, 2, 3, 5, 7, 12, 19, 35 });
|
|
||||||
ArrayIntSet set2 = new ArrayIntSet();
|
|
||||||
set2.add(new int[] { 3, 4, 5, 11, 13, 17, 19, 25 });
|
|
||||||
|
|
||||||
ArrayIntSet set3 = new ArrayIntSet();
|
|
||||||
set3.add(new int[] { 3, 5, 19 });
|
|
||||||
|
|
||||||
// intersect sets 1 and 2; making sure that retainAll() returns
|
|
||||||
// true to indicate that set 1 was modified
|
|
||||||
assertTrue("retain modifies", set1.retainAll(set2));
|
|
||||||
|
|
||||||
// make sure the intersections were correct
|
|
||||||
assertTrue("intersection", set1.equals(set3));
|
|
||||||
|
|
||||||
// make sure that retainAll returns false if we do something that
|
|
||||||
// doesn't modify the set
|
|
||||||
assertTrue("retain didn't modify", !set1.retainAll(set3));
|
|
||||||
|
|
||||||
Random rando = new Random();
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
ArrayIntSet s1 = new ArrayIntSet();
|
|
||||||
ArrayIntSet s2 = new ArrayIntSet();
|
|
||||||
ArrayIntSet s3 = new ArrayIntSet();
|
|
||||||
|
|
||||||
// add some odd numbers to all three sets
|
|
||||||
for (int c = 0; c < 100; c++) {
|
|
||||||
int r = rando.nextInt(5000) * 2 + 1;
|
|
||||||
s1.add(r);
|
|
||||||
s2.add(r);
|
|
||||||
s3.add(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
// now add some even numbers to each of the first two sets in
|
|
||||||
// non-overlapping ranges
|
|
||||||
for (int c = 0; c < 100; c++) {
|
|
||||||
s1.add(rando.nextInt(5000)*2);
|
|
||||||
s2.add(rando.nextInt(5000)*2 + 15000);
|
|
||||||
}
|
|
||||||
|
|
||||||
// now ensure that s1.retainAll(s2) equals s3
|
|
||||||
s1.retainAll(s2);
|
|
||||||
assertTrue("random intersection", s1.equals(s3));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
//
|
||||||
|
// samskivert library - useful routines for java programs
|
||||||
|
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||||
|
//
|
||||||
|
// 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 com.samskivert.util.tests;
|
||||||
|
|
||||||
|
import com.samskivert.util.AbstractIntSet;
|
||||||
|
import com.samskivert.util.HashIntSet;
|
||||||
|
|
||||||
|
public class HashIntSetTest extends IntSetTestBase
|
||||||
|
{
|
||||||
|
protected AbstractIntSet createSet ()
|
||||||
|
{
|
||||||
|
return new HashIntSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected AbstractIntSet createSet (int[] values)
|
||||||
|
{
|
||||||
|
return new HashIntSet(values);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// $Id$
|
||||||
|
//
|
||||||
|
// samskivert library - useful routines for java programs
|
||||||
|
// Copyright (C) 2001-2010 Michael Bayne, et al.
|
||||||
|
//
|
||||||
|
// 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 com.samskivert.util.tests;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
import org.junit.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import com.samskivert.util.AbstractIntSet;
|
||||||
|
|
||||||
|
public abstract class IntSetTestBase
|
||||||
|
{
|
||||||
|
@Test
|
||||||
|
public void testAdd ()
|
||||||
|
{
|
||||||
|
AbstractIntSet set = createSet();
|
||||||
|
set.add(new int[] { 3, 5, 5, 9, 5, 7, 1 });
|
||||||
|
int[] values = { 1, 3, 5, 7, 9 };
|
||||||
|
assertTrue("values equal", Arrays.equals(values, set.toIntArray()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConstruct ()
|
||||||
|
{
|
||||||
|
int[] values = { 11, 3, 20, 6, 16, 15, 24, 23, 21, 10, 4, 19, 13, 25, 22, 18 };
|
||||||
|
AbstractIntSet set1 = createSet(values);
|
||||||
|
AbstractIntSet set2 = createSet();
|
||||||
|
set2.add(values);
|
||||||
|
assertTrue(set1.equals(set2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIterate ()
|
||||||
|
{
|
||||||
|
AbstractIntSet set = createSet(new int[] { 3, 5, 5, 9, 5, 7, 1 });
|
||||||
|
Set<Integer> jset = new TreeSet<Integer>();
|
||||||
|
jset.addAll(set);
|
||||||
|
assertTrue(jset.equals(set));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOps ()
|
||||||
|
{
|
||||||
|
AbstractIntSet set1 = createSet();
|
||||||
|
set1.add(new int[] { 1, 2, 3, 5, 7, 12, 19, 35 });
|
||||||
|
AbstractIntSet set2 = createSet();
|
||||||
|
set2.add(new int[] { 3, 4, 5, 11, 13, 17, 19, 25 });
|
||||||
|
|
||||||
|
AbstractIntSet set3 = createSet();
|
||||||
|
set3.add(new int[] { 3, 5, 19 });
|
||||||
|
|
||||||
|
// intersect sets 1 and 2; making sure that retainAll() returns
|
||||||
|
// true to indicate that set 1 was modified
|
||||||
|
assertTrue("retain modifies", set1.retainAll(set2));
|
||||||
|
|
||||||
|
// make sure the intersections were correct
|
||||||
|
assertTrue("intersection", set1.equals(set3));
|
||||||
|
|
||||||
|
// make sure that retainAll returns false if we do something that
|
||||||
|
// doesn't modify the set
|
||||||
|
assertTrue("retain didn't modify", !set1.retainAll(set3));
|
||||||
|
|
||||||
|
Random rando = new Random();
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
AbstractIntSet s1 = createSet();
|
||||||
|
AbstractIntSet s2 = createSet();
|
||||||
|
AbstractIntSet s3 = createSet();
|
||||||
|
|
||||||
|
// add some odd numbers to all three sets
|
||||||
|
for (int c = 0; c < 100; c++) {
|
||||||
|
int r = rando.nextInt(5000) * 2 + 1;
|
||||||
|
s1.add(r);
|
||||||
|
s2.add(r);
|
||||||
|
s3.add(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now add some even numbers to each of the first two sets in
|
||||||
|
// non-overlapping ranges
|
||||||
|
for (int c = 0; c < 100; c++) {
|
||||||
|
s1.add(rando.nextInt(5000)*2);
|
||||||
|
s2.add(rando.nextInt(5000)*2 + 15000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now ensure that s1.retainAll(s2) equals s3
|
||||||
|
s1.retainAll(s2);
|
||||||
|
assertTrue("random intersection", s1.equals(s3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract AbstractIntSet createSet ();
|
||||||
|
|
||||||
|
protected abstract AbstractIntSet createSet (int[] values);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user