From: "Jesús Gabriel y Galán" Date: 2009-03-12T00:52:20+09:00 Subject: Re: hash of hashes w/defaults doesn't add keys as expected On Wed, Mar 11, 2009 at 4:33 PM, rpardee@gmail.com wrote: > Hey All, > > This surprised me: > >  hash_hash = Hash.new(Hash.new(0)) > >  hash_hash[:foo][:bar] += 1 >  hash_hash[:foo][:baz] += 1 > >  puts(hash_hash[:foo].keys.inspect)  # => [:baz, :bar] >  puts(hash_hash.keys.inspect)        # => []  ? > > Shouldn't hash_hash.keys == [:foo]?  I'm running ruby 1.8.6 > (2007-03-13 patchlevel 0) [i386-mswin32]. > The idiom you are using to construct the hash specifies what the hash *returns* when accesing a non-existing key. It doesn't set that value for that key in the hash: irb(main):001:0> a = Hash.new(0) => {} irb(main):002:0> a[2] => 0 irb(main):003:0> a => {} You example: hash_hash[:foo][:bar] += 1 Could be split in several parts, so that you better understand what's going on: hash_hash[:foo] => this returns the default object (which is a hash), but doesn't create an entry in the hash. For simplicity in the example, let's call this object default_object. Your code is equivalent then to: default_object[:bar] = default_object[:bar] + 1 (because of the +=). The right hand side default_object[:bar] returns 0, which is the default object of the default_object. Plus 1 makes 1, and this is assigned to the key :bar in the default object. That's why you see :bar => 1 when you access a non-existing key in the hash_hash, because you have modified the default object. The hash_hash is not modified with any extra entry, though. > If this is expected behavior, what's the easiest/best performing way > to make sure those keys are added? You want the block form of Hash.new: irb(main):009:0> hash_hash = Hash.new {|h,k| h[k] = Hash.new(0)} => {} irb(main):010:0> hash_hash[:foo][:bar] += 1 => 1 irb(main):011:0> hash_hash => {:foo=>{:bar=>1}} If you want an infinitely nested hash, this is a neat trick: irb(main):012:0> nested_hash = Hash.new {|h,k| h[k] = Hash.new &h.default_proc} => {} irb(main):013:0> nested_hash[:a][:b][:c][:d] = 1 => 1 irb(main):014:0> nested_hash => {:a=>{:b=>{:c=>{:d=>1}}}} Hope this helps, Jesus.