Ruby:如何使用dup / clone不突变原始实例变量?

时间:2019-01-24 19:32:44

标签: ruby

正在学习Ruby,我正在创建一个Battleship项目,并且下面的代码作为我正在创建的“ Board”类的实例方法。

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

基本上,此方法将创建@grid变量的另一个实例,该实例将用:N代替每个:S符号。

RSPEC有两个要求:1)“应返回表示网格的2D数组,其中将每个:S替换为:N”和2)“不应对原始@grid进行变异”。

我的问题是我的上述代码满足了第一个要求,但它破坏了第二个要求。有人可以向我解释是什么导致原始@grid文件发生突变吗?我已经遍历了15次代码,看不到我在哪里重写或重新分配了原始@grid变量。

提供给我们的“正确”解决方案使用的是“ .map”,但我想了解为什么该解决方案无法正常工作,并且最终导致了原始@grid变量的变异。

  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]]

谢谢!

3 个答案:

答案 0 :(得分:6)

这是一个常见的新手错误。

假设

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

这正是您所期望和期望的。现在考虑以下内容。

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

同样,这是所需的结果。还有一个:

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!这是您遇到的问题。要查看此处发生的情况,让我们看一下组成ab的各种对象的ID。回想一下,每个Ruby对象都有一个唯一的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] 

在这里,我们简单地将b[0](最初是对象a[0])替换为另一个对象('cat'),该对象当然具有不同的id。这不会影响a。 (在下面,我仅给出id的最后三位。如果两个相同,则整个id都相同。)现在考虑以下内容。

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] 

我们看到ab的元素与执行b[0][0] = 'cat'之前的对象是相同的对象。但是,该分配更改了ID为...620的对象的值,这说明了a以及b被更改的原因。

为避免修改a,我们需要执行以下操作。

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] 

现在b的元素与a的元素是不同的对象,因此对b的任何更改都不会影响a

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

如果我们有

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

我们需要dup到三个级别:

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]]

以此类推。

答案 1 :(得分:3)

dupclone较浅您复制网格数组的内容,这些内容是对内部数组的引用。这些数组不会被复制,而是被引用。这就是为什么后来会改变原始网格的原因。

您需要在网格和dup内部数组上进行映射

答案 2 :(得分:0)

dupclone都用于制作重复记录。

您可以为dup Dup Api Documents

引用此链接

您可以在clone clone Api Documents上引用此链接