Skip to content

Instantly share code, notes, and snippets.

@Integralist
Last active October 24, 2023 16:51
Show Gist options
  • Save Integralist/9041051 to your computer and use it in GitHub Desktop.
Save Integralist/9041051 to your computer and use it in GitHub Desktop.
How to clone a Hash (in Ruby) and modify the cloned hash without affecting the original object
# This is the ONLY way I've found that works
# All other suggested solutions (see below examples) don't actually work
# And as an extra bonus: this deep copies as well!
def deep_copy(o)
Marshal.load(Marshal.dump(o))
end
def test(some_data)
some_data.each { |k, v| some_data.tap { |d| d[k].upcase! } }
end
blah = { :foo => 'bar' }
blah_clone = blah.clone # cloning the hash so we affect the clone and not the original
test(blah_clone) # cloned hash has been changed as expected => {:foo=>"BAR"}
blah # shouldn't be touched but => {:foo=>"BAR"}
#########################################################################################
# I've also tried the following (straight copied from a stack overflow answer which is supposed to work but doesn't)...
def copyhash(inputhash)
h = Hash.new
inputhash.each do |pair|
h.store(pair[0], pair[1])
end
return h
end
original = { :key => 'foobar' }
test = copyhash(original)
test[:key].upcase!
test # => {:key=>"FOOBAR"}
original # => {:key=>"FOOBAR"}
#########################################################################################
# The following also doesn't work...
original = { :key => 'foobar' }
test = Hash[original]
original.object_id # => 2262
test.object_id # => 2268
test[:key].upcase! # => "FOOBAR"
test => {:key=>"FOOBAR"}
original => {:key=>"FOOBAR"}
#########################################################################################
# The following also doesn't work...
h0 = { "John"=>"Adams","Thomas"=>"Jefferson","Johny"=>"Appleseed"}
h1 = h0.inject({}) do |new, (name, value)|
new[name] = value;
new
end
h1["John"].upcase!
h0["John"] # => "ADAMS"
h1["John"] # => "ADAMS"
#########################################################################################
@ncrause
Copy link

ncrause commented Jan 8, 2018

Simply using "merge" (without the "!") seems to work.

hash1 = {:key1 => 'value1', :key2 => 2}
hash2 = {}.merge(hash1)
hash2[:key3] = 'value3'

>> hash1
=> {:key1=>"value1", :key2=>2}
>> hash2
=> {:key1=>"value1", :key2=>2, :key3=>"value3"}

However, this doesn't copy deep.

@marvingreenberg
Copy link

You're completely misunderstanding the problem you observe. This is from the bizarre mutability of Ruby strings, and your test. Clone specifically says it is a shallow copy. The other things are probably similarly shallow. If you really need a deep copy adding a '.clone' for the values in some of the approaches above will probably work.

a = 'hello'; b = 'world'
h = {hello: a, world: b}
h  # => {:hello=>"hello", :world=>"world"}
h2 = h.clone  # irrelevant to this discussion
a.upcase!   # => "HELLO"
h # => {:hello=>"HELLO", :world=>"world"}
h2 # => {:hello=>"HELLO", :world=>"world"} (again irrelevant)

If you put the same mutable object in a hash and change it, it gets changed. The merge from @ncrause has all the same behavior as all the others, because the problem you outline is not from the hash but from the object in the hash.

@tonatiuh
Copy link

tonatiuh commented Oct 17, 2018

Clone doesn't work:

irb(main):001:0> SOMETHING = { a: { b: :c }}.freeze
=> {:a=>{:b=>:c}}
irb(main):002:0> x = { d: :d }.merge(SOMETHING.clone)
=> {:d=>:d, :a=>{:b=>:c}}
irb(main):003:0> x[:a][:d] = :d
=> :d
irb(main):004:0> x
=> {:d=>:d, :a=>{:b=>:c, :d=>:d}}
irb(main):005:0> SOMETHING
=> {:a=>{:b=>:c, :d=>:d}}

The approach I found that works is the one originally posted in this gist:

def deep_copy(o)
  Marshal.load(Marshal.dump(o))
end

@alonSadan
Copy link

Thanks @tonatiuh has the same problem and it worked for me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment