Làm cách nào để sao chép hàm băm trong Ruby?


197

Tôi sẽ thừa nhận rằng tôi là một người mới chơi ruby ​​(viết kịch bản cào, bây giờ). Trong hầu hết các ngôn ngữ, các hàm tạo sao chép rất dễ tìm. Nửa giờ tìm kiếm đã không tìm thấy nó trong ruby. Tôi muốn tạo một bản sao của hàm băm để tôi có thể sửa đổi nó mà không ảnh hưởng đến thể hiện ban đầu.

Một số phương pháp dự kiến ​​không hoạt động như dự định:

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

Trong khi đó, tôi đã dùng đến cách giải quyết không phù hợp này

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

Nếu bạn đang làm việc với Hashcác đối tượng đơn giản , câu trả lời được cung cấp là tốt. Nếu bạn đang xử lý các đối tượng giống Hash đến từ những nơi bạn không kiểm soát, bạn nên xem xét liệu bạn có muốn lớp singleton được liên kết với Hash trùng lặp hay không. Xem stackoverflow.com/questions/10183370/ Mạnh
Sim

Câu trả lời:


223

Các clonephương pháp là tiêu chuẩn của Ruby, được xây dựng theo cách để làm một nông-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"}

Lưu ý rằng hành vi có thể bị ghi đè:

Phương pháp này có thể có hành vi cụ thể của lớp. Nếu vậy, hành vi đó sẽ được ghi lại theo #initialize_copyphương thức của lớp.


Clone là một phương thức trên Object, BTW, vì vậy mọi thứ đều có quyền truy cập vào nó. Xem chi tiết API tại đây
Dylan Lacey

29
Thêm một bình luận rõ ràng hơn ở đây cho những người không đọc câu trả lời khác rằng đây là một bản sao nông.
grumpasaurus

Tài liệu #initialize_copy dường như không tồn tại cho Hash, mặc dù có một liên kết đến nó trên trang tài liệu Hash ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy
philwhln

14
Và đối với những người mới bắt đầu sử dụng Ruby khác, "bản sao nông" có nghĩa là mọi đối tượng dưới cấp đầu tiên vẫn là một tài liệu tham khảo.
RobW

9
Lưu ý rằng điều này không làm việc cho băm lồng nhau cho tôi (như đã đề cập trong các câu trả lời khác). Tôi đã sử dụng Marshal.load(Marshal.dump(h)).
bheeshmar

178

Như những người khác đã chỉ ra, clonesẽ làm điều đó. Hãy nhận biết rằng clonebăm tạo ra một bản sao nông. Điều đó có nghĩa là:

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

Điều đang xảy ra là các tham chiếu của hàm băm đang được sao chép, nhưng không phải là các đối tượng mà các tham chiếu tham chiếu đến.

Nếu bạn muốn có một bản sao sâu thì:

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_copylàm việc cho bất kỳ đối tượng có thể được sắp xếp theo thứ tự. Hầu hết các kiểu dữ liệu tích hợp (Array, Hash, String, & c.) Có thể được sắp xếp theo thứ tự.

Marshalling là tên của Ruby để tuần tự hóa . Với marshalling, đối tượng - với các đối tượng mà nó đề cập - được chuyển đổi thành một chuỗi byte; những byte đó sau đó được sử dụng để tạo một đối tượng khác giống như bản gốc.


Thật tuyệt khi bạn đã cung cấp thông tin về sao chép sâu, nhưng nó sẽ đi kèm với một cảnh báo rằng điều này có thể gây ra tác dụng phụ ngoài ý muốn (ví dụ: sửa đổi hàm băm sửa đổi cả hai). Mục đích chính của nhân bản băm là ngăn chặn sửa đổi bản gốc (đối với tính không thay đổi, v.v.).
K. Thợ mộc

6
@ K.Carpenter Không phải là một bản sao nông cạn chia sẻ các phần của bản gốc? Theo như tôi hiểu thì bản sao sâu sắc là một bản sao không chia sẻ bản gốc, vì vậy sửa đổi cái này sẽ không sửa đổi cái kia.
Wayne Conrad

1
Làm thế nào chính xác là Marshal.load(Marshal.dump(o))sao chép sâu? Tôi thực sự không thể hiểu những gì xảy ra đằng sau hậu trường
Muntasir Alam

Điều này cũng nổi bật là nếu bạn h1[:a] << 'bar'sửa đổi đối tượng ban đầu (chuỗi được trỏ bởi h1 [: a]) nhưng nếu bạn phải làm h1[:a] = "#{h1[:a]}bar"thay vào đó, bạn sẽ tạo một đối tượng chuỗi mới và chỉ h1[:a]vào đó, trong khi đó h2[:a]là vẫn chỉ vào chuỗi cũ (chưa sửa đổi).
Max Williams

@MuntasirAlam Tôi đã thêm một vài từ về những gì marshalling làm. Tôi hy vọng điều đó sẽ giúp.
Wayne Conrad


13

Hash có thể tạo một hàm băm mới từ hàm băm hiện có:

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
Lưu ý rằng điều này có cùng một vấn đề sao chép sâu như #clone và #dup.
forforf

3
@forforf là ​​chính xác. Đừng cố gắng sao chép cấu trúc dữ liệu nếu bạn không hiểu sâu so với bản sao nông.
James Moore

5

Tôi cũng là người mới chơi Ruby và tôi gặp phải vấn đề tương tự khi sao chép hàm băm. Sử dụng như sau. Tôi không biết gì về tốc độ của phương pháp này.

copy_of_original_hash = Hash.new.merge(original_hash)

3

Như đã đề cập trong phần Xem xét bảo mật của tài liệu Nguyên soái ,

Nếu bạn cần giải tuần tự hóa dữ liệu không đáng tin cậy, hãy sử dụng JSON hoặc định dạng tuần tự hóa khác chỉ có thể tải các loại đơn giản, 'nguyên thủy' như String, Array, Hash, v.v.

Dưới đây là một ví dụ về cách thực hiện nhân bản bằng JSON trong 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

Sử dụng Object#clone:

h1 = h0.clone

(Thật khó hiểu, tài liệu cho clonebiết đó initialize_copylà cách để ghi đè lên điều này, nhưng liên kết cho phương thức đó để Hashhướng dẫn bạn replacethay vào đó ...)


1

Vì phương pháp nhân bản tiêu chuẩn duy trì trạng thái đóng băng, không phù hợp để tạo các đối tượng bất biến mới dựa trên đối tượng ban đầu, nếu bạn muốn các đối tượng mới sẽ hơi khác so với ban đầu (nếu bạn thích lập trình không trạng thái).


1

Bản sao chậm. Đối với hiệu suất có lẽ nên bắt đầu với băm trống và hợp nhất. Không bao gồm trường hợp băm lồng nhau ...

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
  tổng số hệ thống người dùng (thực)
  nhân bản 1.960000 0.080000 2.040000 (2.029604)
  hợp nhất 1.690000 0.080000 1.770000 (1.767828)
  tiêm 3.120000 0.030000 3.150000 (3.152627)
  

1

Đây là trường hợp đặc biệt, nhưng nếu bạn bắt đầu với hàm băm được xác định trước mà bạn muốn lấy và tạo một bản sao, bạn có thể tạo một phương thức trả về hàm băm:

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

h1 = johns

Kịch bản cụ thể mà tôi có là tôi đã có một bộ băm lược đồ JSON trong đó một số băm được tạo ra từ những cái khác. Ban đầu tôi đã định nghĩa chúng là các biến lớp và gặp vấn đề sao chép này.


0

bạn có thể sử dụng bên dưới để sao chép sâu các đối tượng Hash.

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

16
Đây là một bản sao câu trả lời của Wayne Conrad.
Andrew Grimm

0

Vì Ruby có hàng triệu cách để làm điều đó, đây là một cách khác bằng cách sử dụng Enumerable:

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

-3

Cách khác để Deep_Copy làm việc cho tôi.

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

Điều này tạo ra một deep_copy vì h2 được hình thành bằng cách sử dụng một đại diện mảng của h1 thay vì tham chiếu của h1.


3
Âm thanh đầy hứa hẹn nhưng không hoạt động, đây là một bản sao nông cạn khác
Ginty
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.