Subversion Repositories programming

Rev

Rev 100 | Blame | Compare with Previous | Last modification | View Log | RSS feed

//import chap09.list03.Map;
// License: Public Domain (added on 07-11-2005)
import java.io.*;
import java.util.*;

public class HashTable implements Map {  // open addressing
    private Entry[] entries; 
    
    //size is the total number of elements, including NIL
    //used is the total number of elements, NOT INCLUDING NIL
    private int size, used;
    private float loadFactor;
    private final Entry NIL = new Entry(null,null);  // dummy
    
    private int probeType; //holds the type of probing to use    
    
    //variable to hold total number of collisions this run.
    //Definition: collision - the number of times we must jump to
    //                        get to the next null space in the table
    //                        This includes jumping NIL's
    //
    //NOTE: will only be counted on inserting values
    private int collisions; //initialized to 0 by default

    //create a HashTable with a certain capacity (maximum items)
    //and a maximum load factor (if we exceed this we will rehash)
    public HashTable(int capacity, float loadFactor) {
        entries = new Entry[capacity];
        this.loadFactor = loadFactor; }

    //create a HashTable with a certain capacity (maximum items)
    //and a default load factor of 0.75
    public HashTable(int capacity) {
        this(capacity,0.75F);} //calls the HashTable( int, float ) constructor

    //create a default HashTable with default capacity (101 items) and
    //default load factor of 0.75
    public HashTable() {
        this(101); } //calls the HashTable( int ) constructor

    //Method to find a certain key in the HashTable
    //Precondition: key != null
    //Postcondition: return the entry's value if the key is found
    //               otherwise return null for not found
    public Object get(Object key) {
        int h=hash(key);
        for (int i=0; i<entries.length; i++) {
            int j = nextProbe(h,i);
            Entry entry=entries[j];
            if (entry==null) break;
            if (entry==NIL) continue;
            if (entry.key.equals(key)) return entry.value;  // success
        }
        return null;  // failure: key not found
    }
    
    //Method to add a key/value pair to the HashTable
    //Key - the value that gets hash() called on it. Should be unique (hopefully)
    //Value - the actual data that is to be stored.
    //Precondition: key != null
    //Postcondition: return null if the object was inserted sucessfully
    //               return the value that was there if we updated a value
    //               return null if the table overflows (should never happen,
    //               but it is there just in case)
    public Object put(Object key, Object value) {
        if (used>loadFactor*entries.length) rehash();
        
        System.out.print(key); //print out the key

        int h=hash(key); //hash the key
        
        //run once for each position in the Table
        for (int i=0; i<entries.length; i++) { 
            //get the [first, next] place to try putting the value
            int j = nextProbe(h,i); 
            //get the Entry at j (where we want to put value)
            Entry entry=entries[j]; 
            
            //print the location we tried to place the value
            System.out.print(" -> " + j); 
        
            //if entry[j] == null then we have found a good place to put value!
            if (entry==null) {
        
                System.out.println(); //terminate the line
        
                entries[j] = new Entry(key,value);
                ++size;
                ++used;
                return null;  // insertion success
            }
        
            //if entry[j] == NIL then we must continue to probe for a new place,
            //otherwise we will confuse the remove() and get() methods
            if (entry==NIL) { collisions++; continue; }

            //if entry[j].key == key then we need to update the record
            if (entry.key.equals(key)) {
                Object oldValue=entry.value;
                entries[j].value = value;
                return oldValue;  // update success
            }

            //if we get here, we have had a collision that is not a NIL,
            //and is not an Entry that needs updating, therefore it is something
            //else that hashes to the same value.  Therefore we must increment the
            //collision counter
            collisions++;
        
        } //end for loop
    
    return null;  // failure: table overflow
    } //end put()

    //Method to remove a key/value pair from the table. 
    //This will leave a NIL behind
    //Precondition: key != null
    //Postcondition: return the old value if the key was found, then 
    //               put a NIL in it's place. return null if the key 
    //               was not found in the table.
    public Object remove(Object key) {
        int h=hash(key); //hash the key
        //run once for every place in the table
        for (int i=0; i<entries.length; i++) {  
            //find the [first, next] place to put the value
            int j = nextProbe(h,i);
            Entry entry=entries[j];
            
            //break out of the loop, since the key was not found
            if (entry==null) break; 
            if (entry==NIL) continue; //keep going, since we found a NIL
            if (entry.key.equals(key)) { //we found the key we are looking for...
                Object oldValue=entry.value; //save value to return
                entries[j] = NIL; //replace the value with a NIL
                --size; //decrement size
                return oldValue;  // success
            }
        }
        return null;  // failure: key not found
    }

    //Method to access private variable size
    //Precondition: none
    //Postcondition: return size
    public int size() {
        return size;
    }

    //Method to access private variable collisions
    //Precondition: none
    //Postcondition: return collisions
    public int collisions() {
        return collisions;
    }

    //Method to set the probeType variable
    //
    //0 == Linear Probing (step size 1) DEFAULT
    //1 == Prime  Probing (step size 3)
    //2 == Prime  Probing (step size 5)
    //3 == Prime  Probing (step size 7)
    //4 == Prime  Probing (step size 11)
    //5 == Quadratic Probing
    //6 == Double Hashing
    //
    //Precondition: 0 <= newValue <= 6
    //Postcondition: probeType will be changed
    //WARNING: Please do NOT change probeType while in a test run
    public void setProbeType( int newValue ) {
        //put in for safety, just in case
        if( newValue < 0 || newValue > 6 ) { newValue = 0; } 
        probeType = newValue;
    }

    //A private class which will hold a key/value pair
    private class Entry {
        Object key, value;
        Entry(Object k, Object v) { key=k; value=v; }
    }

    //our implementation of the hash() function. Takes the Object.hashCode() method
    //and chops off the sign bit
    //Precondition: None (we check for key == null)
    //Postcondition: throw an IllegalArgumentException() if the key == null
    //               return an integer hash for the given key
    private int hash(Object key) {
        if (key==null) throw new IllegalArgumentException();
        return (key.hashCode() & 0x7FFFFFFF) % entries.length;
    }
    
    //Method to implement linear probing
    //Precondition: h != null; i != null
    //Postcondition: return the next place to try and put the value
    private int nextProbe(int h, int i) {
        
    int returnVal = 0; //set to zero to stop compiler error

    switch( probeType ) {
        case 0: //linear probing
            returnVal = (h + i)%entries.length;
            break;
        case 1: //Prime Probing ( p=3  )
            returnVal = (h + (i * 3 ))%entries.length; 
            break;
        case 2: //Prime Probing ( p=5  )
            returnVal = (h + (i * 5 ))%entries.length;
            break;
        case 3: //Prime Probing ( p=7  )
            returnVal = (h + (i * 7 ))%entries.length;
            break;
        case 4: //Prime Probing ( p=11 )
            returnVal = (h + (i * 11))%entries.length;
            break;
        case 5: //Quadratic Probing
            returnVal = (h + (i * i ))%entries.length;
            break;
        //I am aware that my implementation of Double Hashing (which follows) is
        //wasteful in terms of computation time (we must create the array "step"
        //each and every time this code is run.  I did it this way to abstract the
        //method used to find the nextProbe() location from the put method.
        case 6: //Double Hashing
            int[] step = { 2,3,5,7,11 };
            int stepSize = step[h%4];
            returnVal = (h + (i * stepSize))%entries.length;
            break;
    }
    //return the final value
    return returnVal;
    
    }

    //Method to rehash the entire table.  This will get rid of NIL's
    //Precondition: none
    //Postcondition: entries will be twice the size plus one (ensures odd size)
    //               all NIL will be gone
    //               all values will be re-hash()ed
    private void rehash() {
        Entry[] oldEntries = entries;
        entries = new Entry[2*oldEntries.length+1];
        for (int k=0; k<oldEntries.length; k++) {
            Entry entry=oldEntries[k];
            if (entry==null || entry==NIL) continue;
            int h=hash(entry.key);
            for (int i=0; i<entries.length; i++) {
                int j = nextProbe(h,i);
                if (entries[j]==null) {
                    entries[j] = entry;
                    break;
                }
            }
        }
        used = size;
    }
}