What is a Hash Table?
Hash table is a data structure specialized for reading random elements, fast.
Each element in a table is called an entry which is a pair of key and value.
-----------
| entry 1 | ---> {key: value}
-----------
| entry 2 |
-----------
| ... |
-----------
In order to insert an entry, we need to use hash functions to produce the hash code of a key
you’re going to insert. Since hash code is always an integer, we can use this value as an index and store the value
.
hash_code = hash_function('key')
table[hash_code] = 'value'
# ... some code
hash_code = hash_function('key')
puts table[hash_code] # 'value'
As you can see, I used a string for my key. A key could be of any type in a hash table including the object. Same key will produce the same hash code. We can then use it to access the key and retrieve or update the value.
Below is the implementation of an open-addressed hash table with double hashing.
class HashTable
class Slot
attr_accessor :key, :value, :vacated
def initialize key, value, vacated=false
self.key = key
self.value = value
self.vacated = vacated
end
def free
self.value = nil
self.vacated = true
end
end
PRIMES = [13, 31, 61, 127, 251, 509]
MAX_REBUILDS = 6 # Utmost equal to PRIMES.count
attr_accessor :size, :slots
def initialize
self.slots = 5
self.size = 0
@rebuilds = 0
@h1 = -> (k) { k % self.slots }
@h2 = -> (k) { 1 + (k % (self.slots - 1)) }
fill_table self.slots
end
# idiomatic way to get an entry
def [](key)
get(key)
end
def []=(key, value)
upsert(key, value)
end
def delete key
find_slot(key)&.free
end
def print
i = 1
@table.each do |e|
if e
puts "#{i} -> #{e.key}: #{e.value}"
else
puts "#{i}. empty"
end
i += 1
end
end
private
def get key
find_slot(key)&.value
end
def double_hash hashcode, idx
h1 = @h1.call(hashcode)
h2 = @h2.call(hashcode)
((h1 + (idx * h2)) % self.slots).abs()
end
def fill_table slots
@table = [nil] * slots
end
def rebuild
raise "Too many entries." if @rebuilds >= MAX_REBUILDS
old = @table
self.slots = PRIMES[@rebuilds]
self.size = 0
fill_table self.slots
old.each do |e|
upsert e.key, e.value if e
end
@rebuilds += 1
end
def upsert key, value
rebuild if self.size > (self.slots / 2)
if (slot = find_slot(key))
slot.value = value
return
end
0.upto(self.slots - 1) do |i|
index = double_hash key.hash, i
slot = @table[index]
if slot.nil? || slot.vacated
@table[index] = Slot.new key, value
self.size += 1
return
end
end
raise "Weak hash function"
end
def find_slot key
0.upto(self.slots - 1) do |i|
index = double_hash key.hash, i
slot = @table[index]
return nil if slot.nil? || slot.vacated
return slot if slot.key == key
end
nil
end
end