开发者

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 defaul开发者_运维百科t'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"=>[]} 


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


class Object
  def deep_clone
    Marshal::load(Marshal.dump(self))
  end
end

default = {"a"=>[], "b"=>[], "c"=>[]}
ary = default.deep_clone
ary["a"] << "foo"
default {"a"=>[], "b"=>[], "c"=>[]}


A way to do this is as follows:

ary = Marshal.load(Marshal.dump(default)) 


clone only does shallow copies, which is why cloning your hash still keeps everything pointed at the same nested arrays.

You can avoid this through the Marshal class by dumping and then loading in the object values:

> default = {"a" => [], "b" => [], "c" => []}
=> {"a"=>[], "b"=>[], "c"=>[]} 
> ary = Marshal.load(Marshal.dump(default))
=> {"a"=>[], "b"=>[], "c"=>[]} 
> ary["a"] << "foo"
=> ["foo"]
> default
=> {"a"=>[], "b"=>[], "c"=>[]} 


Depending on what you want to do, a simpler alternative to writing a deep clone method might be to write a method that creates a new default array every time it's called:

def default
  {"a"=>[], "b"=>[], "c"=>[]}
end

ary = default #=> {"a"=>[], "b"=>[], "c"=>[]}

ary["a"] << "foo" #=> {"a"=>["foo"], "b"=>[], "c"=>[]}

default #=> {"a"=>[], "b"=>[], "c"=>[]}

Of course, if the contents of your default hash changes over the course of the program this won't work and you'll have to look into the cloning or marshalling techniques, but if the contents are fixed this might be a more straightforward solution.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜