nested hashes with default value 17. Jun 2008

To instantiate arbitrarily nested hashes in Ruby you can write

h = Hash.new{|hash,key| hash[key] = Hash.new(&h.default_proc)}

This let’s you do

h[1] = 2 # => 2
h[2][1] = 3 # => 3
h[3][1][1] = 4 # => 4
h.inspect # => {1=>2, 2=>{1=>3}, 3=>{1=>{1=>4}}}

That’s nice, but maybe you want your innermost hash to have a default value to access the keys via +=-operator without having to check if the key has been initialized before? You might be tempted to write the following.

h = Hash.new(Hash.new(0))

Looks nice. Now let’s assign a value and check if it’s stored correctly.

h[1][1] = 2 # => 2
h[2][2] = 3 # => 3

Looks fine too. Now let’s inspect the hash.

h.inspect # => {}

Whoops! Where have my keys gone? Let’s examine the inner hashes.

h[1].object_id # => 1751410
h[2].object_id # => 1751410

Okay, so obviously instead of instantiating new hashes for each key the same hash was used. Let’s examine the default value.

h.default.object_id # => 1751410

Ah, so the keys were stored in the default value hash. Let’s check and see.

h.default # => {1=>2, 2=>3}

So instead of generating a nested structure for each key all keys are stored flatly in the innermost hash. That is not what we wanted. We can however talk the hash into doing the intented by using a default procedure instead of a default value, which is of course documentated in the lovely core API documentation.

h = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = 0}}

While working fine in Ruby 1.9, this causes problems in in Ruby 1.8 because block arguments are not local there.

h = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = 0}} # => {}
h[1][1] = 1 # => 1
h.inspect # {1=>{1=>1}}
h[2][1] # => 0
h.inspect # => {1=>0}

To get it working in Ruby 1.8 the variables in each block have to be uniquely named.

h = Hash.new {|h,k| h[k] = Hash.new {|h1,k1| h1[k1] = 0}} # => {}
h[1][1] = 1 # => 1
h.inspect # {1=>{1=>1}}
h[2][1] # => 0
h.inspect # {1=>{1=>1}, 2=>{1=>0}}

Now we can fill the twodimensional hash with data and see the result.

h = Hash.new {|h,k| h[k] = Hash.new {|h1,k1| h1[k1] = 0}} # => {}
h[1][1] += 1 # => 1
h[1][2] += 2 # => 2
h[2][3] += 3 # => 3
h.inspect # => "{1=>{1=>1, 2=>2}, 2=>{3=>3}}"
h[1][3] # => 0
h.inspect # => {1=>{1=>3, 2=>4, 3=>0}, 2=>{1=>0, 3=>6}}

Et voilà!

 

Kommentar schreiben

Markdown Syntax