Can anyone explain the following? Our "intuitive" solution (h1) to
creating a constructor for a hash nested within a hash did not work. It
was only after attempting a workaround (h3) and finding that it
performed very poorly that we Googled (h2) a correct solution. But it
is not obvious to us why this more complex constructor is required.
# Unexpected result, h1 hash is empty.
h1=Hash.new(Hash.new(0)) # => {}
h1[1][2]='h1' # => "h1"
h1 # => {}
# Expected result, h2 hash contains nested hash.
h2=Hash.new{|hash,key| hash[key]=Hash.new(0)} # => {}
h2[1][2]='h2' # => "h2"
h2 # => {1=>{2=>"h2"}}
# Expected result but expensive.
h3=Hash.new(Hash.new(0)) # => {}
temp=h3[1].dup # => {}
temp[2]='h3' # => "h3"
h3[1]=temp # => {2=>"h3"}
h3 # => {1=>{2=>"h3"}}
Thanks,
Ben Armstrong
Hash.new, when passed a block, stores the block as a sort of
"key_missing" callback, passing it the hash itself and the key. The
callback doesn't modify the hash, though, it just returns a virtual
value. However, because the block does have access to the hash, it can
call a method on that hash that updates it, hence the second
constructor you tried.
Compare:
irb(main):001:0> h = Hash.new {|h, k| 2*k}
=> {}
irb(main):002:0> h[1]
=> 2
irb(main):003:0> h[2]
=> 4
irb(main):004:0> h
=> {}
irb(main):005:0> h1 = Hash.new {|h, k| h[k] = 2*k}
=> {}
irb(main):006:0> h1[1]
=> 2
irb(main):007:0> h1[2]
=> 4
irb(main):008:0> h1
=> {1=>2, 2=>4}
And even something more complicated like:
irb(main):009:0> h2 = Hash.new {|h, k| h[k] = 2*k; "missing, filling it
in"}
=> {}
irb(main):010:0> h2[1]
=> "missing, filling it in"
irb(main):011:0> h2[1]
=> 2
martin
martin
···
Ben Armstrong <BArmstrong@dymaxion.ca> wrote:
Can anyone explain the following? Our "intuitive" solution (h1) to
creating a constructor for a hash nested within a hash did not work. It
was only after attempting a workaround (h3) and finding that it
performed very poorly that we Googled (h2) a correct solution. But it
is not obvious to us why this more complex constructor is required.
Can anyone explain the following? Our "intuitive" solution (h1) to
creating a constructor for a hash nested within a hash did not work. It
was only after attempting a workaround (h3) and finding that it
performed very poorly that we Googled (h2) a correct solution. But it
is not obvious to us why this more complex constructor is required.
# Unexpected result, h1 hash is empty.
h1=Hash.new(Hash.new(0)) # => {}
h1[1][2]='h1' # => "h1"
h1 # => {}
When you pass an object to the Hash.new constructor, that object is
used as the default value for keys that don't have values. But that
object isn't really "in" the hashtable data structure, it is just
associated with the object (i.e. a member variable):
irb(main):001:0> h1=Hash.new(Hash.new(0))
=> {}
irb(main):002:0> h1[0].object_id
=> 22753180
irb(main):003:0> h1[1].object_id
=> 22753180
irb(main):004:0> h1[0][0]
=> 0
irb(main):005:0> h1[0][0]='foo'
=> "foo"
irb(main):006:0> h1[0][0]
=> "foo"
irb(main):007:0> h1
=> {}
# Expected result, h2 hash contains nested hash.
h2=Hash.new{|hash,key| hash[key]=Hash.new(0)} # => {}
h2[1][2]='h2' # => "h2"
h2 # => {1=>{2=>"h2"}}
Others have described why this works and is the proper solution.
# Expected result but expensive.
h3=Hash.new(Hash.new(0)) # => {}
temp=h3[1].dup # => {}
temp[2]='h3' # => "h3"
h3[1]=temp # => {2=>"h3"}
h3 # => {1=>{2=>"h3"}}
This is expensive because you are pulling out the default value from
the hashtable and duplicating it on each key operation then actually
putting it into the hashtable data structure. As you know now from the
above this is not the proper way to work with hashtables with
dynamically created values.
Ryan
···
On 11/9/05, Ben Armstrong <BArmstrong@dymaxion.ca> wrote:
Check 1.8.4 - I think this has made it into the core
martin
···
David A. Black <dblack@wobblini.net> wrote:
Or my favorite:
h = Hash.new {|h,k| raise "No such key: #{k}" }