Dictionary operations (INSERT, SEARCH and DELETE) are required by many applications. One very effective data structures for implementing dictionaries is hash table. The average time to search for an element in hash table is O(1).

Hash table ia unordered symbol table where key is interpreted as an array index and value associated with key ‘i’ is stored in array entry ‘i’.

hashtable.png

Two main parts in hash algorithm are hash function and collision resolution method.

HASH FUNCTION

There are 3 primary requirements for implementing a good hash function:

  • it should be consistent - equal keys must produce the same hash value.
  • it should be efficient to complete.
  • it should uniformly distribute the keys.

We can check consistency and distribution with a simple test:

    @Test
    void shouldHashKey() {
        HashTable<String, Integer> hashTable = new HashTable<>(7);
        int christianCode = hashTable.hash("Christian");
        assertEquals(0, christianCode);

        int davidCode = hashTable.hash("David");
        assertEquals(2, davidCode);

        int aliceCode = hashTable.hash("Alice");
        assertEquals(4, aliceCode);

        int tomCode = hashTable.hash("Tom");
        assertEquals(6, tomCode);
        
    }
    protected int hash(Key key) {
        int h = key.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12) ^ (h >>> 7) ^ (h >>> 4);
        return h & (tableSize - 1);
    }

COLLISION RESOLUTION

Collision resolution handles the case when two or more keys to be inserted hash to the same index. Straightforward strategy for collision resolution is separate chaining. For each index in array there is a data structure like Linked List or a Tree Map and keys from this data structure are hashed to this index.

hashtable_sep_chain.png

Implementation of this kind of hash table is not too complicated. So in test cases we need to check that:

  • inserted value is present
  • deleted value is absent
    @Test
    void insertedValueShouldBePresent() {
        HashTable<String, Integer> hashTable = new HashTable<>(5);
        hashTable.put("Alice", 123654);

        assertEquals(123654, hashTable.get("Alice"));
    }

    @Test
    void removedValueShouldBeAbsent() {
        HashTable<String, Integer> hashTable = new HashTable<>(5);
        hashTable.put("Alice", 123654);
        hashTable.put("David", 456123);

        hashTable.delete("Alice");
        assertNull(hashTable.get("Alice"));
    }
    public void put(Key key, Value value) {
        if (value == null) {
            delete(key);
            return;
        }
        if (keyValuePairsNumber >= 10 * tableSize) resize(2 * tableSize);

        int i = hash(key);
        if (!chains[i].containsKey(key)) keyValuePairsNumber++;
        chains[i].put(key, value);
    }

    public Value get(Key key) {
        int i = hash(key);
        return chains[i].get(key);
    }

    public void delete(Key key) {
        int i = hash(key);
        if (chains[i].containsKey(key)) keyValuePairsNumber--;
        chains[i].remove(key);

        if (keyValuePairsNumber <= 2 * tableSize) resize(tableSize / 2);
    }

Complete implementation with test cases can be found on my GitHub.

Sources:

  1. Algorithms by Robert Sedgewick.
  2. Introduction to Algorithms by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford Stein.