Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Duplicating a Ruby array of strings

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

like image 727
dangerousdave Avatar asked Apr 05 '10 17:04

dangerousdave


People also ask

How do you concatenate an array in Ruby?

This can be done in a few ways in Ruby. The first is the plus operator. This will append one array to the end of another, creating a third array with the elements of both. Alternatively, use the concat method (the + operator and concat method are functionally equivalent).

What is DUP in Ruby?

According to Ruby-doc, both #clone and #dup can be used to create a shallow copy of an object, which only traverse one layer of complexity, meaning that the instance variables of obj are copied, but not the objects they reference. They would all share the same attributes; modifying one would result a change on another.

Can you split an array Ruby?

split is a String class method in Ruby which is used to split the given string into an array of substrings based on a pattern specified. Here the pattern can be a Regular Expression or a string. If pattern is a Regular Expression or a string, str is divided where the pattern matches.

What does .first do Ruby?

The first() is an inbuilt method in Ruby returns an array of first X elements. If X is not mentioned, it returns the first element only. Parameters: The function accepts X which is the number of elements from the beginning. Return Value: It returns an array of first X elements.


2 Answers

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
like image 84
Fabian Avatar answered Sep 29 '22 14:09

Fabian


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.

like image 27
sepp2k Avatar answered Sep 29 '22 14:09

sepp2k