¿Cómo copio un hash en Ruby?


197

Admito que soy un poco un novato rubí (escribiendo guiones de rake, ahora). En la mayoría de los idiomas, los constructores de copias son fáciles de encontrar. Media hora de búsqueda no lo encontró en rubí. Quiero crear una copia del hash para poder modificarlo sin afectar la instancia original.

Algunos métodos esperados que no funcionan según lo previsto:

h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1=Hash.new(h0)
h2=h1.to_hash

Mientras tanto, he recurrido a esta solución poco elegante

def copyhash(inputhash)
  h = Hash.new
  inputhash.each do |pair|
    h.store(pair[0], pair[1])
  end
  return h
end

Si se trata de Hashobjetos simples , la respuesta proporcionada es buena. Si se trata de objetos similares a Hash que provienen de lugares que no controla, debe considerar si desea que la clase singleton asociada con el Hash esté duplicada o no. Ver stackoverflow.com/questions/10183370/…
Sim

Respuestas:


223

El clonemétodo es la forma estándar incorporada de Ruby para hacer una copia superficial :

irb(main):003:0> h0 = {"John" => "Adams", "Thomas" => "Jefferson"}
=> {"John"=>"Adams", "Thomas"=>"Jefferson"}
irb(main):004:0> h1 = h0.clone
=> {"John"=>"Adams", "Thomas"=>"Jefferson"}
irb(main):005:0> h1["John"] = "Smith"
=> "Smith"
irb(main):006:0> h1
=> {"John"=>"Smith", "Thomas"=>"Jefferson"}
irb(main):007:0> h0
=> {"John"=>"Adams", "Thomas"=>"Jefferson"}

Tenga en cuenta que el comportamiento puede ser anulado:

Este método puede tener un comportamiento específico de clase. Si es así, ese comportamiento se documentará según el #initialize_copymétodo de la clase.


Clone es un método en Object, por cierto, por lo que todo tiene acceso a él. Vea los detalles de la API aquí
Dylan Lacey

29
Agregar un comentario más explícito aquí para aquellos que no están leyendo otras respuestas de que esto es una copia superficial.
grumpasaurus

La documentación de #initialize_copy no parece existir para Hash, a pesar de que hay un enlace a ella en la página de documentación de Hash ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln

14
Y para otros principiantes de Ruby, "copia superficial" significa que cada objeto debajo del primer nivel sigue siendo una referencia.
RobW

9
Tenga en cuenta que esto no funcionó para los hashes anidados para mí (como se menciona en otras respuestas). He utilizado Marshal.load(Marshal.dump(h)).
bheeshmar

178

Como otros han señalado, clonelo harán. Tenga en cuenta que cloneun hash hace una copia superficial. Es decir:

h1 = {:a => 'foo'} 
h2 = h1.clone
h1[:a] << 'bar'
p h2                # => {:a=>"foobar"}

Lo que sucede es que las referencias de hash se están copiando, pero no los objetos a los que se refieren las referencias.

Si quieres una copia profunda, entonces:

def deep_copy(o)
  Marshal.load(Marshal.dump(o))
end

h1 = {:a => 'foo'}
h2 = deep_copy(h1)
h1[:a] << 'bar'
p h2                # => {:a=>"foo"}

deep_copyfunciona para cualquier objeto que se pueda ordenar. La mayoría de los tipos de datos integrados (Array, Hash, String, etc.) se pueden ordenar.

Marshalling es el nombre de Ruby para la serialización . Con la clasificación, el objeto, con los objetos a los que se refiere, se convierte en una serie de bytes; esos bytes se usan para crear otro objeto como el original.


Es bueno que haya proporcionado la información sobre la copia profunda, pero debe venir con una advertencia de que esto puede causar efectos secundarios no deseados (por ejemplo, la modificación de cualquiera de los hash modifica a ambos). El objetivo principal de clonar un hash es evitar la modificación del original (por inmutabilidad, etc.).
K. Carpenter

66
@ K.Carpenter ¿No es una copia superficial que comparte partes del original? La copia profunda, según tengo entendido, es una copia que no comparte ninguna parte del original, por lo que modificar una no modificará la otra.
Wayne Conrad

1
¿Cómo es exactamente Marshal.load(Marshal.dump(o))la copia profunda? Realmente no puedo entender lo que sucede detrás de escena
Muntasir Alam

Lo que esto destaca también es que si lo hace h1[:a] << 'bar', modifica el objeto original (la cadena apuntada por h1 [: a]) pero si fuera a hacerlo h1[:a] = "#{h1[:a]}bar", crearía un nuevo objeto de cadena, y apuntaría h1[:a]a eso, mientras h2[:a]es Todavía apunta a la cadena antigua (sin modificar).
Max Williams

@MuntasirAlam Agregué algunas palabras sobre lo que hace la clasificación. Espero que eso ayude.
Wayne Conrad


13

Hash puede crear un nuevo hash a partir de un hash existente:

irb(main):009:0> h1 = {1 => 2}
=> {1=>2}
irb(main):010:0> h2 = Hash[h1]
=> {1=>2}
irb(main):011:0> h1.object_id
=> 2150233660
irb(main):012:0> h2.object_id
=> 2150205060

24
Tenga en cuenta que esto tiene el mismo problema de copia profunda que #clone y #dup.
forforf

3
@forforf es correcto. No intente copiar estructuras de datos si no entiende la copia profunda frente a la copia superficial.
James Moore

5

También soy un novato en Ruby y enfrenté problemas similares al duplicar un hash. Usa lo siguiente. No tengo idea de la velocidad de este método.

copy_of_original_hash = Hash.new.merge(original_hash)

3

Como se menciona en la sección Consideraciones de seguridad de la documentación de Marshal ,

Si necesita deserializar datos no confiables, use JSON u otro formato de serialización que solo pueda cargar tipos simples y 'primitivos' como String, Array, Hash, etc.

Aquí hay un ejemplo sobre cómo hacer clonación usando JSON en Ruby:

require "json"

original = {"John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
cloned = JSON.parse(JSON.generate(original))

# Modify original hash
original["John"] << ' Sandler'
p original 
#=> {"John"=>"Adams Sandler", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"}

# cloned remains intact as it was deep copied
p cloned  
#=> {"John"=>"Adams", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"}

1

Uso Object#clone:

h1 = h0.clone

(Confusamente, la documentación clonedice que esa initialize_copyes la forma de anular esto, pero el enlace para ese método lo Hashdirige a usted en su replacelugar ...)


1

Dado que el método de clonación estándar conserva el estado congelado, no es adecuado para crear nuevos objetos inmutables basados ​​en el objeto original, si desea que los nuevos objetos sean ligeramente diferentes al original (si le gusta la programación sin estado).


1

El clon es lento. Para el rendimiento probablemente debería comenzar con hash en blanco y fusionar. No cubre el caso de hashes anidados ...

require 'benchmark'

def bench  Benchmark.bm do |b|    
    test = {'a' => 1, 'b' => 2, 'c' => 3, 4 => 'd'}
    b.report 'clone' do
      1_000_000.times do |i|
        h = test.clone
        h['new'] = 5
      end
    end
    b.report 'merge' do
      1_000_000.times do |i|
        h = {}
        h['new'] = 5
        h.merge! test
      end
    end
    b.report 'inject' do
      1_000_000.times do |i|
        h = test.inject({}) do |n, (k, v)|
          n[k] = v;
          n
        end
        h['new'] = 5
      end
    end
  end
end
  sistema de usuario de banco total (real)
  clon 1.960000 0.080000 2.040000 (2.029604)
  fusionar 1.690000 0.080000 1.770000 (1.767828)
  inyectar 3.120000 0.030000 3.150000 (3.152627)
  

1

Este es un caso especial, pero si está comenzando con un hash predefinido que desea obtener y hacer una copia, puede crear un método que devuelva un hash:

def johns 
    {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
end

h1 = johns

El escenario particular que tuve fue que tenía una colección de hashes de esquema JSON donde algunos hashes se construyeron a partir de otros. Inicialmente los estaba definiendo como variables de clase y me encontré con este problema de copia.


0

puede usar a continuación para copiar en profundidad los objetos Hash.

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))

16
Este es un duplicado de la respuesta de Wayne Conrad.
Andrew Grimm

0

Dado que Ruby tiene un millón de formas de hacerlo, aquí hay otra forma de usar Enumerable:

h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1 = h0.inject({}) do |new, (name, value)| 
    new[name] = value;
    new 
end

-3

Una forma alternativa de Deep_Copy que funcionó para mí.

h1 = {:a => 'foo'} 
h2 = Hash[h1.to_a]

Esto produjo una copia profunda ya que h2 se forma usando una representación de matriz de h1 en lugar de las referencias de h1.


3
Suena prometedor pero no funciona, esta es otra copia superficial
Ginty
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.