HashMap Implementation from Scratch in DSA Rust - Time & Space Complexity
When building a HashMap from scratch, it's important to know how fast it can add and find items.
We want to understand how the time to do these actions grows as we add more items.
Analyze the time complexity of the following code snippet.
struct HashMap {
buckets: Vec>,
}
impl HashMap {
fn insert(&mut self, key: String, value: String) {
let index = self.hash(&key) % self.buckets.len();
self.buckets[index].push((key, value));
}
fn hash(&self, key: &String) -> usize {
key.bytes().fold(0, |acc, b| acc.wrapping_mul(31).wrapping_add(b as usize))
}
}
This code shows a simple HashMap with buckets as vectors of key-value pairs. It inserts by hashing the key and adding to the bucket.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Inserting into a bucket vector and searching that bucket when needed.
- How many times: The insert operation hashes once, then adds to a bucket which may require scanning that bucket if searching.
As more items are added, the number of buckets stays fixed unless resized, so buckets get longer.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 1 to 2 steps per insert/search (small bucket size) |
| 100 | About 10 steps per insert/search if buckets not resized (bucket size grows) |
| 1000 | About 100 steps per insert/search if buckets not resized (long buckets) |
Pattern observation: Without resizing, operations grow roughly proportional to how many items share a bucket, which grows as input grows.
Time Complexity: O(n)
This means in the worst case, inserting or searching can take time proportional to the number of items if many keys collide in the same bucket.
[X] Wrong: "HashMap operations always take constant time no matter what."
[OK] Correct: If many keys end up in the same bucket, operations slow down because the bucket must be searched linearly.
Understanding how hashing and bucket size affect speed shows you know the trade-offs behind HashMaps, a key skill in coding interviews.
"What if we added automatic resizing and rehashing when buckets get too full? How would the time complexity change?"
Practice
HashMap in data structures?Solution
Step 1: Understand HashMap functionality
A HashMap stores data as key-value pairs, allowing quick access by key.Step 2: Compare with other data structures
Unlike stacks or queues, HashMaps do not maintain order but focus on fast lookup.Final Answer:
To store key-value pairs for fast lookup -> Option CQuick Check:
HashMap = key-value fast access [OK]
- Confusing HashMap with sorted structures
- Thinking HashMap stores elements in order
- Mixing HashMap with stack or queue behavior
HashMap from scratch?Solution
Step 1: Recall Rust HashMap creation syntax
Rust usesHashMap::new()to create an empty HashMap.Step 2: Check other options for Rust correctness
Options B, C, and D are not valid Rust syntax for HashMap creation.Final Answer:
let map = HashMap::new(); -> Option DQuick Check:
Rust HashMap empty = HashMap::new() [OK]
- Using constructor syntax like new HashMap()
- Calling HashMap() without ::new()
- Using non-existent methods like empty()
let mut map = MyHashMap::new();
map.insert("apple", 3);
map.insert("banana", 5);
map.insert("apple", 7);
println!("{:?}", map.get("apple"));
What will be printed?Solution
Step 1: Analyze insertions for key "apple"
First insert sets "apple" to 3, second insert updates "apple" to 7.Step 2: Check the get method output
Getting "apple" returns the latest value, which is 7, wrapped in Some.Final Answer:
Some(7) -> Option BQuick Check:
Last insert value = get output [OK]
- Assuming first inserted value remains
- Confusing None with Some value
- Mixing values of different keys
fn insert(&mut self, key: K, value: V) {
let index = self.hash(&key) % self.buckets.len();
if self.buckets[index].is_empty() {
self.buckets[index].push((key, value));
} else {
self.buckets[index].push((key, value));
}
}Solution
Step 1: Review insert logic for existing keys
The code always pushes new (key, value) pairs without checking for existing keys.Step 2: Identify consequence of missing update
This causes duplicate keys in the same bucket, breaking expected HashMap behavior.Final Answer:
It does not update value if key exists, causing duplicates -> Option AQuick Check:
Insert must update existing key, not just push [OK]
- Ignoring key existence before insert
- Assuming push replaces existing entries
- Missing bucket resizing logic
Solution
Step 1: Understand separate chaining collision handling
Separate chaining uses a list (vector) at each bucket to store multiple key-value pairs.Step 2: Efficient insert requires checking existing keys
Before inserting, search the bucket's vector for the key to update value or append if not found.Step 3: Evaluate options
Store key-value pairs in a vector at each bucket; on insert, search vector for key to update or append correctly describes this approach; others ignore key checks or hashing.Final Answer:
Store key-value pairs in a vector at each bucket; on insert, search vector for key to update or append -> Option AQuick Check:
Separate chaining + key check = efficient insert [OK]
- Ignoring key existence in bucket
- Not using hashing for bucket selection
- Replacing bucket data blindly
Ready to prove it?
Pass the test and this pattern moves unknown
