可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'll admit that I'm a bit of a ruby newbie (writing rake scripts, now). In most languages, copy constructors are easy to find. Half an hour of searching didn't find it in ruby. I want to create a copy of the hash so that I can modify it without affecting the original instance.
Some expected methods that don't work as intended:
h0 = { "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1=Hash.new(h0)
h2=h1.to_hash
In the meantime, I've resorted to this inelegant workaround
def copyhash(inputhash)
h = Hash.new
inputhash.each do |pair|
h.store(pair[0], pair[1])
end
return h
end
回答1:
The clone
method is Ruby's standard, built-in way to do a shallow-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"}
Note that the behavior may be overridden:
This method may have class-specific behavior. If so, that behavior will be documented under the #initialize_copy
method of the class.
回答2:
As others have pointed out, clone
will do it. Be aware that clone
of a hash makes a shallow copy. That is to say:
h1 = {:a => 'foo'}
h2 = h1.clone
h1[:a] << 'bar'
p h2 # => {:a=>"foobar"}
What's happening is that the hash's references are being copied, but not the objects that the references refer to.
If you want a deep copy then:
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
works for any object that can be marshalled. Most built-in data types (Array, Hash, String, &c.) can be marshalled.
Marshalling is Ruby's name for serialization. With marshalling, the object--with the objects it refers to--is converted to a series of bytes; those bytes are then used to create another object like the original.
回答3:
If you're using Rails you can do:
h1 = h0.deep_dup
http://apidock.com/rails/Hash/deep_dup
回答4:
Hash can create a new hash from an existing hash:
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
回答5:
I am also a newbie to Ruby and I faced similar issues in duplicating a hash. Use the following. I've got no idea about the speed of this method.
copy_of_original_hash = Hash.new.merge(original_hash)
回答6:
As mentioned in Security Considerations section of Marshal documentation,
If you need to deserialize untrusted data, use JSON or another
serialization format that is only able to load simple, ‘primitive’
types such as String, Array, Hash, etc.
Here is an example on how to do cloning using JSON in 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"}
回答7:
Use Object#clone
:
h1 = h0.clone
(Confusingly, the documentation for clone
says that initialize_copy
is the way to override this, but the link for that method in Hash
directs you to replace
instead...)
回答8:
you can use below to deep copy Hash objects.
deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
回答9:
Since standard cloning method preserves the frozen state, it is not suitable for creating new immutable objects basing on the original object, if you would like the new objects be slightly different than the original (if you like stateless programming).
回答10:
Clone is slow. For performance should probably start with blank hash and merge. Doesn't cover case of nested hashes...
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
bench user system total ( real)
clone 1.960000 0.080000 2.040000 ( 2.029604)
merge 1.690000 0.080000 1.770000 ( 1.767828)
inject 3.120000 0.030000 3.150000 ( 3.152627)
回答11:
Since Ruby has a million ways to do it, here's another way using Enumerable:
h0 = { "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1 = h0.inject({}) do |new, (name, value)|
new[name] = value;
new
end
回答12:
This is a special case, but if you're starting with a predefined hash that you want to grab and make a copy of, you can create a method that returns a hash:
def johns
{ "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
end
h1 = johns
The particular scenario that I had was I had a collection of JSON-schema hashes where some hashes built off others. I was initially defining them as class variables and ran into this copy issue.
回答13:
Alternative way to Deep_Copy that worked for me.
h1 = {:a => 'foo'}
h2 = Hash[h1.to_a]
This produced a deep_copy since h2 is formed using an array representation of h1 rather than h1's references.