ฉันจะคัดลอกแฮชใน Ruby ได้อย่างไร


197

ฉันจะยอมรับว่าฉันเป็นมือใหม่ทับทิม (เขียนสคริปต์คราดตอนนี้) ในภาษาส่วนใหญ่ตัวสร้างสำเนาจะค้นหาได้ง่าย ครึ่งชั่วโมงของการค้นหาไม่พบว่าเป็นทับทิม ฉันต้องการสร้างสำเนาของแฮชเพื่อให้สามารถแก้ไขได้โดยไม่กระทบกับอินสแตนซ์ดั้งเดิม

วิธีการที่คาดหวังบางอย่างที่ไม่ทำงานตามที่ต้องการ:

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

ในระหว่างนี้ฉันใช้วิธีแก้ปัญหาที่ไม่เหมาะสมนี้

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

หากคุณจัดการกับHashวัตถุธรรมดาคำตอบที่ให้นั้นดี หากคุณกำลังจัดการกับวัตถุที่คล้ายกับ Hash ที่มาจากสถานที่ที่คุณไม่สามารถควบคุมได้คุณควรพิจารณาว่าคุณต้องการคลาส singleton ที่เกี่ยวข้องกับ Hash ที่ทำซ้ำหรือไม่ ดูstackoverflow.com/questions/10183370/…
Sim

คำตอบ:


223

cloneวิธีการเป็นมาตรฐานของรูบี้ในตัววิธีการทำตื้นสำเนา :

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

โปรดทราบว่าพฤติกรรมดังกล่าวอาจถูกเขียนทับ:

วิธีนี้อาจมีลักษณะการทำงานเฉพาะคลาส ถ้าเป็นเช่นนั้นพฤติกรรมนั้นจะได้รับการบันทึกไว้ภายใต้#initialize_copyวิธีการของชั้นเรียน


Clone เป็นวิธีการใน Object, BTW ดังนั้นทุกอย่างสามารถเข้าถึงได้ ดูรายละเอียด API ได้ที่นี่
Dylan Lacey

30
การเพิ่มความคิดเห็นที่ชัดเจนมากขึ้นที่นี่สำหรับผู้ที่ไม่ได้อ่านคำตอบอื่น ๆ ว่านี่เป็นสำเนาตื้น
Grumpasaurus

#initialize_copy ดูเหมือนจะไม่มีเอกสารสำหรับแฮชแม้ว่าจะมีลิงก์ไปยังหน้าเอกสารแฮชruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln

14
และสำหรับผู้เริ่มต้นทับทิมคนอื่น ๆ "การคัดลอกตื้น" หมายความว่าทุกวัตถุที่อยู่ต่ำกว่าระดับแรกยังคงเป็นข้อมูลอ้างอิง
RobW

9
โปรดทราบว่านี่ใช้ไม่ได้กับแฮชซ้อนสำหรับฉัน (ดังที่ได้กล่าวไว้ในคำตอบอื่น ๆ ) Marshal.load(Marshal.dump(h))ผมใช้
bheeshmar

178

ตามที่คนอื่น ๆ ได้ชี้ให้เห็นcloneจะทำ ระวังว่าcloneแฮชทำสำเนาตื้น ๆ กล่าวคือ:

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

สิ่งที่เกิดขึ้นคือมีการคัดลอกข้อมูลอ้างอิงของแฮช แต่ไม่ใช่วัตถุที่มีการอ้างอิงถึง

หากคุณต้องการสำเนาลึก:

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_copyทำงานได้กับวัตถุใด ๆ ที่สามารถถูกจัดเรียง ชนิดข้อมูลที่มีอยู่แล้วส่วนใหญ่ (Array, Hash, String, & c.) สามารถจัดเรียงได้

นายอำเภอเป็นชื่อทับทิมสำหรับอนุกรม ด้วย marshalling วัตถุ - กับวัตถุที่อ้างถึง - จะถูกแปลงเป็นชุดไบต์ ไบต์เหล่านั้นจะถูกใช้เพื่อสร้างวัตถุอื่นเหมือนต้นฉบับ


เป็นเรื่องดีที่คุณได้ให้ข้อมูลเกี่ยวกับการทำสำเนาแบบลึก แต่ควรมีคำเตือนว่าสิ่งนี้อาจทำให้เกิดผลข้างเคียงที่ไม่ได้ตั้งใจ (ตัวอย่างเช่นการดัดแปลงแฮชปรับเปลี่ยนทั้งสองอย่าง) วัตถุประสงค์หลักของการโคลนแฮชคือการป้องกันการดัดแปลงของต้นฉบับ (สำหรับการเปลี่ยนรูปแบบ ฯลฯ )
เค. คาร์เพนเตอร์

6
@ K.Carpenter มันเป็นสำเนาตื้น ๆที่แบ่งปันบางส่วนของต้นฉบับหรือไม่? ตามที่ฉันเข้าใจแล้วเป็นสำเนาที่แชร์ส่วนหนึ่งของต้นฉบับไม่ได้ดังนั้นการแก้ไขหนึ่งจะไม่แก้ไขอีก
Wayne Conrad

1
การMarshal.load(Marshal.dump(o))ทำสำเนาลึกแค่ไหน ฉันไม่เข้าใจจริงๆว่าเกิดอะไรขึ้นเบื้องหลัง
Muntasir Alam

สิ่งที่ไฮไลท์นี้เป็นอย่างดีก็คือว่าถ้าคุณทำh1[:a] << 'bar'คุณปรับเปลี่ยนวัตถุเดิม (สตริงชี้ไปตาม h1 [: เป็น]) แต่ถ้าคุณทำh1[:a] = "#{h1[:a]}bar"แทนคุณจะสร้างวัตถุสตริงใหม่และจุดh1[:a]ที่ว่าในขณะที่h2[:a]มี ยังคงชี้ไปที่สตริงเก่า (ไม่ได้แก้ไข)
Max Williams

@MuntasirAlam ฉันได้เพิ่มบางคำเกี่ยวกับ marshalling ฉันหวังว่าจะช่วย
Wayne Conrad

73

หากคุณใช้ Rails คุณสามารถทำได้:

h1 = h0.deep_dup

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


2
Rails 3 มีปัญหากับชุดข้อมูล deep_duping ภายใน Hashes Rails 4 แก้ไขสิ่งนี้
pdobb

1
ขอบคุณสำหรับจุดนี้แฮชของฉันยังคงได้รับผลกระทบเมื่อใช้ dup หรือ clone
Esgi Dendyanri

13

แฮชสามารถสร้างแฮชใหม่จากแฮชที่มีอยู่:

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
โปรดทราบว่านี่เป็นปัญหาการคัดลอกที่ลึกเช่นเดียวกับ #clone และ #dup
forforf

3
@forforf ถูกต้อง อย่าพยายามคัดลอกโครงสร้างข้อมูลถ้าคุณไม่เข้าใจการคัดลอกที่ลึกและตื้น
James Moore

5

ฉันยังเป็นมือใหม่สำหรับ Ruby และฉันก็ประสบปัญหาคล้ายกันในการทำแฮชซ้ำ ใช้สิ่งต่อไปนี้ ฉันไม่รู้เกี่ยวกับความเร็วของวิธีนี้

copy_of_original_hash = Hash.new.merge(original_hash)

3

เป็นที่กล่าวถึงในส่วนการรักษาความปลอดภัยการพิจารณาเอกสารจอมพล ,

หากคุณต้องการลบข้อมูลที่ไม่น่าเชื่อถือให้ใช้ JSON หรือรูปแบบการทำให้เป็นอนุกรมอื่นที่สามารถโหลดได้ง่ายประเภท 'ดั้งเดิม' เช่น String, Array, Hash เป็นต้น

นี่คือตัวอย่างเกี่ยวกับวิธีการโคลนโดยใช้ JSON ใน 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

การใช้Object#clone:

h1 = h0.clone

(สับสนสำหรับเอกสารที่cloneบอกว่าinitialize_copyเป็นวิธีการแทนที่นี้ แต่ลิงค์สำหรับวิธีการที่จะHashนำคุณไปreplaceแทน ... )


1

เนื่องจากวิธีการโคลนแบบมาตรฐานรักษาสถานะการแช่แข็งจึงไม่เหมาะสำหรับการสร้างวัตถุที่ไม่เปลี่ยนรูปแบบใหม่โดยยึดตามวัตถุต้นฉบับหากคุณต้องการให้วัตถุใหม่แตกต่างจากของจริงเล็กน้อยเล็กน้อย (ถ้าคุณชอบการเขียนโปรแกรมไร้สัญชาติ)


1

โคลนช้า เพื่อประสิทธิภาพควรเริ่มต้นด้วยแฮชว่างและผสาน ไม่ครอบคลุมกรณีของแฮชซ้อน ...

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
  ระบบผู้ใช้แบบรวม (จริง)
  โคลน 1.960000 0.080000 2.040000 (2.029604)
  ผสาน 1.690000 0.080000 1.770000 (1.767828)
  ฉีด 3.120000 0.030000 3.150000 (3.152627)
  

1

นี่เป็นกรณีพิเศษ แต่ถ้าคุณเริ่มต้นด้วยแฮชที่กำหนดไว้ล่วงหน้าที่คุณต้องการคว้าและทำสำเนาคุณสามารถสร้างวิธีที่ส่งคืนแฮชได้:

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

h1 = johns

สถานการณ์เฉพาะที่ฉันมีคือฉันมีชุดแฮช JSON-schema ซึ่งมีแฮชบางตัวที่สร้างขึ้นมา เริ่มแรกฉันกำหนดให้พวกเขาเป็นตัวแปรคลาสและพบปัญหาการคัดลอก



0

เนื่องจาก Ruby มีวิธีนับล้านที่จะทำได้นี่เป็นอีกวิธีหนึ่งในการใช้ Enumerable:

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

-3

ทางเลือกอื่นในการ Deep_Copy ที่เหมาะกับฉัน

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

สิ่งนี้สร้าง deep_copy ตั้งแต่ h2 เกิดขึ้นโดยใช้การแทนค่าอาร์เรย์ของ h1 แทนที่จะเป็นการอ้างอิงของ h1


3
ฟังดูมีแนวโน้ม แต่ใช้งานไม่ได้นี่เป็นอีกฉบับคัดลอกตื้น
Ginty
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.