¿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
Precipitado
fuente
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.

Mark Rushakoff
fuente
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.

Wayne Conrad
fuente
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
73

Si está utilizando Rails, puede hacer:

h1 = h0.deep_dup

http://apidock.com/rails/Hash/deep_dup

modales
fuente
2
Rails 3 tiene un problema con las matrices deep_duping dentro de Hashes. Rails 4 arregla esto.
pdobb
1
Gracias por señalar esto, mi hash aún se vio afectado cuando uso dup o clon
Esgi Dendyanri
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
James Moore
fuente
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)
Kapil Aggarwal
fuente
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"}
Fabricante de varita
fuente
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 ...)

Josh Lee
fuente
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).

Kuonirat
fuente
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)
  
Justin
fuente
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.

grumpasaurus
fuente
0

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

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
ktsujister
fuente
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
Rohit
fuente
-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.

usuario2521734
fuente
3
Suena prometedor pero no funciona, esta es otra copia superficial
Ginty