Rubyでハッシュを複製する
概要
デフォルト値を持つ別のハッシュを使用して、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}}
解決策
@pjs の指摘によれば、Hash#dup はハッシュの最上位レベルに対して「正しいことを行う」ということです。ただし、ネストされたハッシュの場合は、やはり失敗します。
gem の使用に抵抗がない場合は、(とりわけ) まさにこの目的のために私が作成した gem である deep_enumerable の使用を検討してください。
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 ほど十分にテストされていないことに注意してください。