This post covers Chapter 5 of Grokking Algorithms, which introduces hash tables.
Hash Table
A hash table is a data structure that provides quick access to key-value pairs. It does this by using a hash function to determine where in memory a particular value is stored.
Since each key in a hash table must be unique, a key can predictably be translated into a memory offset.
Hash Function
This…
In ordinary English, a hash is a mixed up mess. For example, "hash
browns" are fried mashed potato.
In computing, a hash got its name by analogy with "mixed up". A hash is
short for "hash table".
Through this article, we are going to take a look at the data structure called Hash Table.
Hash Table
A Hash Table is a data structure that stores data in an associative manner. It is made up of two parts: an array, where the data is stored, and a Hash Function which is a mapping function. Basically, a Hash Function is a function that takes things from one space and maps them to a space for indexing.
A Hash Table is used to implement structures such as dictionary, map, or associative array and it is a data structure in which insertion and search operations are very fast.
The idea behind a Hash Table is that for each element we want to store, we calculate a unique address and we put the value at this index in the array. When we need to find a value, we once again calculate its index and then return the value. In other words, a Hash Table allows us to store and retrieve objects by key.
A Hash Table is an array and initially, this array is empty. When we want to put a value into the Hash Table under a certain key, that key will be used to compute an index in the array. We can see it like so:
Here, the key is "firstName" and it maps to index 2. It means that our Hash Table asked for the index of the key "firstName" and the Hash Function returned 2. We can see it like so:
key ---> HASH FUNCTION --> array_index
Hash Function
Hashing is a technique that lets us convert a range of key values into a range of indexes of an array. The values returned by a Hash Function are called Hash Values. There are multiple ways for constructing a Hash Function but it is important that the function has some basic requirements:
The Hash Value is fully determined by the data being hashed and it should be easy to compute
The Hash Function uses all the input data
The Hash Function should provide a uniform distribution across the Hash Table
The Hash Function generates very different Hash Values for similar strings
Solving collisions
Let's say that our Hash Function will compute the index of a specific string by summing the ASCII values of the characters modulo 373 (that is a prime number). Our set of string will be {"abcdef", "bcdefa", "cdefab" , "defabc"}. Knowing that the ASCII values of a, b, c, d, e, and f are 97, 98, 99, 100, 101, and 102, our addition will always give us the same result (597) just like the operation 597%373.
When different elements get the same Hash Value, it is a called a collision. So, how can we handle such a situation? The two most popular ways to solve this are called Chaining and Open Addressing.
Chaining
When we use this method, we just use a Linked List and put all the values with the same Hash Value into it and so the Hash Table becomes an array of Linked Lists.
Open Addressing
Also called Linear Probing, when we use this method and we want to add a new entry, the Hash Index of the Hashed Value is computed and then the array is examined. If the slot is empty, then the value is inserted. If not, we calculate another Hash Value and check that slot and we continue until a place for the value is found. The probe sequence will be like so:
index = index % hashTableSize index = (index + 1) % hashTableSize index = (index + 2) % hashTableSize index = (index + 3) % hashTableSize ...
Quadratic Probing
Quadratic Probing is similar to Linear Probing but the difference is the interval between successive probes. With Quadratic Probing, the sequence would be something like this:
index = index % hashTableSize index = (index + 1^2) % hashTableSize index = (index + 2^2) % hashTableSize index = (index + 3^2) % hashTableSize ...
Double Hashing
Double Hashing works on a similar idea to Linear and Quadratic Probing. The difference is that a second Hash Function is used to determine the location of the next empty slot. The sequence would look like this:
index = (h1(key) + 1 * h2(key)) % hashTableSize; index = (h1(key) + 2 * h2(key)) % hashTableSize; index = (h1(key) + 3 * h2(key)) % hashTableSize; ...
Basic Examples
Knowing what we know, it is time to make really simple examples using Java.
Linear Probing
First, let's define a class for the Entry:
public class HashEntry { private int key; private int value; HashEntry(int key, int value) { this.key = key; this.value = value; } public int getKey() { return key; } public int getValue() { return value; } }
And now, let's create our Hash Table:
public class HashTable { private final static int TABLE_SIZE = 64; HashEntry[] table; HashTable() { table = new HashEntry[TABLE_SIZE]; for (int i = 0; i < TABLE_SIZE; i++) { table[i] = null; } } public int get(int key) { int hash = (key % TABLE_SIZE); while (table[hash] != null && table[hash].getKey() != key) { hash = (hash + 1) % TABLE_SIZE; } if (table[hash] == null) { return -1; } else { return table[hash].getValue(); } } public void put(int key, int value) { int hash = (key % TABLE_SIZE); while (table[hash] != null && table[hash].getKey() != key) { hash = (hash + 1) % TABLE_SIZE; } table[hash] = new HashEntry(key, value); } }
Chaining
First, we define the Nodes of our Linked List.
public class HashNode { private int key; private int value; private HashNode next; HashNode(int key, int value) { this.key = key; this.value = value; this.next = null; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } public int getKey() { return key; } public HashNode getNext() { return next; } public void setNext(HashNode next) { this.next = next; } }
Now, let's create our Hash Table:
public class HashTable { private final static int TABLE_SIZE = 64; HashNode[] table; HashTable() { table = new HashNode[TABLE_SIZE]; for (int i = 0; i < TABLE_SIZE; i++) { table[i] = null; } } public int get(int key) { int hash = (key % TABLE_SIZE); if (table[hash] == null) { return -1; } else { HashNode entry = table[hash]; while (entry != null && entry.getKey() != key) { entry = entry.getNext(); } if (entry == null) { return -1; } else { return entry.getValue(); } } } public void put(int key, int value) { int hash = (key % TABLE_SIZE); if (table[hash] == null) { table[hash] = new HashNode(key, value); } else { HashNode entry = table[hash]; while (entry.getNext() != null && entry.getKey() != key) { entry = entry.getNext(); } if (entry.getKey() == key) { entry.setValue(value); } else { entry.setNext(new HashNode(key, value)); } } } }
Conclusion
Through this article, we saw what are Hash Tables, how they work and what problem we could have when we use them. It is also good to know that there are many existing well-tested Hash Functions that we can rely on.
Learning Data Structures in JavaScript from Scratch - $10 Course
Learning Data Structures in JavaScript from Scratch – $10 Course
Data structures allow you to improve the efficiency, performance, speed, and scalability of your code/programs/applications. The Udemy course “Learning Data Structures in JavaScript from Scratch” teaches data structures (linked lists, binary search trees, hash tables) from the ground up. You will learn what data structures are, why they are important, and how to code them out in JavaScript. You…
Learning Data Structures in JavaScript from Scratch - $10 Course
Learning Data Structures in JavaScript from Scratch – $10 Course
Data structures allow you to improve the efficiency, performance, speed, and scalability of your code/programs/applications. The Udemy course “Learning Data Structures in JavaScript from Scratch” teaches data structures (linked lists, binary search trees, hash tables) from the ground up. You will learn what data structures are, why they are important, and how to code them out in JavaScript. You…
Learning Data Structures in JavaScript from Scratch - $10 Course
Learning Data Structures in JavaScript from Scratch – $10 Course
Data structures allow you to improve the efficiency, performance, speed, and scalability of your code/programs/applications. The Udemy course “Learning Data Structures in JavaScript from Scratch” teaches data structures (linked lists, binary search trees, hash tables) from the ground up. You will learn what data structures are, why they are important, and how to code them out in JavaScript. You…
My style of coding is essentially less waste and tighter code. This is why I prefer my opening brackets on the same line! :) I prefer code that doesn’t take up space on the page unnecessarily but maintains the same level of readability. One of those examples is the simple if/then construct. If/then constructs have their place but only for more complicated scenarios. Let me give you an example.