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

标签 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)“应该返回一个二维数组,表示其中每个 :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]]

最佳答案

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

假设

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

啊!这是您遇到的问题。要了解此处发生的情况,让我们查看构成 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] 

这里我们简单地用一个不同的对象 ('cat') 替换了最初是对象 a[0] ) 当然有不同的 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]]

我们需要复制到三个级别:

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

等等。

关于Ruby:如何使用 dup/clone 来不改变原始实例变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54354090/

相关文章:

ruby-on-rails - 如何让 Rails 中的 named_scope 返回一个值而不是数组?

ruby-on-rails - 在 Rails 2.3 中组合选项路由

ruby-on-rails - 在 application.rb 中将 enforce_available_locales 设置为 true 会使测试变慢

ruby-on-rails - 您使用 Rails 进行版本控制和部署的工作流程是什么?

ruby-on-rails - 在 Rspec 请求测试中使用 Session、Cookies 或 Current_something

php - 为什么我需要一个电子邮件帐户才能在 ruby​​ 上发送邮件而在 php 上不需要这样的帐户?

mysql - 尝试查看 Heroku 应用程序时出错

ruby-on-rails - 升级到rails 4.1后无法创建新的ActiveRecord

ruby-on-rails - capybara rspec 匹配器相当于 response.should have_selector "form", :action => some_path

ruby - 经典选择器和 xpath 选择器的区别