正在学习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]]
谢谢!
答案 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!这是您遇到的问题。要查看此处发生的情况,让我们看一下组成a
和b
的各种对象的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]
我们看到a
和b
的元素与执行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)
dup
和clone
较浅您复制网格数组的内容,这些内容是对内部数组的引用。这些数组不会被复制,而是被引用。这就是为什么后来会改变原始网格的原因。
您需要在网格和dup内部数组上进行映射
答案 2 :(得分:0)