
// Hash Table using separate chaining

public class HashTableSC<K,V> implements Dictionary<K,V>
{
    // Entry contains next to chain into a list
    private class Entry<K,V>
    {
        private K key;			// Key
	private V value;		// Value
	private Entry<K,V> next;	// Chain !

	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 HashTableSC(int M)
    {
	bucket = (Entry<K,V>[]) new Entry[M]; // Create a hash table of size M 
				         // Note: can't use "new Entry<K,V>[M]"

        capacity = bucket.length;             // Capacity of this hash table
	NItems = 0;		         // # items in hash table

        // Initialize parameters for the hash function
        MAD_p = 109345121;               // We pick this prime number...
        MAD_a = 123;			 // a = non-zero random number
        MAD_b = 456;                     // b = random number
    }

    /* ===============================================================
       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 ] % M
      =============================================================== */
    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(key): find the Entry containing key in hash table

	Returns Entry object containing key 
	Returns null if not found
       ------------------------------------------------------ */
    public Entry<K,V> findEntry(K k)
    {
        int hashIdx = hashValue(k);	// Get hash index using key k

        Entry<K,V> curr = bucket[hashIdx];	// h = head/first of linked list

        while (curr != null)
        {
	    if ( curr.key.equals(k) )
		return curr;
	    curr = curr.next;
	}

        return null;
    }

    /* ------------------------------------------------------
       put(k,v): if key k found: update corresponding value
		 otherwise: insert (k,v)
       ------------------------------------------------------ */
    public void put(K k, V v)
    {
	int  hashIdx = hashValue(k);

	System.out.println(k + " --> bucket: " + hashIdx);
	Entry<K,V> h = findEntry(k);

	if ( h != null )
	    h.value = v;	// Update value
	else
	{
	    // Add newEntry as first element in list at bucket[hashIdx]

	    Entry newEntry = new Entry(k, v);
	    newEntry.next = bucket[hashIdx];
	    bucket[hashIdx] = newEntry;
	    NItems++;
	}
    }

    /* ------------------------------------------------------
       get(k): if key k found, return corresponding value
		otherwise: return null
       ------------------------------------------------------ */
    public V get(K k)
    {
	// This is not necessary, but enlightening to show student the hashIdx
        int  hashIdx = hashValue(k);
        System.out.println(k + " --> bucket: " + hashIdx);

	// The get() method starts here....
        Entry<K,V> h = findEntry(k);

        if ( h != null )
            return h.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)
    {
        int  hashIdx = hashValue(k);

	// Base case 1: empty bucket
	if ( bucket[hashIdx] == null )
	    return null;

	// Base case 2: remove first elem (update bucket[hashIdx] 
	if ( bucket[hashIdx].key.equals(k) )
	{
	    V ret = bucket[hashIdx].value;
	    bucket[hashIdx] =  bucket[hashIdx].next;
	    NItems--;
	    return ret;
	}

	// General case: search in list
        Entry<K,V> previous = bucket[hashIdx];
        Entry<K,V> current  = bucket[hashIdx];

        while (current != null && !current.key.equals(k) )
        {
	    previous = current;
            current = current.next;
        }

	if ( current != null )
	{
	    previous.next = current.next; // Unlink current
            NItems--;			  // Accounting...
            return current.value;	  // Return value
	}
        return null;		// Not found
    }



    // Return string representing item in hash bucket e
    public String toString(Entry e)
    {
	String out = "";

	while ( e != null )
        {
	    out = out + " " + e.toString();
	    e = e.next;
        }

	return out;
    }

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

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

	return s;
    }
}

