How do I copy a hash in Ruby?

I'll admit that I'm a bit of a ruby newbie (writing rake scripts, now). In most languages, copy constructors are easy to find. Half an hour of searching didn't find it in ruby. I want to create a copy of the hash so that I can modify it without affecting the original instance.

Some expected methods that don't work as intended:

h0 = { "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"} h1=Hash.new(h0) h2=h1.to_hash 

In the meantime, I've resorted to this inelegant workaround

def copyhash(inputhash) h = Hash.new inputhash.each do |pair| h.store(pair[0], pair[1]) end return h end 
1

13 Answers

The clone method is Ruby's standard, built-in way to do a shallow-copy:

irb(main):003:0> h0 = {"John" => "Adams", "Thomas" => "Jefferson"} => {"John"=>"Adams", "Thomas"=>"Jefferson"} irb(main):004:0> h1 = h0.clone => {"John"=>"Adams", "Thomas"=>"Jefferson"} irb(main):005:0> h1["John"] = "Smith" => "Smith" irb(main):006:0> h1 => {"John"=>"Smith", "Thomas"=>"Jefferson"} irb(main):007:0> h0 => {"John"=>"Adams", "Thomas"=>"Jefferson"} 

Note that the behavior may be overridden:

This method may have class-specific behavior. If so, that behavior will be documented under the #initialize_copy method of the class.

7

As others have pointed out, clone will do it. Be aware that clone of a hash makes a shallow copy. That is to say:

h1 = {:a => 'foo'} h2 = h1.clone h1[:a] << 'bar' p h2 # => {:a=>"foobar"} 

What's happening is that the hash's references are being copied, but not the objects that the references refer to.

If you want a deep copy then:

def deep_copy(o) Marshal.load(Marshal.dump(o)) end h1 = {:a => 'foo'} h2 = deep_copy(h1) h1[:a] << 'bar' p h2 # => {:a=>"foo"} 

deep_copy works for any object that can be marshalled. Most built-in data types (Array, Hash, String, &c.) can be marshalled.

Marshalling is Ruby's name for serialization. With marshalling, the object--with the objects it refers to--is converted to a series of bytes; those bytes are then used to create another object like the original.

7

If you're using Rails you can do:

h1 = h0.deep_dup 

2

Hash can create a new hash from an existing hash:

irb(main):009:0> h1 = {1 => 2} => {1=>2} irb(main):010:0> h2 = Hash[h1] => {1=>2} irb(main):011:0> h1.object_id => 2150233660 irb(main):012:0> h2.object_id => 2150205060 
2

As mentioned in Security Considerations section of Marshal documentation,

If you need to deserialize untrusted data, use JSON or another serialization format that is only able to load simple, ‘primitive’ types such as String, Array, Hash, etc.

Here is an example on how to do cloning using JSON in Ruby:

require "json" original = {"John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"} cloned = JSON.parse(JSON.generate(original)) # Modify original hash original["John"] << ' Sandler' p original #=> {"John"=>"Adams Sandler", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"} # cloned remains intact as it was deep copied p cloned #=> {"John"=>"Adams", "Thomas"=>"Jefferson", "Johny"=>"Appleseed"} 
1

I am also a newbie to Ruby and I faced similar issues in duplicating a hash. Use the following. I've got no idea about the speed of this method.

copy_of_original_hash = Hash.new.merge(original_hash) 

Use Object#clone:

h1 = h0.clone 

(Confusingly, the documentation for clone says that initialize_copy is the way to override this, but the link for that method in Hash directs you to replace instead...)

Since standard cloning method preserves the frozen state, it is not suitable for creating new immutable objects basing on the original object, if you would like the new objects be slightly different than the original (if you like stateless programming).

Clone is slow. For performance should probably start with blank hash and merge. Doesn't cover case of nested hashes...

require 'benchmark' def bench Benchmark.bm do |b| test = {'a' => 1, 'b' => 2, 'c' => 3, 4 => 'd'} b.report 'clone' do 1_000_000.times do |i| h = test.clone h['new'] = 5 end end b.report 'merge' do 1_000_000.times do |i| h = {} h['new'] = 5 h.merge! test end end b.report 'inject' do 1_000_000.times do |i| h = test.inject({}) do |n, (k, v)| n[k] = v; n end h['new'] = 5 end end end end 
 bench user system total ( real) clone 1.960000 0.080000 2.040000 ( 2.029604) merge 1.690000 0.080000 1.770000 ( 1.767828) inject 3.120000 0.030000 3.150000 ( 3.152627) 
0

This is a special case, but if you're starting with a predefined hash that you want to grab and make a copy of, you can create a method that returns a hash:

def johns { "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"} end h1 = johns 

The particular scenario that I had was I had a collection of JSON-schema hashes where some hashes built off others. I was initially defining them as class variables and ran into this copy issue.

Since Ruby has a million ways to do it, here's another way using Enumerable:

h0 = { "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"} h1 = h0.inject({}) do |new, (name, value)| new[name] = value; new end 

you can use below to deep copy Hash objects.

deeply_copied_hash = Marshal.load(Marshal.dump(original_hash)) 
1

Alternative way to Deep_Copy that worked for me.

h1 = {:a => 'foo'} h2 = Hash[h1.to_a] 

This produced a deep_copy since h2 is formed using an array representation of h1 rather than h1's references.

1

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

You Might Also Like