Cómo hacer copias profundas en Ruby
Yuri Arcurs/Getty Images
A menudo es necesario hacer una copia de un valor en rubí . 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 varios arreglos o hashes en el mismo objeto, encontrará rápidamente que hay muchas trampas.
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 (Plain Old Data) en Rubí .
un = 1
b = un
un += 1
pone b
Aquí, el operador de asignación está haciendo una copia del valor de a y asignándolo a b utilizando el operador de asignación. Cualquier cambio en a no se reflejará en b . Pero, ¿qué pasa con algo más complejo? Considera esto.
un = [1,2]
b = un
a<< 3
pone b.inspeccionar
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 a se reflejan en b , ¿pero por qué? Esto se debe a que el Formación el objeto no es un tipo POD. El operador de asignación no hace una copia del valor, simplemente copia el referencia al objeto Array. los a y b 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 denomina 'copia superficial'.
Qué ofrece Ruby: duplicar y clonar
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 duplicar El método llamará al inicializar_copiar 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.
un = [1,2]
b = a.dup
a<< 3
pone b.inspeccionar
un = [ [1,2] ]
b = a.dup
un[0]<< 3
pone b.inspeccionar
¿Qué ha pasado aquí? los Array#initialize_copy El método de hecho hará una copia de un Array, pero esa copia es en sí misma una copia superficial. Si tiene otros tipos que no sean POD en su arreglo, use duplicar solo será una copia parcialmente profunda. Solo será tan profundo como la primera matriz, más profundo arreglos , hachís u otros objetos solo se copiarán superficialmente.
Hay otro método que vale la pena mencionar, clon . El método clon hace lo mismo que duplicar 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 debe escribir un método de clonación para todas y cada una de las clases que crea.
Un truco: Marshalling
'Ordenar' un objeto es otra forma de decir 'serializar' un objeto. En otras palabras, convierta ese objeto en un flujo de caracteres que se puede escribir en un archivo que luego puede 'descomponer' o 'deserializar' para obtener el mismo objeto. Esto se puede aprovechar para obtener una copia profunda de cualquier objeto.
un = [ [1,2] ]
b = Marshal.load( Marshal.dump(a) )
un[0]<< 3
pone b.inspeccionar
¿Qué ha pasado aquí? Marshal.dump crea un 'volcado' de la matriz anidada almacenada en a . 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.cargar hace lo contrario. Analiza esta matriz de caracteres binarios y crea una matriz completamente nueva, con elementos de matriz completamente nuevos.
Pero esto es un truco. Es ineficiente, no funcionará en todos los objetos (¿qué sucede si intenta clonar una conexión de red de esta manera?) y probablemente no sea muy rápido. Sin embargo, es la forma más fácil de hacer copias profundas por debajo de las personalizadas. inicializar_copiar o clon métodos. Además, se puede hacer lo mismo con métodos como to_yaml o a_xml si tiene bibliotecas cargadas para admitirlas.