I want to use default to reset my ary when I need to. But I can't figure out how to not have default's values changed when ary's values change.
> default = {"a"=>[], "b"=>[], "c"=>[]}
=> {"a"=>[], "b"=>[], "c"=>[]}
> ary = default.clone
=> {"a"=>[], "b"=>[], "c"=>[]}
> ary["a"] << "foo"
=> ["foo"]
> default
=> {"a"=>["foo"], "b"=>[], "c"=>[]}
Hashes have a default value that is returned when accessing keys that do not exist in the hash. By default, that value is nil . Creates a new hash populated with the given objects. Equivalent to the literal { key, value, ... } .
All lists, mutable or not, hash by value. And you can use e.g. mutable ArrayList as hash keys.
What you've discovered here is that Hash#clone
only does a shallow clone, that is it only replicates itself but not the objects that are referenced within it.
There are a number of "deep clone" gems that address this specific problem, or you can write your own to work around it:
class Hash
def deep_clone
Hash[collect { |k,v| [ k, v.respond_to?(:deep_clone) ? v.deep_clone : v ] }]
end
end
class Array
def deep_clone
collect { |v| v.respond_to?(:deep_clone) ? v.deep_clone : v }
end
end
This will let you clone arbitrary Hash and Array objects as required.
Both clone and dup create a shallow copy of your object, which results in this behaviour. I'm not sure what the proper way to achieve a deep copy is, but instead of:
ary = default.clone
Try:
ary = Marshal.load(Marshal.dump(default))
This is taken from a live 2.3.8 environment on ruby 1.8.7
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With