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]]

lkahtz
  • 4,706
  • 8
  • 46
  • 72
  • There is no need to assign to `d` in your block. It should read `...map{|d| Array.new(3)}`. – user229044 Aug 10 '12 at 02:44
  • @meagar: No need for `|d|` at all... – Marc-André Lafortune Aug 10 '12 at 04: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! – Jörg W Mittag May 29 '19 at 07:54

3 Answers3

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.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • Thanks. Why the default new operator acts so weird... kinda perplexing :( – lkahtz Aug 10 '12 at 02:55
  • 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 Aug 10 '12 at 04:31
  • @Marc-André Lafortune, can you elaborate a bit on why it is that? – lkahtz Aug 10 '12 at 05:48
  • [[nil]*3]*5 is doing the same thing as a=Array.new(5,Array.new(3))~~~ This is really annoying. How come? Any explanations? – lkahtz Aug 10 '12 at 06:24
  • 2
    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]]`. – user229044 Aug 10 '12 at 12:43
  • I hope StackOverflow would provide Like button like Facebook. – lkahtz Aug 10 '12 at 23:25
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.

user229044
  • 232,980
  • 40
  • 330
  • 338
  • Unnecessary for what? This is a question about the behaviour of that specific line of code. Necessary or not is irrelevant. – user229044 Aug 10 '12 at 03:54
  • Oh, you're right, sorry. So let me suggest an improvement: `Array.new(5){Array.new(3)}` is what the OP probably wanted. – Marc-André Lafortune Aug 10 '12 at 04:28
  • 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. – user229044 Aug 10 '12 at 12:48
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.

Community
  • 1
  • 1
Marc-André Lafortune
  • 78,216
  • 16
  • 166
  • 166