Probablemente la razón de Ruby no contiene un clon de profundidad tiene que ver con la complejidad del problema. Vea las notas al final.
Para hacer una copia que "copie en profundidad", Hashes, Arrays y valores elementales, es decir, Hacer una copia de cada elemento en el original de tal forma que la copia tendrá los mismos valores, pero los nuevos objetos, puede utilizar esto:
class Object
def deepclone
case
when self.class==Hash
hash = {}
self.each { |k,v| hash[k] = v.deepclone }
hash
when self.class==Array
array = []
self.each { |v| array << v.deepclone }
array
else
if defined?(self.class.new)
self.class.new(self)
else
self
end
end
end
end
Si desea volver a definir el comportamiento del método de Ruby clone
, puede nómbrelo solo clone
en lugar de deepclone
(en 3 lugares), pero no tengo idea de cómo la redefinición del comportamiento del clon de Ruby afectará a las bibliotecas Ruby, o Ruby on Rails, por lo que Caveat Emptor. Personalmente, no puedo recomendar hacer eso.
Por ejemplo:
a = {'a'=>'x','b'=>'y'} => {"a"=>"x", "b"=>"y"}
b = a.deepclone => {"a"=>"x", "b"=>"y"}
puts "#{a['a'].object_id}/#{b['a'].object_id}" => 15227640/15209520
Si desea sus clases a deepclone correctamente, su método new
(inicializar) debe ser capaz de deepclone un objeto de esa clase en la forma estándar, es decir, si el se da el primer parámetro, se supone que es un objeto a ser clonado profundo.
Supongamos que queremos una clase M, por ejemplo. El primer parámetro debe ser un objeto opcional de la clase M. Aquí tenemos un segundo argumento opcional z
para preestablecer el valor de z en el nuevo objeto.
class M
attr_accessor :z
def initialize(m=nil, z=nil)
if m
# deepclone all the variables in m to the new object
@z = m.z.deepclone
else
# default all the variables in M
@z = z # default is nil if not specified
end
end
end
El z
preestablecido se ignora durante la clonación de aquí, pero el método puede tener un comportamiento diferente. Los objetos de esta clase serían creados de esta manera:
# a new 'plain vanilla' object of M
m=M.new => #<M:0x0000000213fd88 @z=nil>
# a new object of M with m.z pre-set to 'g'
m=M.new(nil,'g') => #<M:0x00000002134ca8 @z="g">
# a deepclone of m in which the strings are the same value, but different objects
n=m.deepclone => #<M:0x00000002131d00 @z="g">
puts "#{m.z.object_id}/#{n.z.object_id}" => 17409660/17403500
donde los objetos de la clase M son parte de una matriz:
a = {'a'=>M.new(nil,'g'),'b'=>'y'} => {"a"=>#<M:0x00000001f8bf78 @z="g">, "b"=>"y"}
b = a.deepclone => {"a"=>#<M:0x00000001766f28 @z="g">, "b"=>"y"}
puts "#{a['a'].object_id}/#{b['a'].object_id}" => 12303600/12269460
puts "#{a['b'].object_id}/#{b['b'].object_id}" => 16811400/17802280
Notas:
- If
deepclone
intentos de clonar un objeto que no se clona a sí mismo de la manera estándar, puede fallar.
- Si
deepclone
intenta clonar un objeto que puede clonarse a sí mismo de la manera estándar, y si se trata de una estructura compleja, puede (y probablemente lo hará) crear un clon superficial de sí mismo.
deepclone
no copia profundamente las claves en Hashes. La razón es que generalmente no se tratan como datos, pero si cambia hash[k]
a hash[k.deepclone]
, también se copiarán profundamente.
- Ciertos valores elementales no tienen el método
new
, como Fixnum. Estos objetos siempre tienen la misma ID de objeto, y se copian, no se clonan.
- Tenga cuidado porque cuando copie profundamente, dos partes de su Hash o matriz que contengan el mismo objeto en el original contendrán diferentes objetos en el deepclone.
¡Gracias, Evan! Buenas cosas, aprecio los puntos de referencia. :) – mway
Hola @Evan Pon, agregué [MessagePack] (http://msgpack.org/) en tus ejemplos. Es una buena opción. –
MessagePack se ve muy rápido (2 veces más rápido que Custom en mi máquina). ¿Podría actualizar la respuesta con la recomendación de usarla en lugar de Marshal? –