Duplicate hash in ruby
I am trying to initialize a hash in ruby ββusing a different hash with default values. I need a deep copy, but I only ever get a shallow copy.
Here's an example:
DEFAULT_HASH = { a: 0, b: 1 }.freeze
my_hash = DEFAULT_HASH.dup
my_hash[:a] = 4
Now the value of a in "my_hash" and in DEFAULT_HASH is 4. I only need to change the value in my hash.
I've tried other approaches too:
my_hash = {}.merge DEFAULT_HASH
and
my_hash.merge! DEFAULT_HASH
They all have the same effect. What is the best way to achieve this kind of initialization. I also work with nested hashes, which complicates the complexity a bit.
i.e. my DEFAULT_HASH looks like this:
DEFAULT_HASH = { a:{a:1, b:2}, b:{a:2, b:1} }
How will this affect this?
EDIT: Nested Hash case
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}}
source to share
At the point @pjs, Hash#dup
will "do the right thing" for the top level hash. However, for nested hashes, it still doesn't work.
If you are open to using a gem, consider using deep_enumerable , which I wrote for exactly this purpose (among others).
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}"
Output:
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}}
Alternatively, you can try something line by line:
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
Note that this latter feature is nowhere more well tested than deep_enumerable
.
source to share
You can easily create your own deep duplex method using Marhal :: dump and Marshal :: load :
def deep_dup(obj)
Marshal.load(Marshal.dump(obj))
end
obj
can be any Ruby object (for example, a nested combination of arrays and hashes).
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}}}}
In your example:
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}}
source to share
Not .freeze
ing gave the following result:
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}
source to share