package rpi.goldsd.container; import java.util.Enumeration; /** * The Table class provides a generic associative hashtable by * implementing the AssociativeContainer interface. Objects * are identified by a unique key object that is also used in determining * the corresponding hash values. The current implementation of the * hashtable consists of an array of references to LinkedList objects. *

* One of the current shortcomings of this class is the implicit * requirement that the Comparable key objects also implement * the Hashable interface. Thus, instead of a compilation error * indicating that a key must be Hashable, a run-time exception * occurs. * * @see LinkedList * @see Comparable * @see Hashable * @see DuplicateElementException * @see EmptyEnumeration * @version 1.3, 3/27/98 * @author David Goldschmidt */ public class Table implements AssociativeContainer { /** The default table size used by the default constructor. */ public final static int DEFAULT_SIZE = 101; /** * Encapsulates the information to be stored in this hashtable. * @version 1.0, 3/15/98 * @author David Goldschmidt */ protected class TableData implements Comparable { public TableData( int hashValue, Comparable key, Object data ) { this.hashValue = hashValue; this.key = key; this.data = data; } public boolean isEqualTo( Comparable C ) { if ( C instanceof TableData ) return ( key.isEqualTo( ((TableData)C).key ) ); else return ( key.isEqualTo( C ) ); } public boolean isLessThan( Comparable C ) { if ( C instanceof TableData ) return ( key.isLessThan( ((TableData)C).key ) ); else return ( key.isLessThan( C ) ); } public int hashValue; public Comparable key; public Object data; } /** * Constructs a hashtable with the specified table size. * @param tableSize the number of hash "buckets" to construct. */ public Table( int tableSize ) { table = new LinkedList[tableSize]; numElements = 0; } /** Constructs a hashtable with the default table size. */ public Table() { this( DEFAULT_SIZE ); } /** * Adds a new associative entry to this hashtable. If the * Comparable key parameter does not also implement the * Hashable interface (which is a sub-interface of the * Comparable interface), then an IllegalArgumentException * is thrown. *

* This method performs in O(n) time in the worst-case, * where n is the number of elements in the hashtable. * Inherent in hashing, the average performance is significantly better * since the linked-list used to store the data should be very * small with an evenly distributed hash function. * @param keyAndData the unique key used to identify the associated * data which is simply the Comparable argument. * @exception java.lang.IllegalArgumentException if the given key * object does not implement the Hashable interface. * @exception DuplicateElementException if the key used already * exists in this table. */ public synchronized void add( Comparable keyAndData ) throws DuplicateElementException, IllegalArgumentException { add( keyAndData, keyAndData ); } /** * Adds a new associative entry to this hashtable. If the * Comparable key parameter does not also implement the * Hashable interface (which is a sub-interface of the * Comparable interface), then an IllegalArgumentException * is thrown. *

* This method performs in O(n) time in the worst-case, * where n is the number of elements in the hashtable. * Inherent in hashing, the average performance is significantly better * since the linked-list used to store the data should be very * small with an evenly distributed hash function. * @param key the unique key used to identify the associated data. * @param data the actual data to be stored. * @exception java.lang.IllegalArgumentException if the given key * object does not implement the Hashable interface. * @exception DuplicateElementException if the key used already * exists in this table. */ public synchronized void add( Comparable key, Object data ) throws DuplicateElementException, IllegalArgumentException { if ( ! ( key instanceof Hashable ) ) throw new IllegalArgumentException( "Key must implement Hashable." ); int hashValue = hash( key ); TableData node = new TableData( hashValue, key, data ); if ( table[hashValue] == null ) table[hashValue] = new LinkedList(); else if ( table[hashValue].contains( node ) ) throw new DuplicateElementException( "Duplicate key: " + key + "." ); table[hashValue].addToHead( new ListNode( node ) ); numElements++; } /** * Removes all elements from this hashtable. *

* This method performs in constant time O(1). */ public synchronized void clear() { int tableSize = table.length; table = new LinkedList[tableSize]; numElements = 0; } /** * Returns true if the given key maps to an element in the * associative container. This method makes use of the map() * method. *

* This method performs in O(n) in the worst-case; however, * performance is substantially better in the average case, provided * that the hash function provides an even distribution of hash values. * @param key the unique key used to identify an element in the * associative container. * @return true if the given key maps to an element in * the associative container; false otherwise. */ public boolean contains( Comparable key ) { return ( map( key ) != null ); } /** * Provides an enumeration of the elements in this hashtable. *

* This method performs in constant time O(1). * @return an enumeration of the elements stored in this hashtable. */ public synchronized Enumeration elements() { return ( new Enumeration() { int tablePosition = 0; int elementsReturned = 0; Enumeration e = null; public boolean hasMoreElements() { return ( elementsReturned < numElements ); } public Object nextElement() { if ( ! hasMoreElements() ) return null; if ( e == null || ! e.hasMoreElements() ) { while ( table[tablePosition] == null ) tablePosition++; e = table[tablePosition++].elements(); } ListNode N = (ListNode)e.nextElement(); elementsReturned++; return ( ((TableData)N.getData()).data ); } } ); } /** * Provides an enumeration of the elements in this hashtable for * a specific hash value (i.e. an enumeration of the elements in * the linked list for the given hash value). *

* This method performs in constant time O(1). * @param hashValue the hash value to look up. * @return an enumeration of the elements stored in this hashtable * for a specific hash value. */ public synchronized Enumeration elements( final int hashValue ) { if ( table[hashValue % table.length] == null ) return new EmptyEnumeration(); else { return ( new Enumeration() { int tablePosition = hashValue % table.length; Enumeration e = table[tablePosition].elements(); public boolean hasMoreElements() { return e.hasMoreElements(); } public Object nextElement() { ListNode N = (ListNode)e.nextElement(); TableData D = (TableData)N.getData(); return ( D.data ); } } ); } } /** * Returns the number of elements in this hashtable. *

* This method performs in constant time O(1). * @return the number of elements in this hashtable. */ public synchronized int getSize() { return numElements; } /** * Returns the size of the hashtable. *

* This method performs in constant time O(1). * @return the size of the hashtable. */ public synchronized int getTableSize() { return table.length; } /** * Determines the hash value based on the Comparable key. *

* This method performs in constant time O(1). * @exception IllegalArgumentException if the given key object * does not implement the Hashable interface. */ protected synchronized int hash( Comparable key ) { if ( ! ( key instanceof Hashable ) ) throw new IllegalArgumentException( "Key must implement Hashable." ); return ( ((Hashable)key).hash( table.length ) ); } /** * Returns true if this hashtable contains no elements. *

* This method performs in constant time O(1). * @return true if this hashtable contains no elements; * false otherwise. */ public synchronized boolean isEmpty() { return ( numElements == 0 ); } /** * Provides an enumeration of the keys stored in this hashtable. *

* This method performs in constant time O(1). * @return an enumeration of the keys stored in this hashtable. */ public synchronized Enumeration keys() { return ( new Enumeration() { int tablePosition = 0; int elementsReturned = 0; Enumeration e = null; public boolean hasMoreElements() { return ( elementsReturned < numElements ); } public Object nextElement() { if ( ! hasMoreElements() ) return null; if ( e == null || ! e.hasMoreElements() ) { while ( table[tablePosition] == null ) tablePosition++; e = table[tablePosition++].elements(); } ListNode nextOne = (ListNode)e.nextElement(); elementsReturned++; return ( ((TableData)nextOne.getData()).key ); } } ); } /** * Provides a mechanism for mapping a given key to its corresponding data. *

* This method performs in O(n) time in the worst case, * where n is the number of elements in the hashtable. * Inherent in hashing, the average performance is substantially better * since the linked-list used to store the data should be very * small with an evenly distributed hash value. * @param key the key used to identify and locate the associated data. * @return the data stored in this hashtable identified by the * given key; or null if the given key * does not identify an element in this hashtable. * @exception IllegalArgumentException if the given key object * does not implement the Hashable interface. */ public synchronized Object map( Comparable key ) { if ( ! ( key instanceof Hashable ) ) throw new IllegalArgumentException( "Key must implement Hashable." ); int hashValue = hash( key ); if ( table[hashValue] == null ) return null; ListNode result = table[hashValue].find( key ); if ( result == null ) return null; else return ( ((TableData)result.getData()).data ); } /** * Removes a data element from this hashtable identified by the * given key. *

* This method performs in O(n) time in the worst-case, * where n is the number of elements in the hashtable. * Inherent in hashing, the average performance is substantially better * since the linked-list used to store the data should be very * small with an evenly distributed hash value. * @param key the key used to identify and locate the associated data. * @return the object that is removed from this hashtable. * @exception IllegalArgumentException if the given key object * does not implement the Hashable interface. */ public synchronized Object remove( Comparable key ) { if ( ! ( key instanceof Hashable ) ) throw new IllegalArgumentException( "Key must implement Hashable." ); int hashValue = hash( key ); if ( table[hashValue] == null ) return null; ListNode result = table[hashValue].find( key ); if ( result == null ) return null; table[hashValue].remove( result ); numElements--; if ( table[hashValue].isEmpty() ) table[hashValue] = null; return ( ((TableData)result.getData()).data ); } /** * The toString() method is the default method used to display * the keys and corresponding data elements of this hashtable. *

* This method performs in O(n) time since each individual * node of the hashtable must be visited once. * @return a String representing the keys and corresponding data * elements of this hashtable. */ public synchronized String toString() { String s = ""; Enumeration e = elements(); Enumeration k = keys(); while ( e.hasMoreElements() && k.hasMoreElements() ) s += k.nextElement() + ": " + e.nextElement() + "\n"; return s; } /** The number of associated elements in this hashtable. */ protected int numElements; /** The array of references to LinkedList objects. */ protected LinkedList[] table; }