Ruby: How to use dup/clone to not mutate an original instance variable?

ruby deep clone
ruby freeze
ruby unfreeze
ruby deep_clone
ruby array
rails dup
ruby hash
deep_dup vs dup

Learning Ruby, I'm creating a Battleship project and I have the following code as an instance method for a class "Board" I'm creating.

def hidden_ships_grid
    hidden_s_grid = @grid.dup 
    hidden_s_grid.each_with_index do |sub_array, i|
        sub_array.each_with_index do |el, j|
            # position = [i, j]
            hidden_s_grid[i][j] = :N if el == :S 
       end
    end
end

Basically this method would create another instance of a @grid variable that would replace every :S symbol with a :N instead.

The RSPEC has two requirements: 1) "should return a 2D array representing the grid where every :S is replaced with an :N" and 2) "should not mutate the original @grid".

My problem is that my above code satisfies the first requirement, but it breaks the second requirement. Can someone please explain to me what is causing the original @grid file to be mutated? I've gone through the code 15 times over and I can't see where I rewrite or reassign the original @grid variable.

The "correct" solution provided to us uses ".map" which is fine, but I want to understand why this solution isn't working and ends up mutating the original @grid variable.

  1) Board PART 2 #hidden_ships_grid should not mutate the original @grid
     Failure/Error: expect(board.instance_variable_get(:@grid)).to eq([[:S, :N],[:X, :S]])

       expected: [[:S, :N], [:X, :S]]
            got: [[:N, :N], [:X, :N]]

       (compared using ==)

       Diff:
       @@ -1,2 +1,2 @@
       -[[:S, :N], [:X, :S]]
       +[[:N, :N], [:X, :N]]

Thanks in advance!

This is a common newbie mistake.

Suppose

a = [1, 2, 3]
b = a.dup
  #=> [[1, 2], [3, 4]]
b[0] = 'cat'
  #=> "cat" 
b #=> ["cat", 2, 3] 
a #=> [1, 2, 3] 

This is exactly what you were expecting and hoping for. Now consider the following.

a = [[1, 2], [3, 4]]
b = a.dup
  #=> [[1, 2], [3, 4]]
b[0] = 'cat'
b #=> ["cat", [3, 4]] 
a #=> [[1, 2], [3, 4]] 

Again, this is the desired result. One more:

a = [[1,2], [3,4]]
b = a.dup
  #=> [[1,2], [3,4]]
b[0][0] = 'cat'
b #=> [["cat", 2], [3, 4]] 
a #=> [["cat", 2], [3, 4]] 

Aarrg! This is the problem that you experienced. To see what's happening here, let's look the id's of the various objects that make up a and b. Recall that every Ruby object has a unique Object#id.

a = [[1, 2], [3, 4]]
b = a.dup
a.map(&:object_id)
  #=> [48959475855260, 48959475855240] 
b.map(&:object_id)
  #=> [48959475855260, 48959475855240] 
b[0] = 'cat'
b #=> ["cat", [3, 4]] 
a #=> [[1, 2], [3, 4]] 
b.map(&:object_id)
  #=> [48959476667580, 48959475855240] 

Here we simply replace b[0], which initially was the object a[0] with a different object ('cat') which of course has a different id. That does not affect a. (In the following I will give just the last three digits of id's. If two are the same the entire id is the same.) Now consider the following.

a = [[1, 2], [3, 4]]
b = a.dup
a.map(&:object_id)
  #=> [...620, ...600] 
b.map(&:object_id)
  #=> [...620, ...600] 
b[0][0] = 'cat'
  #=> "cat" 
b #=> [["cat", 2], [3, 4]] 
a #=> [["cat", 2], [3, 4]] 
a.map(&:object_id)
  #=> [...620, ...600] 
b.map(&:object_id)
  #=> [...620, ...600] 

We see that the elements of a and b are the same objects as they were before executing b[0][0] = 'cat'. That assignment, however, altered the value of the object whose id is ...620, which explains why a, as well as b, was altered.

To avoid modifying a we need to do the following.

a = [[1, 2], [3, 4]]
b = a.dup.map(&:dup) # same as a.dup.map { |arr| arr.dup }
  #=> [[1, 2], [3, 4]] 
a.map(&:object_id)
  #=> [...180, ...120] 
b.map(&:object_id)
  #=> [...080, ...040] 

Now the elements of b are different objects than those of a, so any changes to b will not affect a:

b[0][0] = 'cat'
  #=> "cat" 
b #=> [["cat", 2], [3, 4]] 
a #=> [[1, 2], [3, 4]]  

If we had

a = [[1, [2, 3]], [[4, 5], 6]]

we would need to dup to three levels:

b = a.map { |arr0| arr0.dup.map { |arr1| arr1.dup } }
  #=> [[1, [2, 3]], [[4, 5], 6]] 
b[0][1][0] = 'cat'
b #=> [[1, ["cat", 3]], [[4, 5], 6]] 
a #=> [[1, [2, 3]], [[4, 5], 6]]

and so on.

ruby - Original variable still changes with .clone or .dup, A dup or clone in Ruby is a shallow clone, meaning that only the outer object is cloned, not its children. In your case, this means that the  As we saw in Variable References and Mutability article, assignment merely tells Ruby to bind an object to a variable. This means that assignment does not change an object; it merely connects the variable to a new object. While = is not an actual method in Ruby, it acts like a non-mutating method, and should be treated as such.

dup and clone are shallow You copy contents of the grid array that are references to inner array. Those arrays are not copied, but referenced. That's why original grid is chaged later.

You need to map over grid and dup inner arrays

Effective Ruby: 48 Specific Ways to Write Better Ruby, 48 Specific Ways to Write Better Ruby Peter J. Jones method and is supposed to return a default value, but in our case it raises an exception. instance variable then accessing an invalid key would no longer raise an exception. Let's start with dup and clone. Since RaisingHash is delegating mutating methods to  The most common reason to create a copy of an object is to modify it without affecting the original. After all, it might not be yours to mess with. In that case, leaving it frozen doesn’t make much sense. As a result, I tend to find myself using dup far more often than clone. Going forward we’ll focus on dup.

The dup and clone both are for make duplicates records.

You can refer this link for dup Dup Api Documents

You can refer this link for cloneclone Api Documents

Ruby: the differences between dup & clone (Example), A protip by mattetti about ruby, dup, clone, and copy objects. Examples of the singleton methods not being copied. dup: by the bar attribute doesn't get copied but shared between the original and the copied instances: 2- The Object#deep_dup method returns the return value of the dup method call or self if the object is not eligible to deep copy. 3- The Array#deep_dup method map through the calling array and

Understanding Mutable Objects & Frozen Strings, Not all objects in Ruby are mutable. with the changes & then return this new object, leaving the original object intact. Mutability & Variables as Pointers Cloning Objects. One way to deal with this issue is to use the dup method. copy all 1,000,000 String instances held in the Array while in Ruby you actually only have  on dup vs clone ¶ ↑ In general, clone and dup may have different semantics in descendant classes. While clone is used to duplicate an object, including its internal state, dup typically uses the class of the descendant object to create the new instance. When using dup, any modules that the object has been extended with will not be copied.

Dup vs Clone in Ruby: Understanding The Differences, Both examples allow you to keep the original array. dup vs clone Ruby mindmap. Frozen Objects. Dup & clone are not an alias of each other like it happens with  Previously, this was not possible at all. If an object was frozen, the clone was frozen before the cloned object could be modified. It was possible to modify the clone using initialize_clone or initialize_copy, but you couldn't change how to modify the clone on a per-call basis. You couldn't use dup

Know Ruby: clone and dup, In clone , the frozen state of the object is also copied. After all, it might not be yours to mess with. When using dup you'll lose singleton methods added to the original object. We'll make a new instance of CloneWatcher . If you do need to mutate something, we've seen how a new copy can help avoid  The clone filter is for duplicating events. A clone will be created for each type in the clone list. The original event is left unchanged. Created events are inserted into the pipeline as normal events and will be processed by the remaining pipeline configuration starting from the filter that generated them (i.e. this plugin).

Comments
  • Related: stackoverflow.com/questions/8206523/…
  • Thanks, I forgot Ruby doesn't have deepcopy, but I wonder why.. Why not add to 2.6? Python has it.
  • @iGan, good question. I'm sure the Ruby monks have considered it. One can get a deep copy for most objects with Marshal.load(Marshall(dump(obj))). See Marshal.
  • Or copy the entire thing at once: hidden_s_grid = Marshal.load(Marshal.dump(@grid))
  • Isn't marshalling a slower solution than mapping?
  • Or you could use the full_dup or full_clone gems to make full copies of your playing board. Full disclosure: I am the author of those gems. As in: hidden_s_grid = @grid.full_dup
  • @mrzasa: that needs to be measured. I lost count to the number of times I was surprised by benchmark results.