package weiss.util; // Modifed by GN April 4, 2003 for Prog 5 import java.io.Serializable; /** * HashSet implementation. * Matches are based on equals; and hashCode must be consistently defined. */ public class HashSet extends AbstractCollection implements Set { /** * Construct an empty HashSet. */ public HashSet( ) { allocateArray( DEFAULT_TABLE_SIZE ); clear( ); } /** * new by GN - Construct an empty HashSet with * the hash table of a specified size and specified load factor limit. * Note that when the load factor limit is reached, table is enlarged. * @param n = table size, and lf = load factor limit */ public HashSet (int n, double lf) { allocateArray(nextPrime(n)); LOADFACTORLIMIT = lf; clear(); } /** * Construct a HashSet from any collection. */ public HashSet( Collection other ) { allocateArray( other.size( ) * 2 ); clear( ); Iterator itr = other.iterator( ); while( itr.hasNext( ) ) add( itr.next( ) ); } /** * Returns the number of items in this collection. * @return the number of items in this collection. */ public int size( ) { return currentSize; } /** * New by GN * @return load factor of hash table */ public double getLoadFactor () { return (double) currentSize * 100 / this.tableSize(); } /** * TO BE FILLED IN * @return the size of the largest cluster */ public int getLargestClusterSize () { // DUMMY STATEMENT return 0; } /** * TO BE FILLED IN * @return the average size of a cluster */ public double getAverageClusterSize () { // DUMMY STATEMENT return 0; } /** * New by GN * @return the current table size */ public int tableSize() { return array.length; } /** * This method is not part of standard Java 1.2. * Like contains, it checks if x is in the set. * If it is, it returns the reference to the matching * object; otherwise it returns null. * @param x the object to search for. * @return if contains(x) is false, the return value is null; * otherwise, the return value is the object that causes * contains(x) to return true. */ public Object getMatch( Object x ) { int currentPos = findPos( x ); if( array[ currentPos ] == null ) return null; return array[ currentPos ].element; } /** * Tests if some item is in this collection. * @param x any object. * @return true if this collection contains an item equal to x. */ public boolean contains( Object x ) { return isActive( array, findPos( x ) ); } /** * Tests if item in pos is active. * @param pos a position in the hash table. * @param arr the HashEntry array (can be oldArray during rehash). * @return true if this position is active. */ private static boolean isActive( HashEntry [ ] arr, int pos ) { return arr[ pos ] != null && arr[ pos ].isActive; } /** * Adds an item to this collection. * @param x any object. * @return true if this item was added to the collection. */ public boolean add( Object x ) { int currentPos = findPos( x ); if( isActive( array, currentPos ) ) return false; array[ currentPos ] = new HashEntry( x, true ); currentSize++; occupied++; modCount++; // modified by GN if( occupied > array.length * LOADFACTORLIMIT ) rehash( ); return true; } /** * Private routine to perform rehashing. * Can be called by both add and remove. */ private void rehash( ) { HashEntry [ ] oldArray = array; // Create a new, empty table allocateArray( nextPrime( 4 * size( ) ) ); currentSize = 0; occupied = 0; // Copy table over for( int i = 0; i < oldArray.length; i++ ) if( isActive( oldArray, i ) ) add( oldArray[ i ].element ); } /** * Removes an item from this collection. * @param x any object. * @return true if this item was removed from the collection. */ public boolean remove( Object x ) { int currentPos = findPos( x ); if( !isActive( array, currentPos ) ) return false; array[ currentPos ].isActive = false; currentSize--; modCount++; // GN Ideally you would like to parameterize the number 8 also. if( currentSize < array.length / 8 ) rehash( ); return true; } /** * Change the size of this collection to zero. */ public void clear( ) { currentSize = occupied = 0; modCount++; for( int i = 0; i < array.length; i++ ) array[ i ] = null; } /** * Obtains an Iterator object used to traverse the collection. * @return an iterator positioned prior to the first element. */ public Iterator iterator( ) { return new HashSetIterator( ); } /** * This is the implementation of the HashSetIterator. * It maintains a notion of a current position and of * course the implicit reference to the HashSet. */ private class HashSetIterator implements Iterator { private int expectedModCount = modCount; private int currentPos = -1; private int visited = 0; public boolean hasNext( ) { if( expectedModCount != modCount ) throw new ConcurrentModificationException( ); return visited != size( ); } public Object next( ) { if( !hasNext( ) ) throw new NoSuchElementException( ); do { currentPos++; } while( currentPos < array.length && !isActive( array, currentPos ) ); visited++; return array[ currentPos ].element; } public void remove( ) { if( expectedModCount != modCount ) throw new ConcurrentModificationException( ); if( currentPos == -1 || !isActive( array, currentPos ) ) throw new IllegalStateException( ); array[ currentPos ].isActive = false; currentSize--; visited--; modCount++; expectedModCount++; } } private static class HashEntry implements Serializable { public Object element; // the element public boolean isActive; // false if marked deleted public HashEntry( Object e ) { this( e, true ); } public HashEntry( Object e, boolean i ) { element = e; isActive = i; } } /** * Method that performs quadratic probing resolution. * @param x the item to search for. * @return the position where the search terminates. */ private int findPos( Object x ) { // int collisionNum = 0; collisionNum = 0; // modified by GN int currentPos = ( x == null ) ? 0 : Math.abs( x.hashCode( ) % array.length ); while( array[ currentPos ] != null ) { if( x == null ) { if( array[ currentPos ].element == null ) break; } else if( x.equals( array[ currentPos ].element ) ) break; if (DEBUG) { if ( array[ currentPos ].element == null ) { System.out.println("\t" + collisionNum + " \t\t" + currentPos + " \t** null **"); } else { System.out.println("\t" + collisionNum + " \t\t" + currentPos + " \t\"" + array[currentPos].element.toString() + "\""); } } currentPos += 2 * ++collisionNum - 1; // Compute ith probe if( currentPos >= array.length ) // Implement the mod currentPos -= array.length; } return currentPos; } /** * Internal method to allocate array. * @param arraySize the size of the array. */ private void allocateArray( int arraySize ) { array = new HashEntry[ arraySize ]; } /** * Internal method to find a prime number at least as large as n. * @param n the starting number (must be positive). * @return a prime number larger than or equal to n. */ private static int nextPrime( int n ) { if( n % 2 == 0 ) n++; for( ; !isPrime( n ); n += 2 ) ; return n; } /** * Internal method to test if a number is prime. * Not an efficient algorithm. * @param n the number to test. * @return the result of the test. */ private static boolean isPrime( int n ) { if( n == 2 || n == 3 ) return true; if( n == 1 || n % 2 == 0 ) return false; for( int i = 3; i * i <= n; i += 2 ) if( n % i == 0 ) return false; return true; } /** * New by GN * @return number of entries of the hash table * that were accessed by the previous call to findPos() */ public static int getCollisionNum() { return collisionNum + 1; } /** * New by GN * if the DEBUG flag is set to true, findPos will print out all entries accessed. */ public static void setDEBUG(boolean flag) { DEBUG = flag; } private static final int DEFAULT_TABLE_SIZE = 101; private int currentSize = 0; private int occupied = 0; private int modCount = 0; private static int collisionNum = 0; // New by GN private static boolean DEBUG = false; // New by GN private static double LOADFACTORLIMIT = 0.5; // New by GN private HashEntry [ ] array; }