0

Why both pieces of code are not printing the same thing. I was intending the first piece to produce the output of the second

a=Array.new(5,Array.new(3))
for i in (0...a[0].length)
  a[0][i]=2
end
p a

# this prints [[2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2], [2, 2, 2]]*

a=Array.new(5).map{|d|d=Array.new(3)}
for i in (0...a[0].length)
  a[0][i]=2
end
p a

# this prints [[2, 2, 2], [nil, nil, nil], [nil, nil, nil], [nil, nil, nil], [nil, nil, nil]]

3
  • There is no need to assign to d in your block. It should read ...map{|d| Array.new(3)}. Commented Aug 10, 2012 at 2:44
  • @meagar: No need for |d| at all... Commented Aug 10, 2012 at 4:32
  • Can you explain what precisely is unclear to you about the documentation of Array::new? That way, the Ruby developers can improve the documentation so that future developers don't fall into the same trap as you did. Please, help making the world a better place! Commented May 29, 2019 at 7:54

3 Answers 3

3

This one

a=Array.new(5,Array.new(3))

Creates an array that contains the same array object within it five times. It's kinda like doing this:

a = []
b = a
a[0] = 123
puts b[0] #=> 123

Where this one:

a=Array.new(5).map{ Array.new(3) }

Creates a new 3 item array for each item in the parent array. So when you alter the first item it doesn't touch the others.

This is also why you shouldn't really use the Array and Hash constructor default arguments, as they don't always work they way you might expect.

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks. Why the default new operator acts so weird... kinda perplexing :(
Would be a good idea to mention that Array.new(5){Array.new(3)} is what the OP probably wanted to do.
@Marc-André Lafortune, can you elaborate a bit on why it is that?
[[nil]*3]*5 is doing the same thing as a=Array.new(5,Array.new(3))~~~ This is really annoying. How come? Any explanations?
Because that's how Ruby works. The array class defines a * operator. The * operator, when passed an integer N, produces N copies of the array, in an array. [3] * 3 gives you [3,3,3]. You've wrapped that in [], so you have [[3,3,3]]. Now you're multiplying the outer array by 5, so you get 5 copies of its first element, [3,3,3], so you wind up with [[3, 3, 3], [3, 3, 3], [3, 3, 3], [3, 3, 3], [3, 3, 3]].
|
1
Array.new(5,Array.new(3))

In the first example, your array contains 5 references to the same array. You create a single instance of an array with Array.new(3), a reference to which is used for each of the 5 arrays you initialize. When you modify a[0][0], you're also modifying a[1][0], a[2][0], etc. They are all references to the same array.

Array.new(5).map{ |d| Array.new(3) }

In the second example, your array contains 5 different arrays. Your block is invoked 5 times, Array.new(3) is invoked 5 times, and 5 different arrays are created. a[0] is a different array than a[1], etc.

3 Comments

Unnecessary for what? This is a question about the behaviour of that specific line of code. Necessary or not is irrelevant.
Oh, you're right, sorry. So let me suggest an improvement: Array.new(5){Array.new(3)} is what the OP probably wanted.
It doesn't matter what the OP wanted, I wasn't trying to correct their code, I was trying explain it like he asked me to.
1

The following are equivalent:

Array.new(5,Array.new(3))

[Array.new(3)] * 5

inside = Array.new(3); [inside, inside, inside, inside, inside]

They will all produce an array containing the same array. I mean the exact same object. That's why if you modify its contents, you will see that new value 5 times.

As you want independent arrays, you want to make sure that the "inside" arrays are not the same object. This can be achieved in different ways, for example:

Array.new(5){ Array.new(3) }

5.times.map { Array.new(3) }

Array.new(5).map { Array.new(3) }

# or dup your array manually:
inside = Array.new(3); [inside.dup, inside.dup, inside.dup, inside.dup, inside]

Note that the Array.new(5, []) form you used first doesn't copy the obj for you, it will reuse it. As that's not what you want, you should use the block form Array.new(5){ [] } which will call the block 5 times, and each time a new array is created.

The Hash class also has two constructors and is even more tricky.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.