TIL: Hash Edition
I was reviewing some code for a co-worker today and learned something
new about Hash
. My co-worker was using a ruby hash not as a
dictionary but as a O(1) look up collection (also known as a hash
table). Because of this there were a few places in his code that
looked something like this:
h = {}
def add_the_keys(keys_to_add)
keys_to_add.each do |k|
h[k] = 0
end
end
Whenever I see the same value being used for all the keys in a hash I think “use a hash with a default value”. I dutiful rewrote the code to look this like:
h = Hash.new(0)
def add_the_keys(keys_to_add)
keys_to_add.each do |k|
h[k]
end
end
And then the tests started failing. Out of curiousity I tried this instead:
h = Hash.new { |h, k| h[k] = 0 }
def add_the_keys(keys_to_add)
keys_to_add.each do |k|
h[k]
end
end
And the tests passed again. What the heck? So I went into irb and tried a couple things: kxx
h1 = {}
h1[:a] = 0 # 0
h2[:b] # nil
h1.keys # [:a]
h2 = Hash.new(0)
h2[:a] = 0 # 0
h2[:b] # 0
h2.keys # [:a]
h3 = Hash.new { |h, k| h[k] = 0 }
h3[:a] = 0 # 0
h3[:b] # 0
h3.keys # [:a, :b]
Moral of the story: Keys are not added to a hash unless there is an
assigment or there is a default_proc. Also, if you want a collection
with O(1 ) key access use Set
. It is a hash under the hood but using
Set
reveals your intent.
P.S. For extra fun try this:
h4 = Hash.new { |h, k| h[k] }
h4[:a] = 0 # 0
h4[:b] # hang