29

I want to make a copy of an array, to modify the copy in-place, without affecting the original one. This code fails

a = [
  '462664',
  '669722',
  '297288',
  '796928',
  '584497',
  '357431'
]
b = a.clone
b.object_id == a.object_id # => false
a[1][2] = 'X'
a[1] #66X722
b[1] #66X722

The copy should be different than the object. Why does it act like if it were just a reference?

sawa
  • 165,429
  • 45
  • 277
  • 381
Redouane Red
  • 509
  • 1
  • 5
  • 13
  • They are two different objects if you use the inspect method it should show different values for memory allocated. Cloning copies the variables but not the objects they reference. – bkunzi01 Jul 16 '15 at 13:26

6 Answers6

25

You need to do a deep copy of your array.

Here is the way to do it

Marshal.load(Marshal.dump(a))

This is because you are cloning the array but not the elements inside. So the array object is different but the elements it contains are the same instances. You could, for example, also do a.each{|e| b << e.dup} for your case

jazzytomato
  • 6,994
  • 2
  • 31
  • 44
  • Thanks,I'll use that,but I thought that only symbols reference the same object,why do the strings a[1] and b[1] reference the same object? – Redouane Red Jul 16 '15 at 13:28
  • Nice!!! I've tried with dup, clone and Array.new(a) and still change it, I think that ruby creates an array with pointers to each values. – Horacio Jul 16 '15 at 13:40
  • In Ruby everything is an object, so the array contains references to the string and not the strings themselves. When you clone the array, references get copied, but the copies keep pointing to the original strings. – Nic Nilov Jul 16 '15 at 13:41
  • @RedouaneRed the string `a[1]` **is** the same object as `b[1]`. I'm not sure I understand your concern about symbols, because symbols are immutable – jazzytomato Jul 16 '15 at 13:42
  • I just meant that when you create 2 similar strings,you create 2 objects,but when you create 2 similar symbols,you only create 1,thanks @NicNilov for the explaination. – Redouane Red Jul 16 '15 at 13:47
  • Serializing and deserializing isn't necessary here and isn't useful when the objects in the array can't be stringified. I suspect this has extra overhead. [This answer](https://stackoverflow.com/a/46839590/6243352) (`a.map(&:clone)`) is the best solution to OP's specific problem, a more idiomatic version of `a.each{|e| b << e.dup}`. – ggorlen Jun 16 '21 at 17:30
25

Instead of calling clone on the array itself, you can call it on each of the array's elements using map:

b = a.map(&:clone)

This works in the example stated in the question, because you get a new instance for each element in the array.

pjvleeuwen
  • 4,215
  • 1
  • 18
  • 31
wjordan
  • 19,770
  • 3
  • 85
  • 98
  • 1
    this is still not a deepcopy.. for ex: `a = [[1, 2, [3, 4]]]; b = a.map(&:clone); a[0][2][0] = 'foo'` will change `b` too – Sundeep May 26 '18 at 05:30
  • 1
    @Sundeep this question is about a simple array of objects, so a deep copy is not necessary. You can post your new example as a separate question to Stack Overflow if you would like it discussed in more detail. – wjordan May 26 '18 at 05:56
  • 1
    yeah, for the given example in question, this works.. I should've mentioned my comment as fyi/note.. did not mean to say answer is wrong.. – Sundeep May 26 '18 at 06:12
  • 1
    Indeed, if you this `map(&:clone)` approach instead of the `clone` approach as posted in the question, this will give the result that the OP was looking for. So indeed a valid answer. – pjvleeuwen May 07 '19 at 18:38
9

You can use #dup which creates a shallow copy of the object, meaning "the instance variables of object are copied, but not the objects they reference." For instance:

a = [1, 2, 3]

b = a.dup

b # => [1, 2, 3]

Source: https://ruby-doc.org/core-2.5.3/Object.html#method-i-dup

Edit: Listen to Paul below me. I misunderstood the question.

Zachary White
  • 123
  • 1
  • 5
  • 1
    -1 For this question `dup` and `clone` yield the exact same result as the OP got, so not really an answer to this question. For those who are now curious about the difference between `dup` and `clone` see https://stackoverflow.com/questions/10183370/whats-the-difference-between-rubys-dup-and-clone-methods – pjvleeuwen May 07 '19 at 18:45
7

Try this:

b = [] #create a new array 
b.replace(a) #replace the content of array b with the content from array a

At this point, these two arrays are references to different objects and content are the same.

glee8e
  • 6,180
  • 4
  • 31
  • 51
Maya Novarini
  • 319
  • 4
  • 6
  • 2
    this also doesn't do deepcopy... try with `a = [[1, 2, [3, 4]]]` – Sundeep May 26 '18 at 05:35
  • What Sundeep said: if you do this `replace` approach instead of the `clone` approach as posted in the question you will get the exact same result. So this is just a different way to the same thing as the OP did (while he was clearly looking for a different result). – pjvleeuwen May 07 '19 at 18:34
4

You can just map the elements of the array. I believe this is the cleanest solution as of today.

array.map(&:itself)

cesartalves
  • 1,507
  • 9
  • 18
-1

Not sure , if this is answered anywhere else. Tried searching but no success.

Try this

current_array =["a", "b","c","d"]
new_array = current_array[0 .. current_array.length]
new_array[0] ="cool"

output of new_array
 "cool","b","c","d"

Hope this helps.

Anutosh
  • 29
  • 2
  • The solution works. Not sure if someone has already answered this , becaz, it may have escaped my search efforts, since couldn't find the solution after searching, Don't want to repeat if already answered. Hope this helps. – Anutosh Feb 13 '17 at 09:13
  • You missed the trick here. You need to change one of the arrays and check if this change reflects to the other. – stema Mar 14 '18 at 15:13