Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use default on a hash of empty arrays?

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"=>[]} 
like image 295
Jeremy Smith Avatar asked May 09 '11 20:05

Jeremy Smith


People also ask

How might you specify a default value for a hash?

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, ... } .

Can an array be a hash key?

All lists, mutable or not, hash by value. And you can use e.g. mutable ArrayList as hash keys.


2 Answers

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.

like image 128
tadman Avatar answered Sep 30 '22 02:09

tadman


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

like image 37
Jaap Haagmans Avatar answered Sep 30 '22 02:09

Jaap Haagmans