Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ruby array += vs push

Tags:

arrays

ruby

I have an array of arrays and want to append elements to the sub-arrays. += does what I want, but I'd like to understand why push does not.

Behavior I expect (and works with +=):

b = Array.new(3,[])
b[0] += ["apple"]
b[1] += ["orange"]
b[2] += ["frog"]

b => [["apple"], ["orange"], ["frog"]]

With push I get the pushed element appended to EACH sub-array (why?):

a = Array.new(3,[])
a[0].push("apple")
a[1].push("orange")
a[2].push("frog")

a => [["apple", "orange", "frog"], ["apple", "orange", "frog"], ["apple", "orange", "frog"]]

Any help on this much appreciated.

like image 309
David Bunnell Avatar asked Aug 08 '17 14:08

David Bunnell


1 Answers

The issue here is b = Array.new(3, []) uses the same object as the base value for all the array cells:

b = Array.new(3, [])
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380

So when you use b[0].push, it adds the item to "each" sub-array because they are all, in fact, the same array.

So why does b[0] += ["value"] work? Well, looking at the ruby docs:

ary + other_ary → new_ary

Concatenation — Returns a new array built by concatenating the two arrays together to produce a third array.

[ 1, 2, 3 ] + [ 4, 5 ]    #=> [ 1, 2, 3, 4, 5 ]
a = [ "a", "b", "c" ]
c = a + [ "d", "e", "f" ]
c                         #=> [ "a", "b", "c", "d", "e", "f" ]
a                         #=> [ "a", "b", "c" ]

Note that

x += y

is the same as

x = x + y

This means that it produces a new array. As a consequence, repeated use of += on arrays can be quite inefficient.

So when you use +=, it replaces the array entirely, meaning the array in b[0] is no longer the same as b[1] or b[2].

As you can see:

b = Array.new(3, [])
b[0].push("test") 
b #=> [["test"], ["test"], ["test"]]
b[0].object_id #=> 28424380
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380
b[0] += ["foo"]
b #=> [["test", "foo"], ["test"], ["test"]]
b[0].object_id #=> 38275912
b[1].object_id #=> 28424380
b[2].object_id #=> 28424380

If you're wondering how to ensure each array is unique when initializing an array of arrays, you can do so like this:

b = Array.new(3) { [] }

This different syntax lets you pass a block of code which gets run for each cell to calculate its original value. Since the block is run for each cell, a separate array is created each time.

like image 118
eiko Avatar answered Oct 22 '22 08:10

eiko