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
=> {}
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.
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:
hash
looks to see if there is a key named 'a'
- It finds that no, there is no such key.
- It looks if it has stored a default value to return.
- Since you constructed it with
Hash.new([])
it does have such a value, []
and it returns that.
- 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] = []}
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]}
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.