18

I have a 2-dimensional array in Ruby that I want to produce a working duplicate of. Obviously I can't do this;

array=[[3,4],[5,9],[10,2],[11,3]]
temp_array=array

as any modifications I make to temp_array will also be made to array, as I have merely copied the object identifier. I thought I would be able to get around this by simply using;

temp_array=array.dup

but this doesn't work as temp_array is simply an array of object identifiers that get duplicated so I still end up modifying the initial array (if I understand what went wrong when I did this). The solution I found was to do the following;

temp_array=[]
array.each{|sub| temp_array << sub.dup}

This achieves what I want but seems to be an awkward way of solving my problem.

I am concerned about how this would work if I didn't know what my array was going to be containing (e.g. if it was possible that some parts of the array had 3-dimensions). I would potentially have to test the class of each member of the array to see if it had to be iterated over in order to duplicate it. Not an impossible task at all, but it seems messy to me. Is this simply a consequence of Ruby lacking built-in support for multidimensional arrays or is there a simple built-in function to do this that I have missed?

7 Answers 7

35

Here's the "Ruby-esque" way to handle it:

temp_array = Marshal.load(Marshal.dump(your_array_to_be_cloned))

2
  • 3
    That's the way. I like sticking that code in Object.deep_copy. Commented Jan 11, 2010 at 3:25
  • Great, thankyou. This actually explains the whole Marshalling thing to me as well (although I need to go and do some more reading to really get my head around it).
    – brad
    Commented Jan 12, 2010 at 3:10
5

There is the best way of making exact and real copy of the multidimensional array in Ruby is Marshalling.

Here is the Ruby syntax of marshalling:

Marshal.load(Marshal.dump(Name_Of_Your_Original_Array))

Let's see how to use this syntax using the above example i.e.

array=[[3,4],[5,9],[10,2],[11,3]] temp_array=array

In this example it only create an object which points the same memory location of the array, it's not doing the real copy of our array. Here, If you modify the value of your temp_array then it'll automatically reflect the changes in the original array which is array variable in our example. So how do we prevent the automatic changes happen in our original array, we can do this by marshalling.

So! how do we do this, in the example we need to make a real copy of the array into the temp_array.

Let's see, how to do this :

array=[[3,4],[5,9],[10,2],[11,3]] temp_array = Marshal.load(Marshal.dump(array))

Now, we've done the real copy of our multidimensional array, if you modify any value of your temp_array then the changes will not reflect your original array.

4

As other people have pointed out, you can use clone. This will not work, however, as it's a shallow copy, so the sub arrays (this is not really a multidimensional array, I think) will not get cloned. Since arrays are mutable objects in Ruby, the sub arrays will get changed. For instance, check this out

>> blah = [[3,5],6]
=> [[3, 5], 6]
>> joe = blah.clone
=> [[3, 5], 6]
>> joe[0]
=> [3, 5]
>> joe[0].push "blah"
=> [3, 5, "blah"]
>> blah
=> [[3, 5, "blah"], 6]

So as you can see, just doing clone will not work. But you knew that, hence your question.

I cooked this up just now. This will do until you find out the real, Ruby way to do it (I just work in Ruby, I'm not an expert).

def dup_recursive(new_array, old_array)
  old_array.each do |item|
    if item.class == Array
      new_array << dup_recursive([], item)
    else
      new_item = item.dup rescue new_item = item # in case it's got no dupe, like FixedNum
      new_array << new_item
    end
    new_array
  end
end

array=[[3,[9,12]],[5,9],[10,2],[11,3]]
new_array = Array.new
dup_recursive(new_array, array)
puts array.inspect
puts new_array.inspect

I know, I'm not using duck-typing, but I'd be glad to be schooled as to how to do this without asking for the class of the object in question.

Edit: I should've just searched on deep-clone ruby in Google, but sometimes I like writing code :)... anyway, the other solution presented -- Marshal.load( Marshal.dump( array ) ) -- will also work for Hashes and so forth, so it's way better.

2
  • That's actually a nice little piece of code. It's what I was afraid I was going to have to write to deal with this issue but done much better than I would have. It's interesting the number of people who went straight to the clone solution - this issue is obviously not well understood by many Rubyists.
    – brad
    Commented Jan 12, 2010 at 3:11
  • Perhaps true, Brad, but on the other hand, maybe you got a bad sample (of people, answers). Could be time-of-day, wording of question, or anything else. Or could be that this issue is not understood by many Rubyists. Unfortunately Rails kind of shields you from having to understand anything much at all :) Commented Jan 12, 2010 at 11:40
0

You can use DeepEnumerable's deep_dup for this:

>> require 'deep_enumerable'

>> array=[[3,4],[5,9],[10,2],[11,3]]

>> temp_array=array.deep_dup
>> array.each{|sub| sub << "XXX"}

>> array
=> [[3, 4, "XXX"], [5, 9, "XXX"], [10, 2, "XXX"], [11, 3, "XXX"]]

>> temp_array
=> [[3, 4], [5, 9], [10, 2], [11, 3]]
-1

Try running array.dup on each sub-array within the array.

    c = []
    array.each do |row|
      c << row.dup
    end
-8

Try this:

temp_array = array.clone
-9

You can use array.clone as specified here. That will give you a copy of the original object, and not just a pointer.

Not the answer you're looking for? Browse other questions tagged or ask your own question.