Ruby - Difference Between Array#≪≪ and Array#Push

Ruby - Difference between Array# and Array#push

They are very similar, but not identical.

<< accepts a single argument, and pushes it onto the end of the array.

push, on the other hand, accepts one or more arguments, pushing them all onto the end.

The fact that << only accepts a single object is why you're seeing the error.

Ruby array += vs push

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.

Difference between array element and array.push(element)? or string something and string+ something ? in Ruby

However, in both cases (array, string) it operates as just adding /
concatenating values.

It makes no difference "result-wise" - in both cases you get a value containing both operands (in that or another form).

The difference shows up in the way operands are impacted:

  • one performs in-place mutating
  • second is simply concatenating without changing the original strings.

Consider the following examples:

a = 'a'
b = 'b'

Now:

# concatenation - no changes to original strings
a + b #=> "ab"
a #=> "a"
b #=> "b"

Whereas:

# mutation - original string is changed in-place
a << b #=> "ab"
a #=> "ab"

Same goes with arrays:

# concatenation - no changes to original arrays
a = ['a'] #=> ["a"]
b = ['b'] #=> ["b"]
a + b #=> ["a", "b"]
a #=> ["a"]
b #=> ["b"]

# mutation - original array is changed in-place
a << b #=> ["a", ["b"]]
a #=> ["a", ["b"]]

As to Array#push and Array#<< - they do the same thing.

Add element to an array if it's not there already

You can use Set instead of Array.

Difference between concat and push?

The push() adds elements to the end of an array and returns the new length of the array. Thus your return here is invalid.

The concat() method is used to merge arrays. Concat does not change the existing arrays, but instead returns a new array.

Better to filter, if you want a NEW array like so:

var arr = [1, 2, 3, 4];
var filtered = arr.filter(function(element, index, array) {
return (index % 2 === 0);
});

Note that assumes the array arr is complete with no gaps - all even indexed values. If you need each individual, use the element instead of index

var arr = [1, 2, 3, 4];
var filtered = arr.filter(function(element, index, array) {
return (element% 2 === 0);
});

how to push a value to an array with a ternary operator?

thank you all for helping, I like the solution by iGian i.e. without parenthesis.

arr <<= true == false ? 'a' : 'b'

How do I add multiple elements to an array?

Using += operator:

arr = [1]
arr += [2, 3]
arr
# => [1, 2, 3]

What is faster in Ruby, `arr += [x]` or `arr x`

I believe this is down to how MRI allocates arrays (all of this answer is very MRI specific). Ruby tries really hard to be efficient with arrays: small arrays (<= 3 elements) are packed right into the RARRAY structure for example.

Another thing is that if you take an array and start appending values one at a time, ruby doesn't grow the buffer one element at a time, it does it in chunks: this is more efficient, at the expense of a small amount of memory.

One tool to see all this is using memsize_of:

ObjectSpace.memspace_of([]) #=> 40 (on 64 bit platforms
ObjectSpace.memspace_of([1,2]) #=> 40 (on 64 bit platforms
ObjectSpace.memsize_of([1,2,3,4]) #=> 72
ObjectSpace.memsize_of([]<<1<<2<<3<<4) #=> 200

In the first 2 cases, the array is packed within the RARRAY structure so the memory size is just the base size of any object (40 bytes). In the third case ruby had to allocate a array for 4 values (8 bytes each) so the size is 40 + 32 = 72. In the last case ruby grew the storage to 20 elements

This is relevant to the second case. The block inside the benchmark has a freshly created array which still has some spare capacity:

 ObjectSpace.memsize_of((0..20).to_a) #=> 336, enough for nearly 40 elements.

<< can just write its object to the appropriate slot, whereas += has to allocate a new array (both the object and its buffer) and copy all the data.

If I do

a = [1,2,3,4,5]
b = a.dup
ObjectSpace.memsize_of(b) #=> 40

Here b shares its buffer with a, so is reported as using no memory beyond the basic object size. At the point where b gets written to, ruby will have to copy the data (copy on write): in the first benchmark BOTH += and << are actually allocating a new buffer of sufficient size and copying all the data across.

Here is where I get handwavy: this would completely explain things if << and += performed identically, but that's not what is happening. My reading of things is that + is simpler. All it has to do, no matter what is allocate a buffer, and memcpy some data from 2 locations - this is fast.

<< on the other hand is mutating the array so it is paying the overhead of copy on write: it is doing extra work compare to +=. For example ruby needs to track who is sharing buffers so that garbage collection of the original array is possible when no one is sharing it anymore.

A benchmark that goes someway to convincing me that this interpretation is correct is as follows:

require 'benchmark/ips'
b = (0..20).to_a.dup
y = 21
Benchmark.ips do |x|
x.report('<<') { a = b.dup; a << y }
x.report('+=') { a = b.dup; a += [y] }

x.report('<<2') { a = b.dup; a << y; a<< y}
x.report('+=2') { a = b.dup; a += [y]; a += [y] }
end

This is basically the same benchmark as the original, but now appending 2 elements. For << the copy on write overhead will only be incurred the first time. The results I get are

              <<      1.325M (± 7.6%) i/s -      6.639M
+= 1.742M (± 9.5%) i/s - 8.677M
<<2 1.230M (±10.3%) i/s - 6.079M
+=2 1.140M (±10.8%) i/s - 5.656M

So appending to the array is back on top if you do it twice.



Related Topics



Leave a reply



Submit