views:

153

answers:

4

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?

+5  A: 

EDIT:

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

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

Cheers!

Isaac Hodes
That's the way. I like sticking that code in Object.deep_copy.
Wayne Conrad
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
"* Serializes obj and all descendant objects. If anIO is * specified, the serialized data will be written to it, otherwise the * data will be returned as a String. If limit is specified, the * traversal of subobjects will be limited to that depth. If limit is * negative, no checking of depth will be performed."http://github.com/shyouhei/ruby/blob/trunk/marshal.cThis is the place to look for more :)
Isaac Hodes
A: 

Try this:

temp_array = array.clone
Alex Reisner
A: 

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

Michael Dickens
+2  A: 

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.

Yar
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
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 :)
Yar