【问题标题】:Duplicating a Hash in Ruby在 Ruby 中复制哈希
【发布时间】:2015-07-11 23:21:28
【问题描述】:

我正在尝试通过使用另一个具有默认值的哈希来初始化 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?我无法重现你的行为。
  • 这是 JRuby 1.7.19 和 Ruby 2.2.0
  • 我正在使用类似版本的 Ruby,但我看不到同样的东西。您确定DEFAULT_HASH 正在改变吗?
  • 肯定的,看看我对这个问题所做的编辑。这是我的 irb 会话的直接输出。可以看到 DEFAULT_HASH[:b][;a] 得到值 12
  • 好的,所以我没有用你的 nested 案例对此进行测试;这显然改变了事情。在浅哈希上,我看不到这种行为。

标签: ruby hash


【解决方案1】:

就@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。这是我项目中相当重要的一部分。
  • 非常酷的宝石!我喜欢更广泛地应用到一堆其他常见的深度问题
【解决方案2】:

您可以使用Marhal::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 等函数
【解决方案3】:

不是.freezeing 给出了以下结果:

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}

【讨论】:

    猜你喜欢
    • 2011-05-08
    • 2011-12-28
    • 2014-04-09
    • 2021-11-03
    • 2014-03-03
    • 1970-01-01
    • 2021-02-05
    • 2014-01-02
    • 1970-01-01
    相关资源
    最近更新 更多