
// Hash Table using ** Open Addressing ** with Linear Probing
//
// **** Simplified version:  NO Delete yet ****

public class HashTableLinProbe<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 HashTableLinProbe(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 );
    }


    /* ------------------------------------------------------
       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 i = hashIdx;

       do
       {
           if ( bucket[i] == null ) // Is bucket empty ?
	   {
	       bucket[i] = new Entry(k,v); // Insert (k,v);
	       NItems++;
	       return;		           // DONE !
           }
	   else if (bucket[i].key == k ) // Does bucket contains key k ?
	   {
	       bucket[i].value = v;
	       return;		   // DONE !
           }

	   i = (i + 1)%capacity;  // Check in next hash table bucket

       } while ( i != hashIdx ); // All entries searched !

       System.out.println("**** Full ****");
    }

    /* ------------------------------------------------------
       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 i = hashIdx;

        do
        {
            if ( bucket[i] == null ) // Is bucket empty ?
            {
                return null;	   // Key k is not in hash table
            }
            else if (bucket[i].key == k ) // Does bucket contains key k ?
            {
                return bucket[i].value;  // FOUND - DONE !
            }

            i = (i + 1)%capacity;  // Check in next hash table bucket

        } while ( i != hashIdx ); // All entries searched !

        return null;  // No found
    }

    public V remove(K k)
    {
	return null;	// Need to define remove() to implement Dictionary<K,V>
    }

    // Return string representing item in hash bucket e
    public String toString(Entry e)
    {
	if ( e == null )
	    return null;
	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;
    }
}

