11
arr = ["red","green","yellow"]

arr2 = arr.clone
arr2[0].replace("blue")

puts arr.inspect
puts arr2.inspect

produces:

["blue", "green", "yellow"]
["blue", "green", "yellow"]

Is there anyway to do a deep copy of an array of strings, other than using Marshal as i understand that is a hack.

I could do:

arr2 = []
arr.each do |e|
  arr2 << e.clone
end

but it doesn't seem very elegant, or efficient.

Thanks

1
  • you can do shorter inline block: arr.each{|e| arr2 << e.dup} Commented Apr 5, 2010 at 18:21

6 Answers 6

13

Your second solution can be shortened to arr2 = arr.map do |e| e.dup end (unless you actually need the behaviour of clone, it's recommended to use dup instead).

Other than that your two solutions are basically the standard solutions to perform a deep copy (though the second version is only one-level deep (i.e. if you use it on an array of arrays of strings, you can still mutate the strings)). There isn't really a nicer way.

Edit: Here's a recursive deep_dup method that works with arbitrarily nested arrays:

class Array
  def deep_dup
    map {|x| x.deep_dup}
  end
end

class Object
  def deep_dup
    dup
  end
end

class Numeric
  # We need this because number.dup throws an exception
  # We also need the same definition for Symbol, TrueClass and FalseClass
  def deep_dup
    self
  end
end

You might also want to define deep_dup for other containers (like Hash), otherwise you'll still get a shallow copy for those.

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for your answer sepp2k, so if you have a nested array, the only way is to use Marshal?
@Jon: No you could also define a recursive deep_dup method (see my edit), but using Marshal is usually easier.
Can't you avoid patching Numeric etc. by defining deep_dup in Object to do this?: respond_to?(:dup) ? dup : self
@Lars: No, because Numerics do respond to dup. They just respond by throwing an exception.
@sepp2k: Bummer. Then something like [Numeric, Symbol, TrueClass, FalseClass].include?(self.class) ? self : dup; ought to do the trick.
|
7

I am in a similar situation and very concerned about speed. The fastest way for me was to make use of map{&:clone}

So try this:

pry(main)> a = (10000..1000000).to_a.shuffle.map(&:to_s)
pry(main)> Benchmark.ms { b = a.deep_dup }                                                                                     
=> 660.7760030310601
pry(main)> Benchmark.ms { b = a.join("--!--").split("--!--") }
=> 605.0828141160309
pry(main)> Benchmark.ms { b = a.map(&:clone) }
=> 450.8283680770546

Comments

5

I recommend your initial idea, but written slightly more concisely:

arr = ["red","green","yellow"]
arr2 = arr.inject([]) { |a,element| a << element.dup }

1 Comment

There is a problem with DUP if your array has FixNums like integer - can't dup Fixnum stackoverflow.com/questions/1964143/… stackoverflow.com/questions/19165957/ruby-cant-dup-fixnum
3

You can make a deep copy of array a by following code:

 Marshal.load(Marshal.dump(a))

Comments

2

It looks so simple.. Just run the below code:

a = [1,2,3]
b = [].replace(a)

b[1] = 5

puts a
puts b

Run above code and you will notice the difference. Cheers !

Comments

1

You can use this hack:

arr1 = %w{ red green blue }
arr2 = arr1.join("--!--").split("--!--")

But it is just for fun :)

arr2[0].replace("lol")
p arr1
#=> ["red", "green", "blue"]
p arr2
#=> ["lol", "green", "blue"]

And it will work only for 1 level arrays

2 Comments

It will also only work if the array only contains strings and none of the strings contain "--!--" as a substring.
sepp2k, yeap, it's just hack for one purpose only :) like tetra pack

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.