-1

Here's an example with push:

@connections = Hash.new []
@connections[1] = @connections[1].push(2)
puts @connections # => {1=>[2]}

Here's an example with <<

@connections = Hash.new []
@connections[1] << 2
puts @connections # => {}

For some reason the output (@connections) is different, but why? I'm guessing it has something to do with Ruby object model?

Perhaps the new hash object [] is being create each time, but not saved? But why?

Sergey
  • 47,222
  • 25
  • 87
  • 129
  • 1
    Unfortunately, it doesn't answer my question. The fact that push accepts more than one argument doesn't explain the behavior above. – Sergey Sep 07 '14 at 16:46
  • 2
    In your first code example, you didn't just replace push() with << did you? You might have also tried the following in your second example: `@connections[1].push(2)` and then asked: "What's the difference between push() and push()?" – 7stud Sep 07 '14 at 16:59

2 Answers2

10

The difference in your code isn't about << vs. push, it's about the fact that you re-assign in one case and don't in the other. The following two pieces of code are equivalent:

@connections = Hash.new []
@connections[1] = @connections[1].push(2)
puts @connections # => {1=>[2]}

@connections = Hash.new []
@connections[1] = (@connections[1] << 2)
puts @connections # => {1=>[2]}

As are these two:

@connections = Hash.new []
@connections[1].push(2)
puts @connections # => {}

@connections = Hash.new []
@connections[1] << 2
puts @connections # => {}

The reason that re-assignment makes a difference here is that accessing a default value, does not automatically add an entry for it to the hash. That is if you have h = Hash.new(0) and then you do p h[0], you'll print 0, but the value of h will still be {} (not {0 => 0}) because the 0 is not added to the hash. If you do h[0] += 1, this will call the []= method on the hash and actually add an entry for 0 to it, so h becomes {0 => 1}.

So when you do @connections[1] << 2 in your code, you get the default array and perform << on it, but you don't store anything in @connections, so it stays {}. When you do @connections[i] = @connections[i].push(2) or @connections[i] = (@connections[i] << 2), you're calling []=, so the entry gets added to the hash.


However you should note that the hash will return a reference to the same array each time, so even if you do add the entry to the hash, it will likely still not behave as you expect once you add more than one entry (since all entries refer to the same array):

@connections = Hash.new []
@connections[1] = @connections[1].push(2)
@connections[2] = @connections[2].push(42)
puts @connections # => {1 => [2, 42], 2 => [2, 42]}

What you really want is a hash that returns a reference to a new array each time that a new key is accessed and that automatically adds an entry for the new array when that happens. To do that you can use the block form of Hash.new like this:

@connections = Hash.new do |h, k|
  h[k] = []
end
@connections[1].push(2)
@connections[2].push(42)
puts @connections # => {1 => [2], 2 => [42]}
sepp2k
  • 363,768
  • 54
  • 674
  • 675
1

Note that when you write

h = Hash.new |this_hash, non_existent_key| { this_hash[non_existent_key] = [] }

...Ruby will execute the block whenever you try to lookup a key that doesn't exist, and then return the block's return value. A block is like a def in that all variables inside it(including the parameter variables) are created anew every time the block is called. In addition, note that [] is an Array constructor, and each time it is called, it creates a new array.

A block returns the result of the last statement that was executed in the block, which is the assignment statement:

this_hash[non_existent_key] = []

And an assignment statement returns the right hand side, which will be a reference to the same Array that was assigned to the key in the hash, so any changes to the returned Array will change the Array in the hash.

On the other hand, when you write:

Hash.new([])

The [] constructor creates a new, empty Array; and that Array becomes the argument for Hash.new(). There is no block for ruby to call every time you look up a non existent key, so ruby just returns that one Array as the value for ALL non-existent keys--and very importantly nothing is done to the hash.

7stud
  • 46,922
  • 14
  • 101
  • 127