在Ruby中复制哈希表

5
我正在尝试使用具有默认值的另一个哈希来初始化 Ruby 中的哈希。我想要深拷贝,但似乎只得到了浅拷贝。
以下是示例:
DEFAULT_HASH = { a: 0, b: 1 }.freeze
my_hash = DEFAULT_HASH.dup
my_hash[:a] = 4 

现在"my_hash"和"DEFAULT_HASH"中的a的值都是4,我只想改变我哈希表中的值。
我也尝试了其他方法:
my_hash = {}.merge DEFAULT_HASH

并且

my_hash.merge! DEFAULT_HASH

所有这些方法都能产生相同的效果。如何最好地实现这种初始化呢?我还在处理嵌套哈希,这使得问题有些复杂。
即我的DEFAULT_HASH看起来像:
DEFAULT_HASH = { a:{a:1, b:2}, b:{a:2, b:1} }

这会影响如何做到这一点吗?

编辑: 嵌套哈希情况

DEFAULT_HASH = { a:{a:1, b:2}, b:{a:2, b:1} }
=> {:a=>{:a=>1, :b=>2}, :b=>{:a=>2, :b=>1}} 
a=DEFAULT_HASH.dup
=> {:a=>{:a=>1, :b=>2}, :b=>{:a=>2, :b=>1}} 
a[:b][:a]=12
=> 12 
DEFAULT_HASH
=> {:a=>{:a=>1, :b=>2}, :b=>{:a=>12, :b=>1}} 

你使用的是哪个版本的Ruby?我无法重现你的行为。 - Makoto
这是使用JRuby 1.7.19和Ruby 2.2.0。 - System123
我正在使用类似的 Ruby 版本,但我没有看到同样的情况。你确定 DEFAULT_HASH 改变了吗? - Makoto
那么第一个例子是假的吗? - steenslag
不是假的,如果我包括.freeze部分,我就能看到它。没有那个部分它也能正常工作。 - System123
显示剩余2条评论
3个回答

6

回应 @pjs 的观点,Hash#dup 可以正确复制哈希表的顶层。但对于嵌套的哈希表,它仍然会失败。

如果你愿意使用一个 gem,考虑使用我为此目的编写的 deep_enumerable gem(其中包括其他功能)。

DEFAULT_HASH = { a:{a:1, b:2}, b:{a:2, b:1} }
dupped = DEFAULT_HASH.dup

dupped[:a][:a] = 'updated'

puts "dupped:       #{dupped.inspect}"
puts "DEFAULT_HASH: #{DEFAULT_HASH.inspect}"


require 'deep_enumerable'
DEFAULT_HASH = { a:{a:1, b:2}, b:{a:2, b:1} }

deep_dupped = DEFAULT_HASH.deep_dup
deep_dupped[:a][:a] = 'updated'

puts "deep_dupped:  #{deep_dupped.inspect}"
puts "DEFAULT_HASH: #{DEFAULT_HASH.inspect}"

输出:

dupped:       {:a=>{:a=>"updated", :b=>2}, :b=>{:a=>2, :b=>1}}
DEFAULT_HASH: {:a=>{:a=>"updated", :b=>2}, :b=>{:a=>2, :b=>1}}

deep_dupped:  {:a=>{:a=>"updated", :b=>2}, :b=>{:a=>2, :b=>1}}
DEFAULT_HASH: {:a=>{:a=>1, :b=>2}, :b=>{:a=>2, :b=>1}}

或者,你可以尝试类似以下的方法:

def deep_dup(h)
  Hash[h.map{|k, v| [k,
    if v.is_a?(Hash)
      deep_dup(v)
    else
      v.dup rescue v
    end
  ]}]
end

请注意,与deep_enumerable相比,这个最后一个函数的测试远远不够充分。


完美,我不介意为此使用一个 gem。这是我项目中相当重要的一部分。 - System123
非常酷的宝石!我喜欢将其广泛应用于其他常见深度问题。 - Rene Wooller

3

您可以使用Marshal::dumpMarshal::load轻松创建自己的深度复制方法:

def deep_dup(obj)
  Marshal.load(Marshal.dump(obj))
end

obj 可以是任何 Ruby 对象(例如,数组和哈希表的嵌套混合)。

h = { a: { b: { c: { d: 4 } } } }

g = deep_dup(h)         #=> {:a=>{:b=>{:c=>{:d=>4}}}}

g[:a][:b][:c][:d] = 44  #=> 44
g                       #=> {:a=>{:b=>{:c=>{:d=>44}}}} 
h                       #=> {:a=>{:b=>{:c=>{:d=>4}}}} 

关于您的示例:

DEFAULT_HASH = { a: { a:1, b:2 }, b: { a:2, b:1 } }
  #=> {:a=>{:a=>1, :b=>2}, :b=>{:a=>2, :b=>1}} 
h = deep_dup(DEFAULT_HASH)
  #=> {:a=>{:a=>1, :b=>2}, :b=>{:a=>2, :b=>1}} 
h[:b][:a] = 12
  #=> 12
h #=> {:a=>{:a=>1, :b=>2}, :b=>{:a=>12, :b=>1}}
DEFAULT_HASH
  #=> {:a=>{:a=>1, :b=>2}, :b=>{:a=>2, :b=>1}} 

虽然这很简洁,但我普遍担心使用像marshal和eval这样的函数,因为它们可能容易受到注入攻击的影响。 - Rene Wooller

-1

没有使用.freeze,得到了以下结果:

irb(main):001:0> DEFAULT_HASH = { a: 0, b: 1 }
=> {:a=>0, :b=>1}
irb(main):002:0> my_hash = DEFAULT_HASH.dup
=> {:a=>0, :b=>1}
irb(main):003:0> my_hash[:a] = 4
=> 4
irb(main):004:0> my_hash
=> {:a=>4, :b=>1}
irb(main):005:0> DEFAULT_HASH
=> {:a=>0, :b=>1}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接