6

I'm using Ruby 1.9.2-p290 and found:

a = Array.new(2, []).each {|i| i.push("a")}    
=> [["a", "a"], ["a", "a"]]

Which is not what I would expect. But the following constructor style does do what I would expect:

b = Array.new(2) {Array.new}.each {|i| i.push("b")}
=> [["b"], ["b"]] 

Is the first example the expected behavior?

In ruby-doc it looks like my size=2 argument is the same kind of argument for both constructors. I think that if the each method is getting passed that argument that it would use it the same way for both constructors.

3 Answers 3

10

This is a common misunderstanding. In your first example you are creating an array with 2 elements. Both of those are a pointer to the same array. So, when you iterate through your outer array you add 2 elements to the inner array, which is then reflected in your output twice

Compare these:

> array = Array.new(5, [])
=> [[], [], [], [], []] 

# Note - 5 identical object IDs (memory locations)
> array.map { |o| o.object_id }
=> [70228709214620, 70228709214620, 70228709214620, 70228709214620, 70228709214620] 

> array = Array.new(5) { [] }
=> [[], [], [], [], []] 

# Note - 5 different object IDs (memory locations)
> array.map { |o| o.object_id }
=> [70228709185900, 70228709185880, 70228709185860, 70228709185840, 70228709185780] 
Sign up to request clarification or add additional context in comments.

Comments

4

In the first case you're using a single instance of an Array as a default for the elements of the main Array:

a = Array.new(2, []).each {|i| i.push("a")}

The second argument is simply recycled, so the push is applied to the same instance twice. You've only created one instance here, the one being supplied as an argument, so it gets used over and over.

The second method is the correct way to do this:

b = Array.new(2) {Array.new}.each {|i| i.push("b")

This deliberately creates a new instance of an Array for each position in the main Array. The important difference here is the use of the block { ... } which executes once for each position in the new Array. A short-form version of this would be:

b = Array.new(2) { [ ] }.each {|i| i.push("b")

Comments

1

From the ruby documentation:

new(size=0, obj=nil)
new(array)
new(size) {|index| block }

Returns a new array. In the first form, the new array is empty. In the second it is created with size copies of obj (that is, size references to the same obj). The third form creates a copy of the array passed as a parameter (the array is generated by calling to_ary on the parameter). In the last form, an array of the given size is created.

Thus, in the a array you create, you have two references to the same array, thus the push works on both of them. That is, you're pushing "a" onto the same array twice. In the the b array you create, you're actually creating a new array for each element.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.