¿Cómo copio un hash en Ruby?
hash each ruby (13)
Admitiré que soy un poco novato de rubí (escribir 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
Clon es lento. Para el rendimiento, probablemente debería comenzar con hash en blanco y fusión. No cubre 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)
Como 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
Como el método de clonación estándar conserva el estado congelado, no es adecuado para crear nuevos objetos inmutables basándose en el objeto original, si desea que los nuevos objetos sean ligeramente diferentes al original (si le gusta la programación sin estado).
Como otros han señalado, el clone
lo hará. Tenga en cuenta que el 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 las referencias del hash se están copiando, pero no los objetos a los que hacen referencia 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 pueda organizarse. La mayoría de los tipos de datos incorporados (Array, Hash, String, etc.) pueden organizarse.
Marshalling es el nombre de Ruby para la serialization . Con 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.
Como se menciona en la sección Consideraciones de seguridad de la documentación de Marshal ,
Si necesita deserializar datos que no son de confianza, 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 de 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"}
El método de clone
es la forma incorporada estándar de Ruby de hacer una 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 se documentará con el método
#initialize_copy
de la clase.
Este es un caso especial, pero si está empezando con un hash predefinido 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 se basaban en otros. Inicialmente los estaba definiendo como variables de clase y encontré este problema de copia.
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
También soy un novato con Ruby y me enfrenté a problemas similares al duplicar un hash. Use lo siguiente. No tengo idea de la velocidad de este método.
copy_of_original_hash = Hash.new.merge(original_hash)
Una forma alternativa de Deep_Copy que funcionó para mí.
h1 = {:a => ''foo''}
h2 = Hash[h1.to_a]
Esto produjo un deep_copy ya que h2 se forma utilizando una representación de matriz de referencias h1 en lugar de h1.
Use clone :
h1 = h0.clone
(Confusamente, la documentación para clone
dice que initialize_copy
es la manera de anular esto, pero el enlace para ese método en Hash
indica que replace
lugar ...)
puede usar debajo para copiar profundamente los objetos Hash.
deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))