
// Hash Table using ** Open Addressing **

public class HashTableOA<K,V> implements Dictionary<K,V>
{
    // Entry in the hash table using open addressing (no link field)
    private class Entry<K,V>
    {
        K key;			// Key
	V value;		// Value

	public Entry(K k, V v)	// Constructor
	{
	    key = k;
	    value = v;
	}

	public String toString()
	{
	    return "(" + key + "," + value + ")";
	}
    }

    public Entry<K,V>[] bucket;	// Hash table
    public int capacity;        // capacity == bucket.length
    int    NItems;		// # items in hash table

    // MAD formula: ((a * HashCode + b) % p ) % M
    public int MAD_p;		// Prime number in the Multiply Add Divide alg
    public int MAD_a;		// Multiplier   in the Multiply Add Divide alg
    public int MAD_b;		// Offset       in the Multiply Add Divide alg

    public int index_for_key;	// = ((a * HashCode(key) + b) % p ) % M

    public Entry AVAILABLE = new Entry(null, null);  // Special entry to
                                                     // mark deleted entries

    public HashTableOA(int M)
    {
	bucket = (Entry<K,V>[]) new Entry[M]; // Create a hash table of size M 
				              // Can't use "new Entry<K,V>[M]" !
        capacity = bucket.length;	      // Capacity of this hash table
	NItems = 0;		              // # items in hash table

	MAD_p = 109345121;	 	      // We pick this prime number...
	MAD_a = 123;
	MAD_b = 456;
    }

    /* ===============================================================
       Hash function for the hash table:

          1. uses the Hash Code from Java
    	  2. uses the MAD compression function on the hash code

                hash index = [ (a*HashCode + b) % p ] % N
      =============================================================== */
    public int hashValue(K key) 
    {
        int x = key.hashCode();      // key has a built-in hashCode() !!!

        return ((Math.abs(x*MAD_a + MAD_b) % MAD_p) % capacity);
    }

    /* ******************************************************************
       Hash table methods
       ****************************************************************** */
    public int size()
    {
       return( NItems );
    }

    public boolean isEmpty()
    {
       return( NItems == 0 );
    }

    /* ---------------------------------------------------------------
       findEntry(k): find bucket that contains key k

       If k is found:  returns index of bucket for key k
       If k not found: returns -1 AND remembers first available entry
       --------------------------------------------------------------- */
    int firstAvailEntry;
    int lastIndex;

    public int findEntry(K k)
    {
        int hashIdx = hashValue(k);     // Get hash index using key k
	int start = hashIdx;		// Remember the start index

	firstAvailEntry = -1;		// First Available entry not found
	lastIndex = -1;			// Last index is not valid

	do
	{
	    Entry<K,V> e;

	    e = bucket[hashIdx];

	    if ( e == null )
	    {
	 	lastIndex = hashIdx;	// Remember last index checked
		return -1;		// Not found
	    }

	    if ( e != AVAILABLE && e.key.equals(k) ) 
		return hashIdx;		// Found

	    if ( e == AVAILABLE && firstAvailEntry == -1 ) 
		firstAvailEntry = hashIdx;	// Remember the first avail 

	    // Slot is occupied by an entry that is not equal to k 
	    // Do linear search
	    hashIdx = (hashIdx+1)%capacity;

	} while (hashIdx != start);

	// If we exit while loop with (hashIdx == start), key is not found
	return -1;
    }


    /* ------------------------------------------------------
       put(k,v): if key k found: update corresponding value
		 otherwise: insert (k,v)
       ------------------------------------------------------ */
    public void put(K k, V v)
    {
	// Dictatic: good to show student the hash index
	int hashIdx = hashValue(k);
	System.out.println(k + " --> bucket: " + hashIdx);

	int buckIdx = findEntry(k);

	if ( buckIdx >= 0 )	         // Key k was found in bucket buckIdx
	    bucket[buckIdx].value = v;	 // Update value
	else if ( firstAvailEntry >= 0 ) // k not found, AVAIL was found 
	{
	    bucket[firstAvailEntry] = new Entry(k, v);	// Enter (k,v) there
	    NItems++;
	}
	else if ( lastIndex >= 0 )	 // k not found, lastIndex valid
	{
	    bucket[lastIndex] = new Entry(k, v);	// Enter (k,v) there
	    NItems++;
	}
	else
	    System.out.println("Hash table full: (" 
				+ k + "," + v + ") not inserted");
    }

    /* ------------------------------------------------------
       get(k): if key k found, return corresponding value
		otherwise: return null
       ------------------------------------------------------ */
    public V get(K k)
    {
	// Dictatic: good to show student the hash index
	int hashIdx = hashValue(k);
	System.out.println(k + " --> bucket: " + hashIdx);

	int buckIdx = findEntry(k);

	if ( buckIdx >= 0 )	         // Key k was found in bucket buckIdx
	    return bucket[buckIdx].value;
	else 
	    return null;
    }

    /* ----------------------------------------------------------------
       remove(k): if key k found, remove Entry with k and return value
                  otherwise: do nothing and return null
       ------------------------------------------------------ */
    public V remove(K k)
    {
        // Dictatic: good to show student the hash index
        int hashIdx = hashValue(k);
        System.out.println(k + " --> bucket: " + hashIdx);

        int buckIdx = findEntry(k);

        if ( buckIdx >= 0 )              // Key k was found in bucket buckIdx
	{
	    V ret = bucket[buckIdx].value;
            bucket[buckIdx] = AVAILABLE;
	    NItems--;
	    return ret;
	}
        else
            return null;
    }



    // Return string representing item in hash bucket e
    public String toString(Entry e)
    {
	if ( e == null )
	    return null;
	else if ( e == AVAILABLE )
	    return "AVAILABLE";
	else
	    return e.toString();
    }

    public String toString()
    {
        String s = "";

	for (int i = 0; i < bucket.length; i++)
	    s = s + i + ": " + toString(bucket[i]) + "\n";

	return s;
    }
}

