From: Robert Klemme Date: 2011-03-09T17:36:28+09:00 Subject: Re: Unexpected problem: hash[key] << value On Wed, Mar 9, 2011 at 6:35 AM, Mark Beek wrote: > I just stumbled across this surprising behavior myself. It's the first > counter-intuitive mechanism I have come across in my short sweet > experience with Ruby. > > Check this thread for an elaborate discussion (in English) of this > behavior: > > http://www.ruby-forum.com/topic/134424#new > > Here's my take: > > Before we look at your case, let's look at a case that actually works as > you'd expect: initializing a hash with a Fixnum: > > Code > h = Hash.new(0) > puts "h['key1']: #{h['key1']}" > puts "h['key2']: #{h['key2']}" > h['key1'] += 1 > puts "after updating key1" > puts "h['key1']: #{h['key1']}" > puts "h['key2']: #{h['key2']}" > > Result: > h['key1']: 0 > h['key2']: 0 > after updating key1 > h['key1']: 1 > h['key2']: 0 > > Perfect! Mighty handy for word count programs and all sorts of other use > cases. > > Which would lead you to expect the following behavior when you > initialize a hash with an empty array, then append: > > Code > h = Hash.new([]) > puts "h['key1']: #{h['key1']}" > puts "h['key2']: #{h['key2']}" > h['key1'] << 1 > puts "after updating key1" > puts "h['key1']: #{h['key1']}" > puts "h['key2']: #{h['key2']}" > > Result > h['key1']: [] > h['key2']: [] > after updating key1 > h['key1']: [1] > h['key2']: []  #<-- what you'd expect, but NOT what you get > > The actual result is the following: > > h['key1']: [] > h['key2']: [] > after updating key1 > h['key1']: [1] > h['key2']: [1] > . . . and so on > > The problem is that when you initialize a hash with a mutable default > value, all of the defaults are actually references to THE SAME OBJECT. > So when you append to the default array in one hash value, you're > actually changing them all. Witness: > > puts "#{h['key1'].object_id}" > puts "#{h['key2'].object_id}" > puts "#{h['key3'].object_id}" > > Result: > 116528 > 116528 > 116528 > > By contrast, when you update a value with the += construction rather > than <<, you're actually creating a new array object for that value. So > that particular one is no longer referring to the default value. Well, in this case actually the better idiom is this: h = Hash.new {|h,k| h[k] = []} ... h[key] << something Reason: Array#+ will create a new object every time you add something while the idiom presented above only ever creates one Array per key. > The thread referred to above mentions other ways to get what you'd > expect with a default empty array. Still, I gotta admit that I simply > don't understand why Hash.new([]) works the way it does. Who would want > to create a Hash table where changing a single value can potentially > change all other values, past, present, and to come. Talk about side > effects gone wild! Well, first of all this is the default return value. This does not necessarily mean that it will be modified. You might do something like h = Hash.new("missing".freeze) ... puts h[key] And then of course there is a very common idiom counters = Hash.new 0 ... counters[key] += 1 > If anyone can explain the rationale for this behavior,I'd really > appreciate it. I'm probably just missing something. Hopefully that explanation helps. Kind regards robert -- remember.guy do |as, often| as.you_can - without end http://blog.rubybestpractices.com/