Ruby'de bir karmayı nasıl kopyalarım?


197

Biraz yakut bir acemi olduğumu itiraf edeceğim (şimdi komisyon senaryoları yazıyorum). Çoğu dilde, kopya oluşturucuları bulmak kolaydır. Yarım saat arama yakutta bulamadı. Özgün örneği etkilemeden değiştirebilmem için karma dosyasının bir kopyasını oluşturmak istiyorum.

Amaçlandığı gibi çalışmayan bazı beklenen yöntemler:

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

Bu arada, bu yetersiz geçici çözüme başvurdum

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

Düz Hashnesnelerle uğraşıyorsanız , verilen cevap iyidir. Kontrol etmediğin yerlerden gelen Hash benzeri nesnelerle uğraşıyorsan, Hash ile ilişkili singleton sınıfının çoğaltılmasını isteyip istemediğini düşünmelisin. Bkz. Stackoverflow.com/questions/10183370/…
Sim

Yanıtlar:


223

cloneYöntem dahili şekilde bir yapılacak Ruby'nin standardıdır sığ-kopyasını :

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

Davranışın geçersiz kılınabileceğini unutmayın:

Bu yöntem sınıfa özgü davranışlara sahip olabilir. Eğer öyleyse, bu davranış #initialize_copysınıfın yöntemi altında belgelenecektir .


Klon, Object, BTW üzerinde bir yöntemdir, bu nedenle her şeyin ona erişimi vardır. API ayrıntılarını burada görebilirsiniz
Dylan Lacey

30
Bunun başka cevapları okumayanlar için buraya daha açık bir yorum eklemek sığ bir kopya yapar.
grumpasaurus

Hash dokümanı sayfasında bir bağlantı olmasına rağmen #initialize_copy belgeleri Hash için mevcut görünmüyor ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln

14
Ve diğer Ruby yeni başlayanlar için "sığ kopya", birinci seviyenin altındaki her nesnenin hala bir referans olduğu anlamına gelir.
RobW

9
Bu benim için iç içe karma için işe yaramadı unutmayın (diğer cevaplarda belirtildiği gibi). Ben kullandım Marshal.load(Marshal.dump(h)).
bheeshmar

178

Diğerlerinin de belirttiği gibi, cloneyapacak. Unutmayın clonekarmasının bir yüzeysel bir kopyasını yapar. Demek ki:

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

Olan şey, karma referanslarının kopyalanmasıdır, ancak referansların referans aldığı nesneler değildir.

Derin bir kopya istiyorsanız:

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_copysıralanabilir herhangi bir nesne için çalışır. Çoğu yerleşik veri türü (Array, Hash, String ve c) sıralanabilir.

Marshalling , Ruby'nin serileştirme adıdır . Sıralama ile, nesne - atıfta bulunduğu nesnelerle - bir dizi bayta dönüştürülür; bu baytlar daha sonra orijinal gibi başka bir nesne oluşturmak için kullanılır.


Derin kopyalama hakkında bilgi vermeniz güzel, ancak bunun istenmeyen yan etkilere neden olabileceği konusunda bir uyarı ile gelmelidir (örneğin, her iki karma değerini de değiştirmek). Bir karmayı klonlamanın temel amacı orijinalin değiştirilmesini önlemektir (değişmezlik vb. İçin).
K. Carpenter

6
@ K.Carpenter Orijinalin bir bölümünü paylaşan sığ bir kopya değil mi? Derin kopya, anladığım kadarıyla, orijinalin hiçbir bölümünü paylaşmayan bir kopyadır, bu yüzden birini değiştirmek diğerini değiştirmez.
Wayne Conrad

1
Marshal.load(Marshal.dump(o))Derin fotokopi tam olarak nasıl ? Perde arkasında neler olduğunu gerçekten anlayamıyorum
Muntasir Alam

Ne bu olayları yanı bunu yaparsanız olmasıdır h1[:a] << 'bar'orijinal nesneyi değiştirmek (string h1 tarafından işaret [a]) ancak bunu olsaydı h1[:a] = "#{h1[:a]}bar"bunun yerine, yeni bir dize nesne oluşturmak ve nokta olurdu h1[:a], şuna iken h2[:a]DİR hala eski (değiştirilmemiş) dizeyi işaret ediyor.
Max Williams

@MuntasirAlam Marshall'ın ne yaptığı hakkında birkaç kelime ekledim. Umarım bu yardımcı olur.
Wayne Conrad


13

Hash, mevcut bir karmadan yeni bir karma oluşturabilir:

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
Bunun #clone ve #dup ile aynı derin kopyalama sorununa sahip olduğunu unutmayın.
forforf

3
@forforf doğrudur. Derin ve sığ kopyayı anlamadıysanız veri yapılarını kopyalamaya çalışmayın.
James Moore

5

Ben de Ruby'ye yeni başladım ve bir hash kopyalarken benzer sorunlarla karşılaştım. Aşağıdakileri kullanın. Bu yöntemin hızı hakkında hiçbir fikrim yok.

copy_of_original_hash = Hash.new.merge(original_hash)

3

Mareşal belgelerinin Güvenlik Hususları bölümünde belirtildiği gibi ,

Güvenilmeyen verilerin serisini kaldırmanız gerekiyorsa, JSON veya yalnızca String, Array, Hash, vb. Gibi basit, 'ilkel' türleri yükleyebilen başka bir serileştirme biçimi kullanın.

Ruby'de JSON kullanarak klonlamanın nasıl yapılacağıyla ilgili bir örnek:

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

Kullanım Object#clone:

h1 = h0.clone

(Kafa karıştırıcı bir şekilde, belgelerin bunu geçersiz kılmanın yolu cloneolduğunu söylüyor initialize_copy, ancak bu yöntemin bağlantısı Hashsizi replacebunun yerine yönlendiriyor ...)


1

Standart klonlama yöntemi donmuş durumu koruduğundan, yeni nesnelerin orijinalden biraz farklı olmasını isterseniz (vatansız programlama istiyorsanız) orijinal nesneye dayanan yeni sabit nesneler oluşturmak için uygun değildir.


1

Klonlama yavaş. Performans muhtemelen boş hash ile başlamalı ve birleştirilmelidir. İç içe geçmiş karmaları kapsamaz ...

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
  tezgah kullanıcı sistemi toplamı (gerçek)
  klon 1.960000 0.080000 2.040000 (2.029604)
  birleştirme 1.690000 0.080000 1.770000 (1.767828)
  enjekte edin 3.120000 0.030000 3.150000 (3.152627)
  

1

Bu özel bir durumdur, ancak kapmak ve kopyasını oluşturmak istediğiniz önceden tanımlanmış bir karma ile başlıyorsanız, karma döndüren bir yöntem oluşturabilirsiniz:

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

h1 = johns

Sahip olduğum özel senaryo, bazı karmaların başkalarını oluşturduğu JSON-şema karmalarının bir koleksiyonuna sahip olmamdı. Başlangıçta onları sınıf değişkenleri olarak tanımlıyordum ve bu kopyalama sorunu ile karşılaştım.


0

Hash nesnelerini derin kopyalamak için aşağıda kullanabilirsiniz.

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

16
Bu Wayne Conrad'ın cevabının bir kopyası.
Andrew Grimm

0

Ruby'nin bunu yapmak için milyonlarca yolu olduğundan, Enumerable'ı kullanmanın başka bir yolu:

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

-3

Benim için çalışan Deep_Copy için alternatif bir yol.

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

Bu, bir derin_kopi üretti, çünkü h2, h1'in referansları yerine h1'in bir dizi temsili kullanılarak oluşturulmuştur.


3
Umut verici geliyor ama çalışmıyor, bu başka bir sığ kopya
Ginty
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.