¿Cómo copio un hash en Ruby?


Admitiré que soy un poco novato en ruby (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 Ruby. Quiero crear una copia del hash para poder modificarlo sin afectar a la instancia original.

Algunos métodos esperados que no funcionan como se pretende:

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
 174
Author: Max, 2010-11-11

13 answers

El clone método es el estándar de Ruby, forma incorporada de hacer un shallow-copy :

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 será documentado bajo el método #initialize_copy de la clase.

 201
Author: Mark Rushakoff,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-01 21:42:23

Como otros han señalado, clone lo hará. Tenga en cuenta que clone de un 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 se están copiando las referencias del hash, 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_copy funciona para cualquier objeto que puede calcularse. La mayoría de los tipos de datos incorporados (Array, Hash, String, &c.) pueden ser marshalled.

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

 164
Author: Wayne Conrad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-02 13:36:26

Si estás usando Rails puedes hacer:

h1 = h0.deep_dup

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

 57
Author: lmanners,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-30 17:19:04

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
 13
Author: James Moore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-01-21 19:53:02

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

copy_of_original_hash = Hash.new.merge(original_hash)
 5
Author: Kapil Aggarwal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-14 13:38:03

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

Si necesita deserializar datos no confiables, use JSON u otro formato de serialización que solo es capaz de cargar simple, 'primitivo' tipos 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"}
 3
Author: Wand Maker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-15 08:08:25

Uso Object#clone:

h1 = h0.clone

(Confusamente, la documentación para clone dice que initialize_copy es la forma de anular esto, pero el enlace para ese método en Hash te dirige a replace en su lugar...)

 2
Author: Josh Lee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-11-11 17:39:59

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

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
 1
Author: ktsujister,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-28 07:01:47

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 del original (si le gusta la programación sin estado).

 1
Author: kuonirat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-26 09:54:50

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

  bench  user      system      total        ( real)
  clone  1.960000   0.080000    2.040000    (  2.029604)
  merge  1.690000   0.080000    1.770000    (  1.767828)
  inject 3.120000   0.030000    3.150000    (  3.152627)
  
 1
Author: Justin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-18 14:21:51

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
 0
Author: Rohit,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-30 17:20:11

Este es un caso especial, pero si está comenzando con un hash predefinido del que desea tomar 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 construyeron otros. Inicialmente las estaba definiendo como variables de clase y me encontré con este problema de copia.

 0
Author: grumpasaurus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-30 17:20:32

Forma alternativa a Deep_Copy que funcionó para mí.

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

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

 -2
Author: user2521734,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-06-25 21:04:26