Jak skopiować skrót w Ruby?

197

Przyznaję, że jestem trochę rubinowym nowicjuszem (teraz piszę skrypty rake). W większości języków łatwo jest znaleźć konstruktory kopii. Pół godziny poszukiwań nie znalazło go w rubinie. Chcę utworzyć kopię skrótu, aby móc ją zmodyfikować bez wpływu na oryginalne wystąpienie.

Niektóre oczekiwane metody, które nie działają zgodnie z przeznaczeniem:

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

W międzyczasie skorzystałem z tego nieeleganckiego obejścia

def copyhash(inputhash)
  h = Hash.new
  inputhash.each do |pair|
    h.store(pair[0], pair[1])
  end
  return h
end
Stromy
źródło
Jeśli masz do czynienia z prostymi Hashprzedmiotami, podana odpowiedź jest dobra. Jeśli masz do czynienia z obiektami podobnymi do Hash, które pochodzą z miejsc, nad którymi nie kontrolujesz, powinieneś rozważyć, czy chcesz, aby klasa singletonowa była powiązana z Hash, czy nie. Zobacz stackoverflow.com/questions/10183370/…
Sim

Odpowiedzi:

223

cloneMetoda jest standardowy Ruby, wbudowany sposób zrobić płytkich kopii :

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

Pamiętaj, że zachowanie może zostać zastąpione:

Ta metoda może mieć zachowanie specyficzne dla klasy. Jeśli tak, to zachowanie zostanie udokumentowane zgodnie z #initialize_copymetodą klasy.

Mark Rushakoff
źródło
Klonowanie jest metodą opartą na Object, BTW, więc wszystko ma do niej dostęp. Zobacz szczegóły API tutaj
Dylan Lacey
29
Dodanie tutaj bardziej wyraźnego komentarza dla tych, którzy nie czytają innych odpowiedzi, że jest to płytka kopia.
grumpasaurus,
#initialize_copy dokumentacja wydaje się nie istnieć dla Hash, mimo że istnieje link do niej na stronie dokumentu Hash ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln
14
A dla innych początkujących w Ruby „płytka kopia” oznacza, że ​​każdy obiekt poniżej pierwszego poziomu jest nadal referencją.
RobW
9
Zauważ, że to nie działało dla mnie zagnieżdżonych skrótów (jak wspomniano w innych odpowiedziach). Kiedyś Marshal.load(Marshal.dump(h)).
bheeshmar
178

Jak zauważyli inni, clonezrobią to. Pamiętaj, że cloneskrót tworzy płytką kopię. To jest do powiedzenia:

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

Dzieje się tak, że odwołania do skrótu są kopiowane, ale nie do obiektów, do których się odwołują.

Jeśli chcesz głęboką kopię, to:

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_copydziała dla każdego obiektu, który może być zestawiony. Większość wbudowanych typów danych (Array, Hash, String i c.) Można zestawiać.

Marshalling to nazwa Ruby do serializacji . Podczas marshallingu obiekt - wraz z obiektami, do których się odnosi - jest konwertowany na szereg bajtów; bajty te są następnie wykorzystywane do utworzenia kolejnego obiektu, takiego jak oryginał.

Wayne Conrad
źródło
Fajnie, że podałeś informacje o głębokim kopiowaniu, ale powinno ono zawierać ostrzeżenie, że może to powodować niezamierzone skutki uboczne (na przykład modyfikacja jednego z skrótów modyfikuje oba). Głównym celem klonowania skrótu jest zapobieganie modyfikacji oryginału (w celu niezmienności itp.).
K. Carpenter,
6
@ K.Carpenter Czy to nie jest płytka kopia, która dzieli części oryginału? Głęboka kopia, jak rozumiem, jest kopią, która nie dzieli żadnej części oryginału, więc modyfikacja jednej nie spowoduje modyfikacji drugiej.
Wayne Conrad,
1
Jak dokładnie jest Marshal.load(Marshal.dump(o))głębokie kopiowanie? Naprawdę nie rozumiem, co dzieje się za kulisami
Muntasir Alam
Co Podkreśla także, że jeśli nie h1[:a] << 'bar'można modyfikować oryginalnego obiektu (ciąg wskazywanego przez h1 [: a]), ale jeśli było zrobić h1[:a] = "#{h1[:a]}bar"zamiast tego, by utworzyć nowy obiekt string, a punkt h1[:a]na, że podczas h2[:a]Is wciąż wskazuje stary (niezmodyfikowany) ciąg.
Max Williams
@MuntasirAlam Dodałem kilka słów o tym, co robi marshalling. Mam nadzieję że to pomogło.
Wayne Conrad
13

Hash może utworzyć nowy skrót z istniejącego skrótu:

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
źródło
24
Zauważ, że ma to ten sam problem z głębokim kopiowaniem jak #clone i #dup.
forforf
3
@forforf jest poprawny. Nie próbuj kopiować struktur danych, jeśli nie rozumiesz głębokiej kontra płytkiej kopii.
James Moore,
5

Jestem również nowicjuszem w Ruby i napotkałem podobne problemy przy powielaniu skrótu. Użyj następujących. Nie mam pojęcia o szybkości tej metody.

copy_of_original_hash = Hash.new.merge(original_hash)
Kapil Aggarwal
źródło
3

Jak wspomniano w części „Zagadnienia bezpieczeństwa” dokumentacji Marszałka ,

Jeśli chcesz dokonać deserializacji niezaufanych danych, użyj JSON lub innego formatu serializacji, który jest w stanie załadować tylko proste, „prymitywne” typy, takie jak String, Array, Hash itp.

Oto przykład, jak wykonać klonowanie przy użyciu JSON w 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"}
Wand Maker
źródło
1

Użyj Object#clone:

h1 = h0.clone

(Mylące jest to, że dokumentacja clonemówi, że initialize_copyjest to sposób na obejście tego, ale link do tej metody Hashprowadzi do replacetego ...)

Josh Lee
źródło
1

Ponieważ standardowa metoda klonowania zachowuje stan zamrożenia, nie nadaje się do tworzenia nowych niezmiennych obiektów na podstawie oryginalnego obiektu, jeśli chciałbyś, aby nowe obiekty różniły się nieco od oryginału (jeśli lubisz programowanie bezstanowe).

kuonirat
źródło
1

Klon jest wolny. Aby wydajność prawdopodobnie zaczęła się od pustego skrótu i ​​scalenia. Nie obejmuje przypadku zagnieżdżonych skrótów ...

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
  system użytkownika ławki ogółem (rzeczywisty)
  klon 1.960000 0.080000 2.040000 (2.029604)
  scal 1.690000 0,080000 1,770000 (1,767828)
  wstrzyknąć 3.120000 0,030000 3,150000 (3,152627)
  
Justin
źródło
1

Jest to szczególny przypadek, ale jeśli zaczynasz od wstępnie zdefiniowanego skrótu, który chcesz pobrać i wykonać kopię, możesz utworzyć metodę zwracającą skrót:

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

h1 = johns

Szczególnym scenariuszem, który miałem, był zbiór skrótów schematu JSON, w którym niektóre skróty budowały inne. Początkowo definiowałem je jako zmienne klasowe i natrafiłem na ten problem z kopiowaniem.

Grumpasaurus
źródło
0

możesz użyć poniżej, aby głęboko skopiować obiekty mieszania.

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
ktsujister
źródło
16
Jest to kopia odpowiedzi Wayne'a Conrada.
Andrew Grimm,
0

Ponieważ Ruby ma na to milion sposobów, oto inny sposób użycia Enumerable:

h0 = {  "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1 = h0.inject({}) do |new, (name, value)| 
    new[name] = value;
    new 
end
Rohit
źródło
-3

Alternatywny sposób dla Deep_Copy, który działał dla mnie.

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

Spowodowało to utworzenie deep_copy, ponieważ h2 jest tworzone przy użyciu reprezentacji tablicowej h1 zamiast referencji h1.

użytkownik2521734
źródło
3
Brzmi obiecująco, ale nie działa, to kolejna płytka kopia
Ginty,