Can't use an array as default values for Ruby

2019-04-22 21:47发布

问题:

This question already has an answer here:

  • Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([]) 4 answers

I was adding items to a Hash key. I was expecting to get a structure like this:

{ 
  'a' : [1],
  'b' : [2, 3, 4]
}

I used an Array to initialize the Hash.

irb> hash = Hash.new([])
 => {} 

Then started using it:

irb> hash['a'] << 1
 => [1] 
irb> hash['b'] << 2
 => [1, 2] 

But it turns out:

irb> hash
 => {}

回答1:

Try the following instead:

hash = Hash.new{|h, k| h[k] = []}
hash['a'] << 1 # => [1]
hash['b'] << 2 # => [2]

The reason you got your unexpected results is that you specified an empty array as default value, but the same array is used; no copy is done. The right way is to initialize the value with a new empty array, as in my code.



回答2:

The constructor you used stores [] as the default value to return when accessing keys that are unknown. Since Array#<< modifies its receiver in place this initially empty array grows.

To explain in more detail:

When you do hash['a'] << 1 this is what happens:

  1. hash looks to see if there is a key named 'a'
  2. It finds that no, there is no such key.
  3. It looks if it has stored a default value to return.
  4. Since you constructed it with Hash.new([]) it does have such a value, [] and it returns that.
  5. Now [] << 1 is evaluated and this means that hash now stores [1] as the value to return when a previously unencountered key is requested.

If what you want is to store the key value pair instead use the third form of the constructor with a block:

hash = Hash.new{|h, key| h[key] = []}


回答3:

hash['a'] << 1 and hash['b'] << 2 isn't correct syntax for creating a key/value pair. You have to use = for that:

hash['a'] = []
hash['a'] << 1

hash['b'] = []
hash['b'] << 2

That should give you the hash {'a' : [1], 'b' : [2]}



回答4:

This is exactly the behavior that you would expect to see.

You never add anything to the Hash, therefore the Hash is completely empty. When you look up a key, that key will never exist, therefore it returns the default value, which you have specified to be an Array.

So, you look up the key 'a', which doesn't exist, and thus returns the Array you specified as the default value. Then, you call << on that Array, which appends a value (1) to it.

Next, you look up the key 'b', which also doesn't exist, and thus returns the Array you specified as the default value, which now contains the element 1 you added earlier. Then, you call << on that Array, appending the value 2 to it.

You end up with a Hash that is still empty, since you never added anything to it. The default value of the Hash is now an array containing the values 1 and 2.

The output you are seeing is because IRb always prints the result of the last expression that was evaluated. The last expression in your example is calling << on the Array. << returns its receiver, which then is the return value of the entire expression and thus what IRb prints out.



标签: ruby hash