From: Rob Biedenharn Date: 2009-03-12T01:12:59+09:00 Subject: Re: hash of hashes w/defaults doesn't add keys as expected On Mar 11, 2009, at 11:52 AM, Jesús Gabriel y Galán wrote: > 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] Jesus's answer made me realize the true behavior (I was scratching my head on this one a bit). Perhaps this will cement it: hash_hash[:notfoo].keys It gets the same default object for the missing :notfoo key as it got for the missing :foo. -Rob >> >> 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. > Rob Biedenharn http://agileconsultingllc.com Rob@AgileConsultingLLC.com