The O(n) Club: Design HashMap—Trust Issues, Buckets, and Chaos
⚡ TL;DR
Building your own HashMap in Java: for when Copy-Paste isn’t soul-nourishing enough. Brute force? If keys are tiny and tightly packed (say, 0 to 1,000,000), just use a big array. For the rest of us mere mortals, welcome to the world of buckets and chasing collisions. Java snippet (direct addressing):
// Keys: 0 to 1,000,000 glint[] map = new int[1000001]; map[key] = value; // put return map[key]; // get // remove? map[key] = -1; // or another 'not found' marker
🧠 How Devs Usually Mess This Up
Here’s the classic: slap a LinkedList[] inside an array, call it a day, and forget collisions exist. Or worse: design a hash function so bad, everything lands in bucket zero—congrats, you made a very slow list. If you “remove” by setting a value to zero, I’m sorry, but the bugs will haunt you at night. Others trust memory limits… until their app eats up all the RAM and their laptop reboots faster than they can say ‘OutOfMemoryError.’
🔍 What’s Actually Going On
Picture a set of mail baskets (buckets) in a row. Every key gets a basket by some secret rule (the hash). Most of the time, they’re happy and separate. But sometimes, two keys get stuck in the same basket—that’s a collision, and now everyone’s playing nice in a little list. Separate chaining to the rescue! Direct addressing is like buying a million baskets for three letters—practical only if you own Amazon.
🛠️ PseudoCode
- Setup: Array of lists. Each element is a bucket for combo (key, value) pairs.
- Hash Function:
int idx = key % bucketCount;// good enough for interviews, don’t use in cryptography. - To put(key, value):
- Hash key. Find right bucket.
- If key exists, update value. Else, add (key, value) pair.
- To get(key):
- Hash, search bucket for key. Return value, or -1 for ‘lost in translation.’
- To remove(key):
- Hash, bucket, find key, delete. No ghosts, no drama.
💻 The Code
class MyHashMap {
static class Node {
int key, value;
Node(int k, int v) { key = k; value = v; }
}
final int SIZE = 2333; // Prime keeps collisions on their toes
List<Node>[] buckets;
public MyHashMap() {
buckets = new List[SIZE];
for (int i = 0; i < SIZE; i++) buckets[i] = new LinkedList<>();
}
private int hash(int key) { return (key % SIZE + SIZE) % SIZE; }
public void put(int key, int value) {
int idx = hash(key);
for (Node node : buckets[idx]) {
if (node.key == key) { node.value = value; return; }
}
buckets[idx].add(new Node(key, value));
}
public int get(int key) {
int idx = hash(key);
for (Node node : buckets[idx]) {
if (node.key == key) return node.value;
}
return -1;
}
public void remove(int key) {
int idx = hash(key);
Iterator<Node> it = buckets[idx].iterator();
while (it.hasNext()) {
if (it.next().key == key) { it.remove(); return; }
}
}
}
⚠️ Pitfalls, Traps & Runtime Smacks
- Collisions can pile up. Don’t use a bad hash function, or you’re basically writing a slow linked list simulator.
- Negative keys? That
key % SIZEtrick goes negative fast. Wrap it with(key % SIZE + SIZE) % SIZE. You’re welcome. - No memory for direct addressing. Arrays the size of Jupiter are a bad flex unless your keys are tiny integers.
- Not actually removing. Setting to zero or some ‘marker’ won’t cut it—get that key out of the bucket.
- Time/space: O(1) expected, until your hash table turns into a soup of collisions and you’re debugging at 2 AM.
🧠 Interviewer Brain-Tattoo
“A hash map is just an array with trust issues and a list of uninvited guests in every bucket.”