// CSE 143, Winter 2009, Marty Stepp // A HashIntSet implements a set of any type of objects using a hash table. // This provides O(1) average time to add, remove, and search. (Wow!) // This file is identical to HashIntSet except that it uses generics to be able // to store any type of elements, not just integers. // It also supports "re-hashing," which is growing the hash table when it // becomes too full. import java.util.LinkedList; import java.util.List; public class HashAnythingSet implements AnythingSet { private static final int DEFAULT_CAPACITY = 137; // arbitrary large number private static final double FULL_LOAD_FACTOR = 0.75; // how full before rehash // "hash table" - an array of hash "buckets" where values can be stored private List[] hashTable; private int size; // Creates a new empty set with a hash table of default capacity. public HashAnythingSet() { this(DEFAULT_CAPACITY); } // Creates a new empty set with the given capacity. // Precondition: capacity > 0 // The @SuppressWarnings("unchecked") below is to avoid a compiler warning // for casting to a generic type. public HashAnythingSet(int capacity) { rehash(capacity); } // Adds the given value to this set. If it is already contained in // this set, no change is made, because sets do not allow duplicates. // Notice that in this version we call value.hashCode() to get the bucket. public void add(E value) { if (!contains(value)) { if (loadFactor() >= FULL_LOAD_FACTOR) { rehash(hashTable.length * 2); } int index = hashFunction(value); hashTable[index].add(value); size++; } } // Returns true if the given value is contained in this set. // Notice that in this version we call value.hashCode() to get the bucket. public boolean contains(E value) { int index = hashFunction(value); return hashTable[index].contains(value); } // Removes the given value from this set if it is contained in the set. // If the value is not contained in the set, no change is made. public void remove(E value) { int index = hashFunction(value); if (hashTable[index].remove((Integer) value)) { size--; } } // Maps from the given value to its preferred array index to be stored. private int hashFunction(E value) { return Math.abs(value.hashCode()) % hashTable.length; } // Returns the hash table's "load factor," which is the ratio of the number // of elements it contains to the length of the hash table. // Most hash-based collections grow their hash table array when the load // factor exceeds some threshold. private double loadFactor() { return (double) size / hashTable.length; } // Grows the hash table to the given size. If there were previous elements, // we must manually re-add every element because buckets change on resize. @SuppressWarnings("unchecked") private void rehash(int capacity) { List[] oldHashTable = hashTable; size = 0; hashTable = (List[]) (new List[capacity]); for (int i = 0; i < hashTable.length; i++) { hashTable[i] = new LinkedList(); } if (oldHashTable != null) { for (List list : oldHashTable) { for (E element : list) { add(element); } } } } }