Cómo hacer copias profundas en Ruby

A menudo es necesario hacer una copia de un valor en Ruby. Si bien esto puede parecer simple, y es para objetos simples, tan pronto como tenga que hacer una copia de una estructura de datos con múltiples matrices o hashes en el mismo objeto, encontrará rápidamente que hay muchas dificultades..

Objetos y referencias

Para entender lo que está pasando, veamos un código simple. Primero, el operador de asignación que usa un tipo POD (datos antiguos simples) en Ruby.

a = 1
b = a
a + = 1
pone b

Aquí, el operador de asignación está haciendo una copia del valor de un y asignándolo a si utilizando el operador de asignación. Cualquier cambio a un no se reflejará en si. ¿Pero qué hay de algo más complejo? Considera esto.

a = [1,2]
b = a
un << 3
pone b.inspect

Antes de ejecutar el programa anterior, intente adivinar cuál será el resultado y por qué. Esto no es lo mismo que el ejemplo anterior, los cambios realizados en un se reflejan en si, ¿pero por qué? Esto se debe a que el objeto Array no es de tipo POD. El operador de asignación no hace una copia del valor, simplemente copia el referencia al objeto Array. los un y si las variables son ahora referencias al mismo objeto Array, cualquier cambio en cualquiera de las variables se verá en la otra.

Y ahora puede ver por qué copiar objetos no triviales con referencias a otros objetos puede ser complicado. Si simplemente hace una copia del objeto, solo está copiando las referencias a los objetos más profundos, por lo que su copia se conoce como una "copia superficial".

Lo que proporciona Ruby: dup y clone

Ruby proporciona dos métodos para hacer copias de objetos, incluido uno que se puede hacer para hacer copias profundas. los Objeto # dup El método hará una copia superficial de un objeto. Para lograr esto, el dup el método llamará al initialize_copy método de esa clase. Lo que esto hace exactamente depende de la clase. En algunas clases, como Array, inicializará una nueva matriz con los mismos miembros que la matriz original. Esto, sin embargo, no es una copia profunda. Considera lo siguiente.

a = [1,2]
b = a.dup
un << 3
pone b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
pone b.inspect

¿Qué ha pasado aquí? los Array # initialize_copy de hecho, el método hará una copia de una matriz, pero esa copia es en sí misma una copia superficial. Si tiene cualquier otro tipo que no sea POD en su matriz, use dup solo será una copia parcialmente profunda. Solo será tan profundo como la primera matriz, cualquier matriz más profunda, hash u otros objetos solo se copiarán superficialmente.

Hay otro método que vale la pena mencionar, clon. El método de clonación hace lo mismo que dup con una distinción importante: se espera que los objetos anulen este método con uno que pueda hacer copias profundas.

Entonces, en la práctica, ¿qué significa esto? Significa que cada una de sus clases puede definir un método de clonación que hará una copia profunda de ese objeto. También significa que tienes que escribir un método de clonación para cada clase que hagas.

Un truco: Marshalling

"Marcar" un objeto es otra forma de decir "serializar" un objeto. En otras palabras, convierta ese objeto en una secuencia de caracteres que pueda escribirse en un archivo que luego pueda "desarmar" o "deserializar" para obtener el mismo objeto. Esto puede ser explotado para obtener una copia profunda de cualquier objeto..

a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
pone b.inspect

Que ha pasado aqui? Marshal.dump crea un "volcado" de la matriz anidada almacenada en un. Este volcado es una cadena de caracteres binarios destinada a almacenarse en un archivo. Alberga el contenido completo de la matriz, una copia profunda completa. próximo, Marshal.load hace lo contrario Analiza esta matriz de caracteres binarios y crea una matriz completamente nueva, con elementos de matriz completamente nuevos..