From: Rick DeNatale Date: 2007-05-23T21:49:45+09:00 Subject: Re: problem appending to arrays in a hash of arrays On 5/22/07, Chris Carter wrote: > On 5/22/07, Mike Steiner wrote: > > I'm trying to create a hash with each element being an array (initially > > empty), and I've tried a few variations to get what I want. Why don't the > > last 2 lines work? > > > > h = Hash.new ( Array.new ) > > h["abc"] = h["abc"] + [ "foo" ] # this works > > h["abc"] << "foo" # this doesn't > > h["abc"].push ( "foo" ) # this doesn't either > > > > (Note: the above code isn't the actual code I'm using, but a simplified > > version.) > > > >> g = Hash.new {|h,k| h[k]=[]} > => {} > >> g["abc"] << :foo > => [:foo] > >> g > => {"abc"=>[:foo]} That's a fish for Mike, here's a little fishing lesson. Mike, you didn't say how it didn't work, there are actually two related problems. The Hash creation technique: Hash.new(expression) creates a hash which returns the value of expression as the default value when a key doesn't exist in the hash. Normally hashes return nil for an unknown key. The expression is evaluated before calling the new method, so in your case h = Hash.new(Array.new) creates an empty array for the default value, this instance of array will be returned for any key which isn't in the hash. Now: 1: h[:a] += [:c] # this replaces h[:a] 2: h[:b] << :d # this appends :d to what is returned by h[:b] 3: h[:b] # returns [:d] which looks like it worked BUT 4: h[:c] # also returns [:d] ?????? What happened is that the hash simply returns that default value, which is the same object every time. That's problem #1, line 2 actually changes the state of the default value. Now: 5: h[:d] << :f # returns [:d, :f] and so does 6: h[:b] # ??? and 7: h[:c] # ??? This is problem two, accessing the hash via a non-existent key doesn't change the hash, it simply returns the default value. 8. h.inspect # returns "{:a=>[:c]}" Now Chris's solution uses Hash.new without a parameter but with a block h = Hash.new {|h, k| h[k] = []} which makes a hash which, if accessed with an unknown key, evaluates the block passing the hash itself, and the key as parameters, and returns the value of the block. Chris's block actually stores an empty array in the hash at that key. And since the block is evaluated at the time the hash is accessed rather than when it is created, each key will get it's own instance of an empty array. Which solves both problems. Note that the block given to Hash.new can do whatever it wants to with the key and hash. Chris's block stored the value, which is generally done, but it doesn't have to. For example if the Hash were created with h = Hash.new {[]} we'd get a hash which returned a newly instantiated empty array for an unknown key, but didn't store it. This wouldn't work for what Mike's trying to do: h[:a] << :b # returns [:b] but then h[:a] # returns [] This solves problem #1, but not problem #2. This technique might be useful in other situations though. -- Rick DeNatale My blog on Ruby http://talklikeaduck.denhaven2.com/