Wie kopiere ich einen Hash in Ruby?


197

Ich gebe zu, dass ich ein bisschen ein Rubin-Neuling bin (jetzt schreibe ich Rake-Skripte). In den meisten Sprachen sind Kopierkonstruktoren leicht zu finden. Eine halbe Stunde Suche fand es nicht in Rubin. Ich möchte eine Kopie des Hashs erstellen, damit ich ihn ändern kann, ohne die ursprüngliche Instanz zu beeinflussen.

Einige erwartete Methoden, die nicht wie beabsichtigt funktionieren:

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

In der Zwischenzeit habe ich auf diese unelegante Problemumgehung zurückgegriffen

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

Wenn Sie mit einfachen HashObjekten arbeiten, ist die Antwort gut. Wenn Sie mit Hash-ähnlichen Objekten arbeiten, die von Orten stammen, die Sie nicht kontrollieren, sollten Sie überlegen, ob die dem Hash zugeordnete Singleton-Klasse dupliziert werden soll oder nicht. Siehe stackoverflow.com/questions/10183370/…
Sim

Antworten:


223

Die cloneMethode ist Rubys Standardmethode für die Erstellung einer flachen Kopie :

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

Beachten Sie, dass das Verhalten möglicherweise überschrieben wird:

Diese Methode kann klassenspezifisches Verhalten aufweisen. In diesem Fall wird dieses Verhalten nach der #initialize_copyMethode der Klasse dokumentiert .


Klonen ist eine Methode für Object, BTW, also hat alles Zugriff darauf. Siehe die API-Details hier
Dylan Lacey

29
Wenn Sie hier einen expliziteren Kommentar für diejenigen hinzufügen, die keine anderen Antworten lesen, handelt es sich um eine flache Kopie.
Grumpasaurus

Die Dokumentation #initialize_copy scheint für Hash nicht zu existieren, obwohl auf der Hash-Dokumentseite ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln

14
Und für andere Ruby-Anfänger bedeutet "flache Kopie", dass jedes Objekt unter der ersten Ebene immer noch eine Referenz ist.
RobW

9
Beachten Sie, dass dies für verschachtelte Hashes bei mir nicht funktioniert hat (wie in anderen Antworten erwähnt). Ich habe benutzt Marshal.load(Marshal.dump(h)).
Bheeshmar

178

Wie andere darauf hingewiesen haben, clonewird es tun. Beachten Sie, dass cloneein Hash eine flache Kopie erstellt. Das heißt:

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

Was passiert ist, dass die Referenzen des Hashs kopiert werden, aber nicht die Objekte, auf die sich die Referenzen beziehen.

Wenn Sie eine tiefe Kopie wünschen, dann:

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_copyfunktioniert für jedes Objekt, das gemarshallt werden kann. Die meisten integrierten Datentypen (Array, Hash, String usw.) können gemarshallt werden.

Marshalling ist Rubys Name für Serialisierung . Beim Marshalling wird das Objekt - mit den Objekten, auf die es sich bezieht - in eine Reihe von Bytes konvertiert. Diese Bytes werden dann verwendet, um ein anderes Objekt wie das Original zu erstellen.


Es ist schön, dass Sie die Informationen zum Tiefenkopieren angegeben haben, aber es sollte eine Warnung enthalten sein, dass dies unbeabsichtigte Nebenwirkungen verursachen kann (wenn Sie beispielsweise einen der beiden Hashs ändern, werden beide geändert). Der Hauptzweck des Klonens eines Hashs besteht darin, eine Änderung des Originals zu verhindern (aus Gründen der Unveränderlichkeit usw.).
K. Carpenter

6
@ K.Carpenter Ist es nicht eine flache Kopie, die Teile des Originals teilt? Deep Copy ist, wie ich es verstehe, eine Kopie, die keinen Teil des Originals teilt. Wenn Sie also eine ändern, wird die andere nicht geändert.
Wayne Conrad

1
Wie genau wird Marshal.load(Marshal.dump(o))tief kopiert? Ich kann nicht wirklich verstehen, was hinter den Kulissen passiert
Muntasir Alam

Dies unterstreicht auch, dass Sie, wenn Sie h1[:a] << 'bar'das ursprüngliche Objekt ändern (die Zeichenfolge, auf die h1 [: a] zeigt), aber wenn Sie dies h1[:a] = "#{h1[:a]}bar"stattdessen tun würden, ein neues Zeichenfolgenobjekt erstellen und darauf zeigen würden h1[:a], während es h2[:a]ist zeigt immer noch auf die alte (unveränderte) Zeichenfolge.
Max Williams

@MuntasirAlam Ich habe ein paar Worte darüber hinzugefügt, was Marshalling macht. Ich hoffe das hilft.
Wayne Conrad


13

Hash kann aus einem vorhandenen Hash einen neuen Hash erstellen:

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
Beachten Sie, dass dies das gleiche Problem mit tiefen Kopien wie #clone und #dup hat.
Forforf

3
@forforf ist korrekt. Versuchen Sie nicht, Datenstrukturen zu kopieren, wenn Sie keine tiefe oder flache Kopie verstehen.
James Moore

5

Ich bin auch ein Neuling bei Ruby und hatte ähnliche Probleme beim Duplizieren eines Hashs. Verwenden Sie Folgendes. Ich habe keine Ahnung von der Geschwindigkeit dieser Methode.

copy_of_original_hash = Hash.new.merge(original_hash)

3

Wie im Abschnitt "Sicherheitsüberlegungen" der Marshal-Dokumentation erwähnt ,

Wenn Sie nicht vertrauenswürdige Daten deserialisieren müssen, verwenden Sie JSON oder ein anderes Serialisierungsformat, mit dem nur einfache, primitive Typen wie String, Array, Hash usw. geladen werden können.

Hier ist ein Beispiel zum Klonen mit 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

Verwendung Object#clone:

h1 = h0.clone

(Verwirrenderweise heißt es in der Dokumentation zu clone, dass dies initialize_copyder Weg ist, dies zu überschreiben, aber der Link für diese Methode in Hashverweist Sie replacestattdessen auf ...)


1

Da die Standardklonmethode den eingefrorenen Zustand beibehält, eignet sie sich nicht zum Erstellen neuer unveränderlicher Objekte auf der Grundlage des Originalobjekts, wenn Sie möchten, dass sich die neuen Objekte geringfügig vom Original unterscheiden (wenn Sie zustandslose Programmierung wünschen).


1

Klon ist langsam. Für die Leistung sollte wahrscheinlich mit leerem Hash beginnen und zusammenführen. Deckt nicht den Fall verschachtelter Hashes ab ...

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 insgesamt (real)
  Klon 1.960000 0.080000 2.040000 (2.029604)
  Zusammenführen 1.690000 0.080000 1.770000 (1.767828)
  injizieren 3.120000 0.030000 3.150000 (3.152627)
  

1

Dies ist ein Sonderfall. Wenn Sie jedoch mit einem vordefinierten Hash beginnen, von dem Sie eine Kopie erstellen möchten, können Sie eine Methode erstellen, die einen Hash zurückgibt:

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

h1 = johns

Das besondere Szenario, das ich hatte, war, dass ich eine Sammlung von JSON-Schema-Hashes hatte, in denen einige Hashes aus anderen aufgebaut waren. Ich habe sie ursprünglich als Klassenvariablen definiert und bin auf dieses Kopierproblem gestoßen.


0

Sie können unten verwenden, um Hash-Objekte tief zu kopieren.

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

16
Dies ist ein Duplikat von Wayne Conrads Antwort.
Andrew Grimm

0

Da Ruby über eine Million Möglichkeiten verfügt, können Sie Enumerable auf folgende Weise verwenden:

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

-3

Alternativer Weg zu Deep_Copy, der für mich funktioniert hat.

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

Dies erzeugte eine deep_copy, da h2 unter Verwendung einer Array-Darstellung von h1 anstelle der Referenzen von h1 gebildet wird.


3
Klingt vielversprechend, funktioniert aber nicht, dies ist eine weitere flache Kopie
Ginty
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.