Review

  • We started by studying the dictionary data structure that store (key, value) pairs

  • We defined the Dictionary interface

      public interface Dictionary<K,V>
      {
          public void put(K k, V v);
          public V get(K k);  
          public V remove(K k);  
      }

  • We implemented the Dictionary interface using a array

  • We discovered that:

    • Searching in a dictionary is O(n) when entries are unsorted

    • Searching in a dictionary is O(log(n)) when entries are sorted

  • We learned about hashing that can speedup the array operations by relating a key k to an array index using a hash value

We will now implement the dictionary data structure using the hashing technique... a.k.a.: a hash table

The Entry class for a hash table using Separate Chaining

  • Previously, we used the following Entry<K,V> class in the ArrayMap<K,V>:

    public class Entry<K,V>
    {
        private K key;     // Key
        private V value;   // Value
    
    
        public Entry(K k, V v)  // Constructor
        {
            key = k;
            value = v;
        }
    
        ... // Methods omitted for brevity
    }
    

  • We have used this Entry<sK,V> class to implement the ArrayMap dictionary data structure

  • In order to support separate chaining (where each bucket is organized as a linked list):

    • The Entry<K,V> objects must contain a linking field

The Entry class for a hash table using Separate Chaining

  • The modified Entry<K,V> class used in a hash table with Separate Chaining :

    public class Entry<K,V>
    {
        private K key;     // Key
        private V value;   // Value
        private Entry<K,V> next; // Link to create a linked list
    
        public Entry(K k, V v)  // Constructor
        {
            key = k;
            value = v;
        }
    
        ... // Methods omitted for brevity
    }
    

    The next variable is needed in Separate Chaining to link Entry objects into a linked list

The instance variables used to implement the HashTableSP (Separate Chaining) class

  • The Hash Table using Separate Chaining consists of an array of Entry<K,V> objects:

    public class HashTableSC<K,V> implements Dictionary<K,V>
    {
        // Entry contains next to chain into a linked list
        private class Entry<K,V>
        {
            K key;                  // Key
            V value;                // Value
            Entry<K,V> next;        // Chain !
    
            public Entry(K k, V v)  // Constructor
            {
                key = k;
                value = v;
            }
            .... // Other stuff omitted for brevity
        }
    
        public Entry<K,V>[] bucket; // The 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
    
        .... // Other stuff omitted for brevity
    }
    

The instance variables used to implement the HashTableSP (Separate Chaining) class

  • We also need some variables to implement the MAD compression function in hashing:

    public class HashTableSC<K,V> implements Dictionary<K,V>
    {
        // Entry contains next to chain into a linked list
        private class Entry<K,V>
        {
            K key;                  // Key
            V value;                // Value
            Entry<K,V> next;        // Chain !
    
            public Entry(K k, V v)  // Constructor
            {
                key = k;
                value = v;
            }
            .... // Other stuff omitted for brevity
        }
    
        public Entry<K,V>[] bucket; // The Hash table
        public int capacity;        // capacity == bucket.length
        int    NItems;              // # items in hash table
    
        // MAD formula: ( Math.abs(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
    
        .... // Other stuff omitted for brevity
    }
    

The constructor used to initialize a HashTableSP object

  • Now we must define a constructor to initialize the instance variables:

    public class HashTableSC<K,V> implements Dictionary<K,V>
    {
        ... // Other stuff omitted for brevity
    
        public Entry<K,V>[] bucket; // The Hash table
        public int capacity;        // capacity == bucket.length
        int    NItems;              // # items in hash table
    
        // MAD formula: ( Math.abs(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)   // Create a hash table of size M
        {
            bucket = (Entry[]) new Entry[M]; // Create a hash table of size 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;                     // a = non-zero random number
            MAD_b = 456;                     // b = random number
        }
        ....
    }
    

The constructor used to initialize a HashTableSP object

  • We first instantiate the array used to store the entries of the dictionary:

    public class HashTableSC<K,V> implements Dictionary<K,V>
    {
        ... // Other stuff omitted for brevity
    
        public Entry<K,V>[] bucket; // The Hash table
        public int capacity;        // capacity == bucket.length
        int    NItems;              // # items in hash table
    
        // MAD formula: ( Math.abs(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)   // Create a hash table of size M
        {
            bucket = (Entry[]) new Entry[M]; // Create a hash table of size 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;                     // a = non-zero random number
            MAD_b = 456;                     // b = random number
        }
        ....
    }
    

The constructor used to initialize a HashTableSP object

  • Amd then initialize the variables for the MAD compression function:

    public class HashTableSC<K,V> implements Dictionary<K,V>
    {
        ... // Other stuff omitted for brevity
    
        public Entry<K,V>[] bucket; // The Hash table
        public int capacity;        // capacity == bucket.length
        int    NItems;              // # items in hash table
    
        // MAD formula: ( Math.abs(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)   // Create a hash table of size M
        {
            bucket = (Entry[]) new Entry[M]; // Create a hash table of size 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;                     // a = non-zero random number
            MAD_b = 456;                     // b = random number
        }
        ....
    }
    

The hash function in hashing

  • Recall that we use the following hash function:

        H(k) = H2( H1( k ) )
    
     where:
    
        H1( k ) = the hash code of the key k
        H2( x ) = the MAD compression function
    

  • The definition of the hash function hashValue(k) is therefore:

        /* ==================================================
           The hash function for the hash table
           ================================================== */
        public int hashValue(K key)
        {
            int x = key.hashCode(); // Uses Object.hashCode()
    
            return ((Math.abs(x*MAD_a+MAD_b)%MAD_p)%capacity);
        }
    

Now we are ready to implement put( ), get( ) and remove( )... but first - a help method...

The help method findEntry(k)

  • We learned from the implementation of ArrayMap that:

    • Many operations on a Dictionary require search for the entry containing some key

      E.g.: put( ) and get( ) are very similar in ArrayMap !!

  • Therefore, we introduce a helper method to find the dictionary entry that contains a specific key:

       public Entry<K,V> findEntry(K k):
    
          return the reference to the Entry contain key k if found
          return null if key k is not found in the dictionary
    

The help method findEntry(k)

  • The helper method findEntry(k):

        /* ----------------------------------------------------------
           findEntry(key): find the Entry containing key in hash table
    
            Returns Entry object containing key if found
            Returns null if not found
           ------------------------------------------------------ */
        public Entry findEntry(K k)
        {
            int hashIdx = hashValue(k);     // Get hash index using key k
    
            Entry h = bucket[hashIdx]; // h = head/first of linked list      
    
            while (h != null)
            {
                if ( h.key.equals(k) )
                    return h;
                h = h.next;
            }
    
            return null;
        }

The help method findEntry(k)

  • Find the bucket that contains the key k:

        /* ----------------------------------------------------------
           findEntry(key): find the Entry containing key in hash table
    
            Returns Entry object containing key if found
            Returns null if not found
           ------------------------------------------------------ */
        public Entry findEntry(K k)
        {
            int hashIdx = hashValue(k);     // Get hash index using key k
    
            Entry curr = bucket[hashIdx]; // curr = head/first of linked list
    
            while (curr != null)
            {
                if ( curr.key.equals(k) )
                    return curr;
                curr = curr.next;
            }
    
            return null;
        }

The help method findEntry(k)

  • Start the list traversal algorithm on the linked list starting at bucket[hashIdx]:

        /* ----------------------------------------------------------
           findEntry(key): find the Entry containing key in hash table
    
            Returns Entry object containing key if found
            Returns null if not found
           ------------------------------------------------------ */
        public Entry findEntry(K k)
        {
            int hashIdx = hashValue(k);     // Get hash index using key k
    
            Entry<K,V> curr = bucket[hashIdx]; // curr = first of linked list
    
            while (curr != null)
            {
                if ( curr.key.equals(k) )
                    return curr;
                curr = curr.next;
            }
    
            return null;
        }

The help method findEntry(k)

  • This is just the classic list traversal algorithm used to find a "node" (entry) containing k:

        /* ----------------------------------------------------------
           findEntry(key): find the Entry containing key in hash table
    
            Returns Entry object containing key if found
            Returns null if not found
           ------------------------------------------------------ */
        public Entry findEntry(K k)
        {
            int hashIdx = hashValue(k);     // Get hash index using key k
    
            Entry<K,V> curr = bucket[hashIdx]; // curr = first of linked list
    
            while (curr != null)
            {
                if ( curr.key.equals(k) )
                    return curr;
                curr = curr.next;
            }
    
            return null;   // Not found
        }

The put(k, v) method of the hash table using Separate Chaining

  • put(k,v): insert/update the entry (k,v) in the hash table

        public void put(K k, V v)
        {
            int  hashIdx = hashValue(k);
    
            Entry 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++;
    
    
            }
        }
    

The put(k, v) method of the hash table using Separate Chaining

  • Find the entry (k,v) using the helper function:

        public void put(K k, V v)
        {
            int  hashIdx = hashValue(k);
    
            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++;
    
    
            }
        }
    

The put(k, v) method of the hash table using Separate Chaining

  • If found, update the value in the entry:

        public void put(K k, V v)
        {
            int  hashIdx = hashValue(k);
    
            Entry<K,V> h = findEntry(k);
    
            if ( h != null )
                h.value = v;        // Update value with v
            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++;
    
    
            }
        }
    

The put(k, v) method of the hash table using Separate Chaining

  • If not found, insert a new (k,v) as the first node in bucket bucket[hashIdx]:

        public void put(K k, V v)
        {
            int  hashIdx = hashValue(k);
    
            Entry<K,V> h = findEntry(k);
    
            if ( h != null )
                h.value = v;        // Update value with v
            else
            {
                // Add newEntry as first element in list at bucket[hashIdx]
    
                Entry newEntry = new Entry(k, v); // Make new entry
    
                newEntry.next = bucket[hashIdx];  // Point to first in bucket
                bucket[hashIdx] = newEntry;       // Make new entry as first
    
                NItems++;
            }
        }
    

  • Note: this is the addFirst( ) algorithm that you have learned in linked list !

    The variable bucket[hashIdx] represents the first variable of a linked list

The get(k) method of the hash table using Separate Chaining

  • get(k): retrieve the value v associated with the key k from the hash table:

        public V get(K k)
        {
    
            Entry<K,V> h = findEntry(k);
    
            if ( h != null )
                return h.value;
            else
                return null;
        }
    

     

     

The get(k) method of the hash table using Separate Chaining

  • Find the entry using the key k:

        public V get(K k)
        {
    
            Entry<K,V> h = findEntry(k);
    
            if ( h != null )
                return h.value;
            else
                return null;
        }
    

     

     

The get(k) method of the hash table using Separate Chaining

  • If the entry is found, return the corresponding value, otherwise return null:

        public V get(K k)
        {
    
            Entry<K,V> h = findEntry(k);
    
            if ( h != null )
                return h.value;
            else
                return null;
        }
    

     

     

The remove(k) method of the hash table using Separate Chaining

  • remove(k): remove the entry (k,v) from the hash table:

        public V remove(K k)
        {
    
            int  hashIdx = hashValue(k);
    
            // General case: search in list
            Entry previous = bucket[hashIdx];
            Entry current  = bucket[hashIdx];
    
            while (current != null)
            {
                if ( current.key.equals(k) )
                {
                    previous.next = current.next;   // Unlink current
                    NItems--;
                    return current.value;           // Return value
                }
    
                previous = current;
                current = current.next;
            }
    
            return null;
        }
    

The remove(k) method of the hash table using Separate Chaining

  • Find the hash bucket for the key k:

        public V remove(K k)
        {
    
            int  hashIdx = hashValue(k);
    
            // General case delete from linked list
            Entry previous = bucket[hashIdx];
            Entry current  = bucket[hashIdx];
    
            while (current != null)
            {
                if ( current.key.equals(k) )
                {
                    previous.next = current.next;   // Unlink current
                    NItems--;
                    return current.value;           // Return value
                }
    
                previous = current;
                current = current.next;
            }
    
            return null;
        }
    

The remove(k) method of the hash table using Separate Chaining

  • Start a list traversal at the first element in the linked list:

        public V remove(K k)
        {
    
            int  hashIdx = hashValue(k);
    
            // General case delete from linked list
            Entry<K,V> previous = bucket[hashIdx];
            Entry<K,V> current  = bucket[hashIdx];
    
            while (current != null)
            {
                if ( current.key.equals(k) )
                {
                    previous.next = current.next;   // Unlink current
                    NItems--;
                    return current.value;           // Return value
                }
    
                previous = current;
                current = current.next;
            }
    
            return null;
        }
    

The remove(k) method of the hash table using Separate Chaining

  • Find the node containing the key k while remembering the previous node:

        public V remove(K k)
        {
    
            int  hashIdx = hashValue(k);
    
            // General case delete from linked 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
        }
    

The remove(k) method of the hash table using Separate Chaining

  • If found, unlink the node and return its value:

        public V remove(K k)
        {
    
            int  hashIdx = hashValue(k);
    
            // General case delete from linked 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
        }
    

The remove(k) method of the hash table using Separate Chaining

  • Otherwise, return null:

        public V remove(K k)
        {
    
            int  hashIdx = hashValue(k);
    
            // General case delete from linked 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
        }
    

Demo: put( ) and get( )

    public static void main(String[] args)
    {
       Dictionary<String,String> H = new HashTableSC<>(5);

       H.put("ice", "cold");
       H.put("fire", "hot");
       H.put("rock", "hard");
       H.put("wool", "soft");
       H.put("sun", "hot");
       H.put("sun", "**bright**");      // Updates the value !
       H.put("moon", "shine");

       System.out.println("\n**** Test get(): ****");
       System.out.println("ice:" + H.get("ice"));
       System.out.println("fire:" + H.get("fire"));
    }

DEMO: 15-hashing/10-separate-chaining/Demo.java

Demo: remove( )

    public static void main(String[] args)
    {
       Dictionary<String,String> H = new HashTableSC<>(5);

       System.out.println(H);

       H.put("ice", "cold");
       H.put("fire", "hot");
       H.put("rock", "hard");
       H.put("wool", "soft");
       H.put("sun", "hot");

       System.out.println("\nInitial hash table:");
       System.out.print(H);
       System.out.println("#Items = " + H.size() + "\n\n");


       System.out.println("\nTest remove():");
       System.out.println("-- ice:" + H.remove("ice"));
       System.out.print(H);
       System.out.println("#Items = " + H.size() + "\n");
       System.out.println("-- fire:" + H.remove("fire"));
       System.out.print(H);
       System.out.println("#Items = " + H.size() + "\n");
       System.out.println("-- sun:" + H.remove("sun"));
       System.out.print(H);
       System.out.println("#Items = " + H.size() + "\n");
    }

DEMO: 15-hashing/10-separate-chaining/Demo2.java