Introduction
Previously, we discussed a custom hashmap implementation. That implementation has a lot of collisions because we are using a fixed-size array.
If we want to reduce the chances of collisions
, we can increase the number of slots in the hashmap, or in other words, resize
our hashmap. Resizing cannot guarantee that collisions will be entirely eliminated, but it will help reduce the likelihood of one happening.
Code Example
private func loadFactor() -> Double? {
if self.hashMap.isEmpty {
return nil
} else {
var filledSlots = 0
for slot in self.hashMap {
if slot != nil {
filledSlots += 1
}
}
return Double(filledSlots / self.hashMap.count)
}
}
private func resize() {
if self.hashMap.isEmpty {
self.hashMap = [nil]
return
}
guard
let load = self.loadFactor()
else { return }
if load < 0.05 {
return
}
let oldHashMap = self.hashMap
self.hashMap = Array(repeating: nil, count: oldHashMap.count * 10)
for kvp in oldHashMap {
if let kvp = kvp {
self.insert(key: kvp.key, value: kvp.value)
}
}
}
Implementation
When resizing, we create a new hashmap
with a larger number of slots
. Then, we re-insert
all the key-value pairs from the old hashmap
into the new
one.
The resize method requires a helper function, load factor
, which determines the number of filled buckets as a percentage of the total number of buckets.
As for the resize
algorithm, it checks if the underlying hashmap
is empty. If it is, it creates a new hashmap
with a length of 1
. It then gets the current load
, and if it’s more than 5%
, it creates a new
empty array hashmap with 10x
the size of the current one. After that, it uses the insert method to re-insert
all key-value pairs from the old
hashmap into the new
one.
The final step is to update the insert
method and perform a resize
check before inserting to ensure there is enough space.
Complete Code Example
final class HashMap<Key: StringProtocol, Value> {
private(set) var hashMap: [(key: Key, value: Value)?]
init(size: Int) {
self.hashMap = Array(repeating: nil, count: size)
}
func getValue(by key: Key) -> Value? {
let index = hashFunction(key: key)
return self.hashMap[index]?.value
}
func insert(key: Key, value: Value) {
self.resize()
let index = hashFunction(key: key)
self.hashMap[index] = (key, value)
}
private func hashFunction(key: Key) -> Int {
var count = 0
for element in key.unicodeScalars {
count += Int(element.value)
}
return count % self.hashMap.count
}
private func resize() {
if self.hashMap.isEmpty {
self.hashMap = [nil]
return
}
guard
let load = self.loadFactor()
else { return }
if load < 0.05 {
return
}
let oldHashMap = self.hashMap
self.hashMap = Array(repeating: nil, count: oldHashMap.count * 10)
for kvp in oldHashMap {
if let kvp = kvp {
self.insert(key: kvp.key, value: kvp.value)
}
}
}
private func loadFactor() -> Double? {
if self.hashMap.isEmpty {
return nil
} else {
var filledSlots = 0
for slot in self.hashMap {
if slot != nil {
filledSlots += 1
}
}
return Double(filledSlots / self.hashMap.count)
}
}
}