Ruby dup/clone recursively
Asked Answered
T

5

24

I have a hash like:

h = {'name' => 'sayuj', 
     'age' => 22, 
     'project' => {'project_name' => 'abc', 
                   'duration' => 'prq'}}

I need a dup of this hash, the change should not affect the original hash.

When I try,

d = h.dup # or d = h.clone
d['name'] = 'sayuj1'
d['project']['duration'] = 'xyz'

p d #=> {"name"=>"sayuj1", "project"=>{"duration"=>"xyz", "project_name"=>"abc"}, "age"=>22}
p h #=> {"name"=>"sayuj", "project"=>{"duration"=>"xyz", "project_name"=>"abc"}, "age"=>22}

Here you can see the project['duration'] is changed in the original hash because project is another hash object.

I want the hash to be duped or cloned recursively. How can I achieve this?

Tymes answered 3/1, 2012 at 10:12 Comment(0)
A
40

Here's how you make deep copies in Ruby

d = Marshal.load( Marshal.dump(h) )
Ambassador answered 3/1, 2012 at 10:13 Comment(4)
This creates full copies of all objects referenced by h. This might be exactly what is needed by Sayuj for simple String hashes. With more complex objects, this might not be desired anymore. Once could override the Hash#dup method to dup all hashes in values recursively. But that would need to be extended for every object type.Verboten
@HolgerJust: yes, that's why it's called a "deep copy" :-)Ambassador
Of course. I just wanted to mention that it might do more than the OP intended (although it's probably just fine) :) So it's just for, well, future reference.Verboten
Note that this will not work when there is a default proc (e.g. h = Hash.new {|h,k| h[k] = 1})Euphonium
H
3

If you are in Rails: Hash.deep_dup

Hage answered 23/11, 2015 at 11:22 Comment(0)
P
2

In case the Marchal #dump/load pair isn't work, for there is a Hash's method #deep_dup, so you can:

h = {'name' => 'sayuj', 
 'age' => 22, 
 'project' => {'project_name' => 'abc', 
               'duration' => 'prq'}}

h1 = h.deep_dup
Poly answered 15/5, 2014 at 12:48 Comment(4)
the method should be h.deep_dup instead of h.deep.dupFreestanding
deep_dup method will turn a Class into anonymous Class, not recommended.Waylon
@TianChen example?Discommend
see this example: class Abc; end h = { 'class' => Abc } h1 = h.deep_dup # => => {"class"=>#<Class:0x0000000abe50a0>}Waylon
L
1

This is an answer to a reasonably old question, but I happened upon it while implementing something similar, thought I'd chime in for a more efficient method.

For the simple, two level deep hash like above, you can also do something like this:

d = h.inject({}) {|copy, (key, value)| 
    copy[key] = value.dup rescue value; copy
}

I ran a test on a hash of hashes with 4k elements, each a few hundred bytes, and it was about 50% faster than the Marshal.dump/load

Of course, it's not as complete, as it won't work if you have a hash as, e.g., the value of the 'project_name' field, but for a simple 2 level hash, it works great / faster.

Lineage answered 9/10, 2013 at 20:2 Comment(0)
C
0

Another alternative is to use the full_dup gem (full disclosure: I am the author of that gem) that handles arrays, hashes, structs, and is extendable to user defined classes.

To use:

require 'full_dup'
# Other code omitted ...
d = h.full_dup

Also note that full_dup handles complex data relationships including those with loops or recursion.

Celisse answered 31/1, 2018 at 16:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.