2

Possible Duplicate:
Strange ruby behavior when using Hash.new([])

This is a simple one, as I'm lost for words.

Why is this happening:

1.9.3-p194 :001 > h = Hash.new([])
 => {} 
1.9.3-p194 :002 > h[:key1] << "Ruby"
 => ["Ruby"] 
1.9.3-p194 :003 > h
 => {} 
1.9.3-p194 :004 > h.keys
 => [] 
1.9.3-p194 :005 > h[:key1]
 => ["Ruby"] 
Community
  • 1
  • 1
changelog
  • 4,646
  • 4
  • 35
  • 62

5 Answers5

8

When you create a hash like this:

h = Hash.new([])       

it means, whenever the hash is accessed with a key that has not been defined yet, its going to return:

[]

Now when you do :

h[:key1] << "Ruby"

h[:key1] has returned [] , to which "Ruby" got pushed, resulting in ["Ruby"], as output, as that is the last object returned. That has also got set as the default value to return when 'h' is accessed with an undefined key. Hence, when you do :

h[:key1] or h[:key2] or h[:whatever]

You will get

"Ruby"

as output. Hope this helps.

changelog
  • 4,646
  • 4
  • 35
  • 62
mkz
  • 212
  • 2
  • 6
  • No problem man! You helped me learn too... I had to read the docs to figure out what happened. – mkz Oct 11 '12 at 12:00
4

Look at the documentation of Hash.new

new → new_hash
new(obj) → new_hash
new {|hash, key| block } → new_hash

If this hash is subsequently accessed by a key that doesn’t correspond to a hash entry, the value returned depends on the style of new used to create the hash.

  1. In the first form, the access returns nil.
  2. If obj is specified, this single object will be used for all default values.
  3. If a block is specified, it will be called with the hash object and the key, and should return the default value. It is the block’s responsibility to store the value in the hash if required.

irb(main):015:0> h[:abc] # ["Ruby"]

So ["Ruby"] is returned as default value instead of nil if key is not found.

saihgala
  • 5,724
  • 3
  • 34
  • 31
  • I just had a lightbulb moment in my head. That actually explains what I thought was wrong with this. Thank you! – changelog Oct 11 '12 at 11:43
  • Thank you for explaining this properly. The other answers just reflect my observations without actually explaining _why_. <3 – Dan Bechard Jul 25 '19 at 23:22
2

This construction Hash.new([]) returns default value but this value is not initialized value of hash. You're trying to work with hash assuming that the default value is a part of hash.

What you need is construction which will initialize the hash at some key:

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

hash[:key1] << "Ruby"

hash #=> {:key1=>["Ruby"]}
megas
  • 21,401
  • 12
  • 79
  • 130
  • This is one of those things about Ruby where I felt that it should work. Thanks for the answer. I'll accept it in 5 minutes when I'm allowed to! – changelog Oct 11 '12 at 11:39
2

You actually did not set the value with h[:keys] << "Ruby". You just add a value for the returned default array of a not found key. So no key is created.

If you write this, it will be okay:

h = Hash.new([])
h[:keys1] = []
h[:keys1] << "Ruby"
Matzi
  • 13,770
  • 4
  • 33
  • 50
1

I have to admit this tripped me out too when I read your question. I had a look at the docs and it became clear though.

If obj is specified, this single object will be used for all default values.

So what you actually doing is modifying this one single array object that is used for the default values, without ever assigning to the key!

Check it out:

h = Hash.new([])

h[:x] << 'x'
# => ['x']

h
# => {}

h[:y]
# => ['x']  # CRAZY TIMES

So you need to do assignment somehow - h[:x] += ['x'] might be the way to go.

Andrew Haines
  • 6,574
  • 21
  • 34
  • Your answer made me laugh! Am I right into thinking that this wasn't the clear behaviour tho? – changelog Oct 11 '12 at 11:47
  • Haha glad to hear it. I'd say it's clear (in that, once you read the docs, it's fairly obvious), but maybe not intuitive (because you need to read the docs to work it out!). – Andrew Haines Oct 11 '12 at 16:09