Skip to content

Instantly share code, notes, and snippets.

@pjb3
Last active December 15, 2015 15:09
Show Gist options
  • Save pjb3/5279741 to your computer and use it in GitHub Desktop.
Save pjb3/5279741 to your computer and use it in GitHub Desktop.
Default values in Hashes
# The inner Hash.new(0) will now be the object that returned
# when you try to fetch the value for a key that doesn't exist
>> h = Hash.new(Hash.new(0))
=> {}
# When you do this, you are mutating the object that is the default value
# of the Hash and not changing the Hash itself
>> h[:foo][10] += 1
=> 1
# The previous statement you can think of more logically like this:
>> h.default[10] += 1
=> {10=>1}
# In other words, give me a reference to the object that is the default value
# for the Hash, and fetch that value of the key 10 in that Hash
# (which is not the original outer Hash), increment it, and assign it
# back to the key of 10 in that Hash.
# The point being that the same single object that was created when you first
# called Hash.new(0) is returned for every missing key,
# not a new Hash.new(0) for each call to a missing key
# To fix that problem, you want is to use the block form of Hash.new,
# which means return the result of evaluating this block each time you
# try to fetch the value for a key that doesn't exist,
# which will generate a new Hash.new(0) each time
>> h = Hash.new { Hash.new(0) }
=> {}
>> h[:foo][10] += 1
=> 1
>> h[:foo][10] += 1
=> 1
# But the problem now is that it generates a new Hash.new(0) each time,
# you mutate it, but that reference isn't assigned to anything,
# so you want really want is this:
>> h[:foo] = { 10 => (h[:foo][10] += 1) }
=> {10=>1}
>> h[:foo] = { 10 => (h[:foo][10] += 1) }
=> {10=>2}
>> h[:bar] = { 10 => (h[:bar][10] += 1) }
=> {10=>1}
# But that's too much typing and duplication, so you can just do
# the assignment in the block when you create the Hash in the first place:
>> hash = Hash.new {|h,k|
puts "no value for #{k.inspect} in #{h.inspect} yet"
h[k] = Hash.new(0) }
=> {}
# In this block, h is the Hash and k is the key you tried to access
# that didn't exist in the hash. You create a new Hash with a default value
# of 0 and assign that to the key k in the Hash h.
# I added a puts statement in there so you can see when the block is called
# On the first call, there is no value for :foo,
# so the block is called, which assigns a value to :foo
>> hash[:foo][10] += 1
no value for :foo in {} yet
=> 1
# Now there is a value for :foo, so that value is returned
# and you are then just mutating that object, the default block is not called
>> hash[:foo][10] += 1
=> 2
# When you use a different key, the block is called again
>> hash[:bar][10] += 1
no value for :bar in {:foo=>{10=>2}} yet
=> 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment