Code-Memo

Hashmaps

A hashmap (or hash table) is a data structure that implements an associative array abstract data type, a structure that can map keys to values. Hashmaps are efficient for both lookup and insertion operations.

Overview

Key Concepts

  1. Key-Value Pairs: Each entry in a hashmap consists of a key and a value.
  2. Hash Function: A function that maps a key to an index in the array. The hash function should distribute keys uniformly across the array.
  3. Collision Handling: Since multiple keys can hash to the same index, a good collision resolution strategy is necessary.

Common Operations

  1. Insertion: Add a key-value pair to the hashmap.
  2. Deletion: Remove a key-value pair from the hashmap.
  3. Lookup: Retrieve the value associated with a given key.
  4. Update: Modify the value associated with a given key.

Time Complexity

Hashmaps in Python

In Python, the built-in dictionary (dict) type is an implementation of a hashmap.

Creating a Dictionary

# Creating an empty dictionary
my_dict = {}

# Creating a dictionary with initial values
my_dict = {
    "apple": 1,
    "banana": 2,
    "cherry": 3
}

Basic Operations

Insertion

# Adding a new key-value pair
my_dict["date"] = 4

Deletion

# Removing a key-value pair
del my_dict["banana"]

Lookup

# Accessing the value associated with a key
value = my_dict["apple"]

Update

# Updating the value associated with a key
my_dict["cherry"] = 5

Methods

Python dictionaries come with several useful methods:

Collision Handling

Chaining

Chaining is a common technique to handle collisions. Each array index points to a linked list of key-value pairs that hash to the same index.

Open Addressing

Open addressing handles collisions by finding another open slot within the array. This can be done using techniques like linear probing, quadratic probing, or double hashing.

Example: Implementing a Simple Hashmap

Here is a basic implementation of a hashmap using chaining in Python:

class HashMap:
    def __init__(self, size=10):
        self.size = size
        self.table = [[] for _ in range(size)]

    def _hash(self, key):
        return hash(key) % self.size

    def insert(self, key, value):
        hash_key = self._hash(key)
        key_exists = False
        bucket = self.table[hash_key]
        for i, kv in enumerate(bucket):
            k, v = kv
            if key == k:
                key_exists = True
                break
        if key_exists:
            bucket[i] = (key, value)
        else:
            bucket.append((key, value))

    def delete(self, key):
        hash_key = self._hash(key)
        bucket = self.table[hash_key]
        for i, kv in enumerate(bucket):
            k, v = kv
            if key == k:
                del bucket[i]
                break

    def lookup(self, key):
        hash_key = self._hash(key)
        bucket = self.table[hash_key]
        for k, v in bucket:
            if key == k:
                return v
        return None

Usage

hash_map = HashMap()
hash_map.insert("apple", 1)
hash_map.insert("banana", 2)
print(hash_map.lookup("apple"))  # Output: 1
hash_map.delete("banana")
print(hash_map.lookup("banana"))  # Output: None