// An implementation of Dictionaries, using hash tables. // Keys need not be comparable, but they must have hashcode methods. // (c) 1998, 2001 duane a. bailey package structure5; import java.util.Iterator; import java.lang.Iterable; import java.lang.Math; /** * Implements a dictionary as a table of hashed key-value pairs. * Collisions are resolved through linear probing. Values used * as keys in this structure must have a hashcode method that returns * the same value when two keys are "equals". Initially, a table of suggested * size is allocated. It will be expanded as the load factor (ratio of * pairs to entries) grows to meet maximumLoadFactor. *

* Example Usage: *

* To create a hashtable by reading a collection of words and * definitions from System.in we could use the following: *

*

 * public static void main (String[] argv){
 *      Hashtable dict = new {@link #Hashtable()};
 *      ReadStream r = new ReadStream();
 *      String word, def;
 *      System.out.println("Enter a word: ");
 *      while(!r.eof()){
 *          word = r.readLine();
 *          System.out.println("Enter a definition: ");
 *          def = r.readLine();
 *          dict.{@link #put(Object,Object) put(word,def)};
 *          System.out.println("Enter a word: ");
 *      }
 *      System.out.println(dict);
 * }
 * 
* @version $Id: Hashtable.java 34 2007-08-09 14:43:44Z bailey $ * @author, 2001 duane a. bailey * @see ChainedHashtable */ public class Hashtable implements Map, Iterable { /** * A single key-value pair to be used as a token * indicating a reserved location in the hashtable. * Reserved locations are available for insertion, * but cause collisions on lookup. */ protected static final String RESERVED = "RESERVED"; /** * The data associated with the hashtable. */ protected Vector> data; /** * The number of key-value pairs in table. */ protected int count; /** * The maximum load factor that causes rehashing of the table. */ protected final double maximumLoadFactor = 0.6; /** * Construct a hash table that is capable of holding at least * initialCapacity values. If that value is approached, it will * be expanded appropriately. It is probably best if the capacity * is prime. Table is initially empty. * * @pre initialCapacity > 0 * @post constructs a new Hashtable * holding initialCapacity elements * * @param initialCapacity The initial capacity of the hash table. */ public Hashtable(int initialCapacity) { Assert.pre(initialCapacity > 0, "Hashtable capacity must be positive."); data = new Vector>(); data.setSize(initialCapacity); count = 0; } /** * Construct a hash table that is initially empty. * * @post constructs a new Hashtable */ public Hashtable() { this(997); } /** * Remove all key-value pairs from hashtable. * * @post removes all elements from Hashtable */ public void clear() { int i; for (i = 0; i < data.size(); i++) { data.set(i,null); } count = 0; } /** * Return the number of key-value pairs within the table. * * @post returns number of elements in hash table * * @return The number of key-value pairs currently in table. */ public int size() { return count; } /** * Determine if table is empty. * * @post returns true iff hash table has 0 elements * * @return True if table is empty. */ public boolean isEmpty() { return size() == 0; } /** * Returns true if a specific value appears within the table. * * @pre value is non-null Object * @post returns true iff hash table contains value * * @param value The value sought. * @return True iff the value appears within the table. */ public boolean containsValue(V value) { for (V tableValue : this) { if (tableValue.equals(value)) return true; } // no value found return false; } /** * Returns true iff a specific key appears within the table. * * @pre key is a non-null Object * @post returns true if key appears in hash table * * @param key The key sought. * @return True iff the key sought appears within table. */ public boolean containsKey(K key) { int hash = locate(key); return data.get(hash) != null && !data.get(hash).reserved(); } /** * Returns a traversal that traverses over the values of the * hashtable. * * @post returns traversal to traverse hash table * * @return A value traversal, over the values of the table. */ public Iterator iterator() { return new ValueIterator((AbstractIterator>)new HashtableIterator(data)); } /** * Get the value associated with a key. * * @pre key is non-null Object * @post returns value associated with key, or null * * @param key The key used to find the desired value. * @return The value associated with the desired key. */ public V get(K key) { int hash = locate(key); if (data.get(hash) == null || data.get(hash).reserved()) return null; return data.get(hash).getValue(); } /** * Get a traversal over the keys of the hashtable. * * @post returns traversal to traverse the keys of hash table; * * @return a traversal over the key values appearing within table. */ public Iterator keys() { return new KeyIterator(new HashtableIterator(data)); } protected int locate(K key) { // compute an initial hash code int hash = Math.abs(key.hashCode() % data.size()); // keep track of first unused slot, in case we need it int reservedSlot = -1; boolean foundReserved = false; while (data.get(hash) != null) { if (data.get(hash).reserved()) { // remember reserved slot if we fail to locate value if (!foundReserved) { reservedSlot = hash; foundReserved = true; } } else { // value located? return the index in table if (key.equals(data.get(hash).getKey())) return hash; } // linear probing; other methods would change this line: hash = (1+hash)%data.size(); } // return first empty slot we encountered if (!foundReserved) return hash; else return reservedSlot; } /** * Place a key-value pair within the table. * * @pre key is non-null object * @post key-value pair is added to hash table * * @param key The key to be added to table. * @param value The value associated with key. * @return The old value associated with key if previously present. */ public V put(K key, V value) { if (maximumLoadFactor*data.size() <= (1+count)) { extend(); } int hash = locate(key); if (data.get(hash) == null || data.get(hash).reserved()) { // logically empty slot; just add association data.set(hash,new HashAssociation(key,value)); count++; return null; } else { // full slot; add new and return old value HashAssociation a = data.get(hash); V oldValue = a.getValue(); a.setValue(value); return oldValue; } } /** * Put all of the values found in another map into this map, * overriding previous key-value associations. * @param other is the source mapping * @pre other map is valid * @post this hashtable is augmented by the values found in other */ public void putAll(Map other) { for (Association e : other.entrySet()) { put(e.getKey(),e.getValue()); } } /** * Remove a key-value pair from the table. * * @pre key is non-null object * @post removes key-value pair associated with key * * @param key The key of the key-value pair to be removed. * @return The value associated with the removed key. */ public V remove(K key) { int hash = locate(key); if (data.get(hash) == null || data.get(hash).reserved()) { return null; } count--; V oldValue = data.get(hash).getValue(); data.get(hash).reserve(); // in case anyone depends on us return oldValue; } /** * @post expands the hashtable to reduce loading */ protected void extend() { // extends the hashtable for larger capacity. int i; AbstractIterator> it = new HashtableIterator(data); // BE AWARE: at this point, we can change the hash table, // but changes to the hashtable traversal implementation might // be problematic. int newSize = 2*data.size(); Assert.condition(newSize > 0, "Hashtable vector size must be greater than 0."); data = new Vector>(); data.setSize(newSize); count = 0; while (it.hasNext()) { Association a = it.next(); put(a.getKey(),a.getValue()); } } /** * @post returns a set of Associations associated with this Map */ public Set> entrySet() { Set> result = new SetList>(); Iterator> i = new HashtableIterator(data); while (i.hasNext()) { result.add(i.next()); } return result; } /** * @post returns a Set of keys used in this Map */ public Set keySet() { Set result = new SetList(); Iterator i = new KeyIterator(new HashtableIterator(data)); while (i.hasNext()) { result.add(i.next()); } return result; } /** * @post returns a Structure that contains the (possibly repeating) * values of the range of this map. */ public Structure values() { List result = new SinglyLinkedList(); Iterator i = new ValueIterator(new HashtableIterator(data)); while (i.hasNext()) { result.add(i.next()); } return result; } /** * Generate a string representation of the hash table. * * @post returns a string representation of hash table * * @return The string representing the table. */ public String toString() { StringBuffer s = new StringBuffer(); int i; s.append(""); return s.toString(); } }