Welcome to this lesson that aims to introduce Hash Tables and Hash Maps, a fundamental concept in data structures and algorithms. Hash tables, also known as Hash Maps
in JavaScript, are extremely useful constructs that can drastically reduce time complexity while solving certain types of algorithmic problems. They provide an efficient way to maintain a collection of key-value pairs and allow quick access, insertion, and removal operations, making them highly effective in situations where quick lookups are necessary.
A key component of a hash map is the hashing function, which generates a hash value (usually an integer) from a given key. This hash value is used to determine where to store the associated value in the hash table. For integer keys, the hashing function simply returns the integer modulo the size of the hash table. However, keys can also be floats, strings, or other immutable types.
Hash maps usually have a limited space, defined by the initial size of the hash table. While ideally, the hashing function should be a one-to-one function (injective), in practice it never is due to limited space and the infinite nature of potential input values. This results in collisions, where two different keys generate the same hash value. Hash maps typically handle collisions through various methods like chaining (using linked lists) or open addressing (linear probing, quadratic probing, etc.).
Have a look at the code snippets below, showcasing a simple example of hashing function implementations for integers, floats, and strings.
JavaScript1// For integer keys, the hashing function returns the integer modulo the size of the hash table. 2function hashFunction(key, tableSize) { 3 return key % tableSize; 4} 5 6// Hashing functions for float keys can convert them to integers. 7function hashFloatKey(key, tableSize) { 8 return Math.floor(key * 1000) % tableSize; // example 9} 10 11// Strings can be hashed by converting each character to its character code. 12function hashStringKey(key, tableSize) { 13 let hash = 0; 14 for (let i = 0; i < key.length; i++) { 15 hash += key.charCodeAt(i); 16 } 17 return hash % tableSize; 18}
In a Hash Map
, data is stored based on a generated hash value from a unique key. That way, we can access data quickly by merely knowing the key. For example, if we have a list of integers and a target number, finding two numbers in the list that sum to the target using a brute force method would require us to compare each number with all the other numbers — a process that would have a quadratic time complexity. However, by using a hash map, we can bypass this by storing each number with its index as it arrives and simultaneously checking if the complement (target minus the current number) is already in the hash map. This method drastically reduces computational overhead, making it much faster.
Here is what the solution might look like:
JavaScript1function twoSum(nums, target) { 2 let hashMap = {}; 3 for (let i = 0; i < nums.length; i++) { 4 let num = nums[i]; 5 if (hashMap[target - num] !== undefined) { 6 return [hashMap[target - num], i]; 7 } 8 hashMap[num] = i; 9 } 10 return []; 11} 12 13// Test 14console.log(twoSum([2, 7, 11, 15], 9)); // Output: [0, 1]
Now that we've sketched a basic understanding of Hash Tables/Maps, in the upcoming exercises, we'll dive deeper into the topic. We will practice implementing Hash Maps/Tables
in JavaScript and solving complex problems faster with this highly efficient data structure. It's quite a powerful tool to have in your algorithmic toolkit, and mastering it will significantly improve your problem-solving skills.