Bagaimana cara menyalin hash di Ruby?

197

Saya akan mengakui bahwa saya sedikit pemula yang ruby ​​(menulis skrip menyapu, sekarang). Dalam sebagian besar bahasa, copy constructor mudah ditemukan. Setengah jam pencarian tidak menemukannya di ruby. Saya ingin membuat salinan hash sehingga saya dapat memodifikasinya tanpa memengaruhi instance asli.

Beberapa metode yang diharapkan yang tidak berfungsi sebagaimana dimaksud:

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

Sementara itu, saya telah menggunakan solusi yang salah ini

def copyhash(inputhash)
  h = Hash.new
  inputhash.each do |pair|
    h.store(pair[0], pair[1])
  end
  return h
end
Terjal
sumber
Jika Anda berurusan dengan Hashobjek biasa , jawaban yang diberikan baik. Jika Anda berurusan dengan objek mirip Hash yang berasal dari tempat yang tidak Anda kontrol, Anda harus mempertimbangkan apakah Anda ingin kelas tunggal yang terkait dengan Hash digandakan atau tidak. Lihat stackoverflow.com/questions/10183370/...
Sim

Jawaban:

223

The cloneMetode standar Ruby, built-in cara untuk melakukan dangkal-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"}

Perhatikan bahwa perilaku mungkin diganti:

Metode ini mungkin memiliki perilaku khusus kelas. Jika demikian, perilaku itu akan didokumentasikan di bawah #initialize_copymetode kelas.

Mark Rushakoff
sumber
Clone adalah metode pada Object, BTW, jadi semuanya memiliki akses ke sana. Lihat detail API di sini
Dylan Lacey
29
Menambahkan komentar yang lebih eksplisit di sini untuk mereka yang tidak membaca jawaban lain bahwa ini adalah salinan yang dangkal.
grumpasaurus
Dokumentasi #initialize_copy tampaknya tidak ada untuk Hash, meskipun ada tautan ke sana di halaman Hash doc ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln
14
Dan untuk pemula Ruby lainnya, "salinan dangkal" berarti bahwa setiap objek di bawah level pertama masih menjadi referensi.
RobW
9
Perhatikan ini tidak berfungsi untuk hash bersarang untuk saya (seperti yang disebutkan dalam jawaban lain). Saya menggunakan Marshal.load(Marshal.dump(h)).
bheeshmar
178

Seperti yang telah ditunjukkan orang lain, cloneakan melakukannya. Ketahuilah bahwa clonehash membuat salinan yang dangkal. Artinya:

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

Apa yang terjadi adalah bahwa referensi hash sedang disalin, tetapi bukan objek yang merujuk referensi.

Jika Anda ingin salinan yang dalam, maka:

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_copybekerja untuk objek apa pun yang dapat disusun. Sebagian besar tipe data bawaan (Array, Hash, String, & c.) Dapat disusun.

Marshalling adalah nama Ruby untuk serialisasi . Dengan marshalling, objek - dengan objek yang dirujuk - dikonversi ke serangkaian byte; byte tersebut kemudian digunakan untuk membuat objek lain seperti aslinya.

Wayne Conrad
sumber
Sangat menyenangkan bahwa Anda telah memberikan informasi tentang penyalinan yang dalam, tetapi harus disertai dengan peringatan bahwa ini dapat menyebabkan efek samping yang tidak diinginkan (misalnya, memodifikasi hash dengan memodifikasi keduanya). Tujuan utama kloning hash adalah mencegah modifikasi dari yang asli (untuk kekekalan, dll).
K. Carpenter
6
@ K.Carpenter Bukankah itu salinan dangkal yang berbagi bagian dari yang asli? Salinan yang dalam, seperti yang saya mengerti, adalah salinan yang tidak berbagi bagian dari aslinya, jadi memodifikasi yang satu tidak akan mengubah yang lain.
Wayne Conrad
1
Bagaimana tepatnya Marshal.load(Marshal.dump(o))penyalinan yang dalam? Saya benar-benar tidak mengerti apa yang terjadi di balik layar
Muntasir Alam
Apa yang disoroti juga adalah bahwa jika Anda h1[:a] << 'bar'mengubah objek asli (string ditunjukkan oleh h1 [: a]) tetapi jika Anda harus melakukannya h1[:a] = "#{h1[:a]}bar", Anda akan membuat objek string baru, dan arahkan h1[:a]ke sana, sementara itu h2[:a]adalah masih menunjuk ke string lama (tidak dimodifikasi).
Max Williams
@MuntasirAlam Saya menambahkan beberapa kata tentang apa yang dilakukan marshalling. Saya harap itu membantu.
Wayne Conrad
73

Jika Anda menggunakan Rails, Anda dapat melakukannya:

h1 = h0.deep_dup

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

lmanners
sumber
2
Rails 3 memiliki masalah dengan deep_duping array di dalam Hash. Rails 4 memperbaiki ini.
pdobb
1
Terima kasih untuk menunjukkan ini, hash saya masih terpengaruh saat menggunakan dup atau klon
Esgi Dendyanri
13

Hash dapat membuat hash baru dari hash yang ada:

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
sumber
24
Perhatikan bahwa ini memiliki masalah penyalinan dalam yang sama dengan #clone dan #dup.
forforf
3
@forforf sudah benar. Jangan mencoba menyalin struktur data jika Anda tidak memahami salinan yang dalam vs yang dangkal.
James Moore
5

Saya juga seorang pemula untuk Ruby dan saya menghadapi masalah serupa dalam menduplikasi hash. Gunakan yang berikut ini. Saya tidak tahu tentang kecepatan metode ini.

copy_of_original_hash = Hash.new.merge(original_hash)
Kapil Aggarwal
sumber
3

Seperti disebutkan di bagian Pertimbangan Keamanan dari dokumentasi Marshal ,

Jika Anda perlu melakukan deserialisasi data yang tidak terpercaya, gunakan JSON atau format serialisasi lain yang hanya dapat memuat tipe 'primitif' sederhana seperti String, Array, Hash, dll.

Berikut adalah contoh cara melakukan kloning menggunakan JSON di 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"}
Pembuat Tongkat
sumber
1

Gunakan Object#clone:

h1 = h0.clone

(Yang membingungkan, dokumentasi untuk clonemengatakan itu initialize_copyadalah cara untuk menimpanya, tetapi tautan untuk metode itu Hashmengarahkan Anda untuk replacesebaliknya ...)

Josh Lee
sumber
1

Karena metode kloning standar mempertahankan keadaan beku, itu tidak cocok untuk membuat objek baru yang tidak dapat diubah berdasarkan objek asli, jika Anda ingin objek baru sedikit berbeda dari yang asli (jika Anda suka pemrograman stateless).

kuonirat
sumber
1

Klon lambat. Untuk kinerja mungkin harus dimulai dengan hash dan penggabungan kosong. Tidak mencakup kasus hash bersarang ...

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
  total sistem pengguna bangku (nyata)
  klon 1.960000 0.080000 2.040000 (2.029604)
  menggabungkan 1,690000 0,080000 1,770000 (1,767828)
  menyuntikkan 3.120000 0.030000 3.150000 (3.152627)
  
Justin
sumber
1

Ini adalah kasus khusus, tetapi jika Anda mulai dengan hash yang telah ditentukan yang ingin Anda ambil dan buat salinannya, Anda bisa membuat metode yang mengembalikan hash:

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

h1 = johns

Skenario khusus yang saya miliki adalah saya memiliki koleksi hash-skema JSON di mana beberapa hash dibangun dari yang lain. Saya awalnya mendefinisikan mereka sebagai variabel kelas dan mengalami masalah salinan ini.

grumpasaurus
sumber
0

Anda dapat menggunakan di bawah ini untuk menyalin objek Hash.

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
ktsujister
sumber
16
Ini adalah duplikat dari jawaban Wayne Conrad.
Andrew Grimm
0

Karena Ruby memiliki sejuta cara untuk melakukannya, inilah cara lain menggunakan Enumerable:

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

Cara alternatif untuk Deep_Copy yang bekerja untuk saya.

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

Ini menghasilkan deep_copy karena h2 dibentuk menggunakan representasi array dari referensi h1 daripada h1.

pengguna2521734
sumber
3
Kedengarannya menjanjikan tetapi tidak berhasil, ini adalah salinan dangkal lainnya
Ginty