Come faccio a copiare un hash in Ruby?


197

Devo ammettere che sono un po 'un principiante rubino (scrivendo script rake, ora). Nella maggior parte delle lingue, i costruttori di copie sono facili da trovare. Mezz'ora di ricerche non l'hanno trovata nel rubino. Voglio creare una copia dell'hash in modo da poterlo modificare senza influire sull'istanza originale.

Alcuni metodi previsti che non funzionano come previsto:

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

Nel frattempo, ho fatto ricorso a questa soluzione alternativa non elegante

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

Se hai a che fare con Hashoggetti semplici , la risposta fornita è buona. Se hai a che fare con oggetti simili a Hash che provengono da luoghi che non controlli, dovresti considerare se vuoi che la classe singleton associata a Hash sia duplicata o meno. Vedi stackoverflow.com/questions/10183370/…
Sim

Risposte:


223

Il clonemetodo è il modo standard di Ruby per fare una copia superficiale :

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"}

Si noti che il comportamento potrebbe essere ignorato:

Questo metodo può avere un comportamento specifico della classe. In tal caso, tale comportamento verrà documentato con il #initialize_copymetodo della classe.


Clone è un metodo su Object, BTW, quindi tutto ha accesso ad esso. Vedi i dettagli API qui
Dylan Lacey,

29
Aggiungendo un commento più esplicito qui per coloro che non leggono altre risposte, si tratta di una copia superficiale.
Grumpasaurus,

La documentazione #initialize_copy non sembra esistere per Hash, anche se esiste un collegamento ad essa nella pagina del documento Hash ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln,

14
E per gli altri principianti di Ruby, "copia superficiale" significa che ogni oggetto al di sotto del primo livello è ancora un riferimento.
RobW

9
Nota che questo non ha funzionato per gli hash nidificati per me (come indicato in altre risposte). Ho usato Marshal.load(Marshal.dump(h)).
bheeshmar,

178

Come altri hanno sottolineato, clonelo farà. Ricorda che cloneun hash fa una copia superficiale. Vale a dire:

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

Quello che sta succedendo è che i riferimenti dell'hash vengono copiati, ma non gli oggetti a cui fanno riferimento i riferimenti.

Se vuoi una copia profonda quindi:

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_copyfunziona per qualsiasi oggetto che può essere eseguito il marshalling. È possibile eseguire il marshalling della maggior parte dei tipi di dati incorporati (Array, Hash, String e ecc.).

Il marshalling è il nome di Ruby per la serializzazione . Con il marshalling, l'oggetto - con gli oggetti a cui fa riferimento - viene convertito in una serie di byte; quei byte vengono quindi utilizzati per creare un altro oggetto come l'originale.


È bello che tu abbia fornito le informazioni sulla copia profonda, ma dovrebbe venire con un avvertimento che ciò può causare effetti collaterali indesiderati (ad esempio, la modifica di entrambi gli hash modifica entrambi). Lo scopo principale della clonazione di un hash è impedire la modifica dell'originale (per immutabilità, ecc.).
K. Carpenter,

6
@ K.Carpenter Non è una copia superficiale che condivide parti dell'originale? Deep copia, a quanto ho capito, è una copia che non condivide alcuna parte dell'originale, quindi la modifica dell'una non modificherà l'altra.
Wayne Conrad,

1
Come sta esattamente Marshal.load(Marshal.dump(o))copiando in profondità? Non riesco davvero a capire cosa succede dietro le quinte
Muntasir Alam,

Ciò che evidenzia anche questo è che se lo fai h1[:a] << 'bar'modifichi l'oggetto originale (la stringa puntata da h1 [: a]) ma se invece dovessi farlo h1[:a] = "#{h1[:a]}bar", creeresti un nuovo oggetto stringa e lo indicherei h1[:a], mentre h2[:a]è punta ancora alla vecchia stringa (non modificata).
Max Williams,

@MuntasirAlam Ho aggiunto alcune parole su ciò che fa il marshalling. Spero che aiuti.
Wayne Conrad,


13

L'hash può creare un nuovo hash da un hash esistente:

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
Si noti che questo ha lo stesso problema di copia profonda di #clone e #dup.
forforf,

3
@forforf è corretto. Non provare a copiare le strutture di dati se non capisci la copia profonda vs. la copia superficiale.
James Moore,

5

Sono anche un principiante di Ruby e ho affrontato problemi simili nel duplicare un hash. Utilizza il seguente. Non ho idea della velocità di questo metodo.

copy_of_original_hash = Hash.new.merge(original_hash)

3

Come menzionato nella sezione Considerazioni sulla sicurezza della documentazione del Maresciallo ,

Se è necessario deserializzare dati non attendibili, utilizzare JSON o un altro formato di serializzazione in grado di caricare solo tipi semplici e "primitivi" come String, Array, Hash, ecc.

Ecco un esempio su come eseguire la clonazione usando JSON in 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

Utilizzare Object#clone:

h1 = h0.clone

(Confusamente, la documentazione per clonedice che initialize_copyè il modo per sovrascrivere questo, ma il link per quel metodo Hashti dirige replaceinvece a ...)


1

Poiché il metodo di clonazione standard conserva lo stato congelato, non è adatto per la creazione di nuovi oggetti immutabili basati sull'oggetto originale, se si desidera che i nuovi oggetti siano leggermente diversi dall'originale (se si preferisce la programmazione senza stato).


1

Il clone è lento. Perché le prestazioni dovrebbero probabilmente iniziare con hash vuoto e unire. Non copre il caso di hash nidificati ...

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
  totale del sistema utente da banco (reale)
  clone 1.960000 0.080000 2.040000 (2.029604)
  unione 1.690000 0.080000 1.770000 (1.767828)
  iniettare 3.120000 0.030000 3.150000 (3.152627)
  

1

Questo è un caso speciale, ma se stai iniziando con un hash predefinito che vuoi catturare e fare una copia, puoi creare un metodo che restituisce un hash:

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

h1 = johns

Lo scenario particolare che ho avuto è stato quello di avere una raccolta di hash dello schema JSON in cui alcuni hash ne avevano costruiti altri. Inizialmente le stavo definendo variabili di classe e ho riscontrato questo problema di copia.


0

puoi usare di seguito per copiare in profondità gli oggetti Hash.

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

16
Questo è un duplicato della risposta di Wayne Conrad.
Andrew Grimm,

0

Dato che Ruby ha un milione di modi per farlo, ecco un altro modo di usare Enumerable:

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

-3

Modo alternativo a Deep_Copy che ha funzionato per me.

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

Questo ha prodotto un deep_copy poiché h2 è formato usando una rappresentazione array di h1 anziché i riferimenti di h1.


3
Sembra promettente ma non funziona, questa è un'altra copia superficiale
Ginty,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.