如何在 Ruby 中复制哈希?
Posted
技术标签:
【中文标题】如何在 Ruby 中复制哈希?【英文标题】:How do I copy a hash in Ruby? 【发布时间】:2011-05-08 15:05:43 【问题描述】:我承认我是一个红宝石新手(现在正在编写 rake 脚本)。在大多数语言中,复制构造函数很容易找到。搜索了半个小时,没有在ruby中找到。我想创建哈希的副本,以便可以在不影响原始实例的情况下对其进行修改。
一些未按预期工作的预期方法:
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 的对象,您应该考虑是否希望与 Hash 关联的单例类重复或不。见***.com/questions/10183370/…
【参考方案1】:
使用Object#clone
:
h1 = h0.clone
(令人困惑的是,clone
的文档说 initialize_copy
是覆盖它的方法,但 Hash
中该方法的链接将您定向到 replace
...)
【讨论】:
【参考方案2】:clone
方法是 Ruby 的标准内置方法,用于执行 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"
请注意,该行为可能会被覆盖:
此方法可能具有特定于类的行为。如果是这样,该行为将记录在该类的
#initialize_copy
方法下。
【讨论】:
克隆是对象上的一个方法,顺便说一句,所以一切都可以访问它。查看 API 详情here 在这里为那些没有阅读其他答案的人添加更明确的评论,这是一个浅拷贝。 #initialize_copy 文档似乎不存在哈希,即使哈希文档页面ruby-doc.org/core-1.9.3/Hash.html#method-i-initialize_copy上有指向它的链接 而对于其他 Ruby 初学者来说,“浅拷贝”意味着第一层以下的每个对象仍然是一个引用。 请注意,这对我来说不适用于嵌套哈希(如其他答案中所述)。我用Marshal.load(Marshal.dump(h))
。【参考方案3】:
正如其他人指出的那样,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
适用于任何可以编组的对象。大多数内置数据类型(数组、哈希、字符串等)都可以编组。
Marshalling 是 Ruby 对 serialization 的名称。通过编组,对象——连同它所指的对象——被转换为一系列字节;然后这些字节被用来创建另一个像原始对象一样的对象。
【讨论】:
@K.Carpenter 不是共享部分原件的浅副本吗?深拷贝,据我了解,是不共享原件任何部分的副本,因此修改一个不会修改另一个。Marshal.load(Marshal.dump(o))
深度复制到底是怎样的?我真的无法理解幕后发生的事情
这也强调了如果你做h1[:a] << 'bar'
你修改了原始对象(h1[:a]指向的字符串)但是如果你做h1[:a] = "#h1[:a]bar"
,你将创建一个新的字符串对象,并指向h1[:a]
,而h2[:a]
仍指向旧的(未修改的)字符串。
注意:通过 Marshal 方法进行克隆会导致远程代码执行。 ruby-doc.org/core-2.2.0/…
@JesseAldridge 是的,如果Marshal.load
的输入不受信任,请记住一个很好的警告。在这种情况下,它的输入来自我们自己进程中的Marshal.dump
。我认为Marshal.load
在这种情况下是安全的。【参考方案4】:
您可以使用下面的方法来深度复制 Hash 对象。
deeply_copied_hash = Marshal.load(Marshal.dump(original_hash))
【讨论】:
这是韦恩康拉德答案的副本。【参考方案5】: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
【讨论】:
请注意,这与 #clone 和 #dup 具有相同的深层复制问题。 @forforf 是正确的。如果您不了解深拷贝和浅拷贝,请不要尝试复制数据结构。【参考方案6】:这是一种特殊情况,但如果您从一个预定义的散列开始,想要抓取并复制它,您可以创建一个返回散列的方法:
def johns
"John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"
end
h1 = johns
我遇到的特殊情况是我有一组 JSON 模式哈希,其中一些哈希建立在其他哈希之上。我最初将它们定义为类变量并遇到了这个复制问题。
【讨论】:
【参考方案7】:由于 Ruby 有一百万种方法可以做到这一点,这里有另一种使用 Enumerable 的方法:
h0 = "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"
h1 = h0.inject() do |new, (name, value)|
new[name] = value;
new
end
【讨论】:
【参考方案8】:由于标准克隆方法保留了冻结状态,因此不适合基于原始对象创建新的不可变对象,如果您希望新对象与原始对象略有不同(如果您喜欢无状态编程)。
【讨论】:
【参考方案9】:克隆很慢。为了提高性能,可能应该从空白哈希和合并开始。不包括嵌套哈希的情况...
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 用户系统总数(真实)
克隆 1.960000 0.080000 2.040000 (2.029604)
合并 1.690000 0.080000 1.770000 ( 1.767828)
注入 3.120000 0.030000 3.150000 ( 3.152627)
【讨论】:
【参考方案10】:对我有用的 Deep_Copy 的替代方法。
h1 = :a => 'foo'
h2 = Hash[h1.to_a]
这产生了一个 deep_copy,因为 h2 是使用 h1 的数组表示而不是 h1 的引用形成的。
【讨论】:
听起来很有希望但不起作用,这是另一个浅拷贝【参考方案11】:如果你使用 Rails,你可以这样做:
h1 = h0.deep_dup
http://apidock.com/rails/Hash/deep_dup
【讨论】:
Rails 3 在哈希中存在 deep_duping 数组的问题。 Rails 4 解决了这个问题。 感谢您指出这一点,使用 dup 或 clone 时我的哈希仍然受到影响【参考方案12】:我也是 Ruby 的新手,我在复制哈希时遇到了类似的问题。使用以下内容。我不知道这种方法的速度。
copy_of_original_hash = Hash.new.merge(original_hash)
【讨论】:
【参考方案13】:正如Security Considerations section of Marshal documentation中提到的,
如果您需要反序列化不受信任的数据,请使用 JSON 或其他 只能加载简单的“原始”的序列化格式 String、Array、Hash等类型
这是一个关于如何在 Ruby 中使用 JSON 进行克隆的示例:
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"
【讨论】:
这在大多数情况下都有效,但如果您的键是整数而不是字符串,请注意。当您往返 JSON 时,键将变成字符串。以上是关于如何在 Ruby 中复制哈希?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Ruby 的 bcrypt 库在哈希中包含明文中的盐? [复制]