Simple Hash Table Overview
As a refresher, a hash table is a way to store a value under a specific key in a data structure. For instance, I could store value "a"
under the key 1
, and then later retrieve it by looking up the key 1
in the hash table.
The simplest example of a hash table that I can think of off the top of my head is a hash table that can only store integers, where the key for the hash table entry is also the value being stored. Let's say your table is of size 8, and it's basically an array in memory:
---------------------------------
| | | | | | | | |
---------------------------------
0 1 2 3 4 5 6 7
Hash Function
Hash functions give you an index on where to store your value. A pretty simple hash function for this table would be to add 1 to the value you want to store, and then mod it by 8 (the table size). In other words, your hash function is (n+1)%8
, where n
is the integer you want to store.
Inserts
If you want to insert a value into this hash table, you call your hash function (in this case (n+1)%8
) on the value you want to insert to give you an index. For instance, if we want to insert 14, we would call (14 + 1) % 8
and get index 7
, so we'd insert it the value in index 7
.
---------------------------------
| | | | | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Similarly, we can insert 33, 82, and 191 like so:
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Collisions
But what happens if we try to insert something that would collide with an entry? 2 should go in index 3
, but it is taken by 82. There are multiple ways to solve this issue, the simplest is to call our hash function again and again repeatedly until we find an empty space.
So the logic is as follows:
- (2+1)%8 = 3
- Index 3 is full
- Plug 3 back into our hash function. (3 + 1) % 8 = 4, which is empty.
- Place our value into index 4.
Now the hash table looks like this, with the value 2 stored at index 4
.
---------------------------------
|191| |33 |82 |2 | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
The downside with this solution is that pretty soon, our table will get full! If you know that your data size is limited, this shouldn't be an issue as long as your table is large enough to hold all possible values. If you want to be able to hold more, you can handle collisions differently. Let's move back to where we were before inserting 2.
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
If you recall, (2+1)%8
gives us index 3
, which is taken. If you don't want your hash table to fill up, you can use each table index as a linked-list, and append to the list at that index. So instead of calling the hash function again, we'll simply append to the list at index 3
:
-----
| 2 |
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
This list can then grow as much as memory will allow. I can insert 18, and it will just be appended to 2:
-----
|18 |
-----
| 2 |
---------------------------------
|191| |33 |82 | | | |14 |
---------------------------------
0 1 2 3 4 5 6 7
Lookups
Lookup up values in your hash table is quick, given that your hash table is of a pretty large size. You simply call your hash function, and get the index. Let's say you want to see if 82 is in your table. The lookup function would call (82+1)%8
= 3
, and look at the item in index 3
, and return it for you. If you looked up 16, the lookup function would look in index 1
, and see that it does not exist.
Lookups Need to Handle Collisions, too!
If you try to look up the value 2, your hash table would have to use the same collision logic it used for storing the data as for retrieving the data. Depending on the way your hash table works, you would either hash the key over and over until you find the entry you are looking for (or find a blank space), or you would iterate through your linked list until you found the item (or got to the end of the list)
Summary
So, hash tables are a good way to store and access key-value pairs quickly. In this example we used the same key as the value, but in real world hash tables the keys aren't so limited. Hash functions will work on the keys to generate an index, and then the key/value can be stored at that index. Hash tables aren't really meant to be iterated through, although it's possible to do so. As you can see, hash tables can have lots of blank spaces, and iterating through them would be a waste of time. Even if the hash table has logic for skipping blank space lookups in its iterator, you would be better suited using a data structure that is designed for iterators, like linked lists.