0

I expected that Array.each and Array.collect would never change an object, like in this example:

a = [1, 2, 3]
a.each { |x| x = 5 }
a #output => [1, 2, 3]

But this doesn't seem to be the case when you are working with an array of arrays or an array of hashes:

a = [[1, 2, 3], [10, 20], ["a"]]
a.each { |x| x[0]=5 }
a #output => [[5, 2, 3], [5, 20], [5]]

Is this behaviour expected or am I doing something wrong?

Doesn't this make ruby behaviour a little unexpected? For example, in C++ if a function argument is declared const, one can be confident the function won't mess with it (ok, it can be mutable, but you got the point).

2
  • 1
    You are experiencing the same behavior as this: a = [[1]], b = a.dup # => [[1]], b[0][0] = 2 #=> [[2]], a #=> [[2]]. dup makes a "shallow" copy of a, whereas you were expecting a "deep" copy. Many sources explain this distinction in greater detail and explain ways of making a "deep" copy. This is one. Commented Mar 29, 2014 at 21:49
  • @CarySwoveland very correct.. #each passes the value to the block using deep copy. Commented Mar 29, 2014 at 22:29

2 Answers 2

3
a = [[1, 2, 3], [10, 20], ["a"]]
a.each { |x| x[0]=5 }

In the above example, x is an array ( which you are passing to the block in each iteration ), from which you are accessing an element from its 0th index, and updating it. As array is mutable object, it also updating. Here a is an array of array.

In 1st iteration x is [1, 2, 3]. Now you are calling, Array#[]= method to update the 0th element of [1, 2, 3].

In 2nd iteration x is [10, 20]. same as above.

..and so on.. Thus after #each has completed its iterations, you got modified a.

a = [1, 2, 3]
a.each { |x| x = 5 }

In the above example, you are passing the array element to the each block, which are Fixnum object, and not mutable also. Here a ia an array of elements, and you are just accessing those elements.

update ( to clear OP's comment )

a = [[1, 2, 3], [10, 20], ["a"]]
a.each do |x|
  # here x is holding the object from the source array `a`.
  x # => [1, 2, 3]
  x.object_id # => 72635790
  # here you assgined a new array object, which has the same content as the
  # inner array element [1, 2, 3]. But strictly these are 2 different object. Check
  # out the object_id of those two.
  x = [1, 2, 3]
  x # => [1, 2, 3]
  x.object_id # => 72635250
  break # used break to stop iteration after 1st one.
end
Sign up to request clarification or add additional context in comments.

2 Comments

thanks, still I find this unexpected like in a = [[1, 2, 3], [10, 20], ["a"]]; a.each { |x| x=[1,2,3]; x[0]=5 }
@random_user well. Now in the example, you have given in the comment, you are defining a new array object and assigned to x. Although both the array has same elements, they are not same object.
1

Using each or map does not change the array itself. But is might look like it changes elements in the array. In fact when a array is holding references to other object, that references are keep unchanged, but the referenced object itself can change. I agree it is surprising when you learn it.

What you noticed:

a = ['a', 'b', 'c']
a.each { |x| x[0] = 'x' }
puts a    # => ['x', 'x', 'x']

Here the first array element still references the same string, but the string has change.

Why it is important to understand this references?

array = ['a', 'b', 'c']
a = array
b = array
puts b # => ['a', 'b', 'c']
a[0] = 'x'
puts b # => ['x', 'b', 'c']

Does freeze protect us from changes?

a = ['a', 'b', 'c'].freeze
a << ['d'] # throws 'can't modify frozen Array (RuntimeError)'

Seems so. But again only for the array itself. It does not deep freeze the array.

a[0][0] = 'x'
puts a.inspect ['x', 'b', 'c']

I suggest the read about topics like referenced objects, pointers, call by value vs. call by reference.

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.