Ruby -局部变量在改变实例变量时被修改



temp得到@board.dup, @board数组被修改。然而,temp也被修改了!我已经试着阅读了所有的相关文档,但还是找不到解释。

class Test
    def initialize
        @board = [[1,2],[3,4], [5,6]]
    end
    def modify
        temp = @board.dup #Also tried .clone
        print 'temp: ';p temp
        print '@board: ';p @board
        @board.each do |x|
            x << "x"
        end
        print "ntemp: ";p temp
        print '@board: ';p @board
    end
end
x = Test.new
x.modify
输出:

temp: [[1, 2], [3, 4], [5, 6]]
@board: [[1, 2], [3, 4], [5, 6]]
temp: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]] # <= Why did it change?
@board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]

我能做些什么来确保temp不被修改?

你有数组数组,所以你把第一个数组,但内部对象指向同一个实例。在本例中,您只需修改相同的源代码。

像:

arr = [[1, 2, 3]]
arr2 = arr.dup
arr2[0] << 1
p arr
# => [[1, 2, 3, 1]]
p arr2
# => [[1, 2, 3, 1]]

所以你必须使用dup对于所有的数组实例,像这样。

arr = [[1, 2, 3]]
arr3 = arr.map(&:dup)
arr3[0] << 1
p arr
# => [[1, 2, 3]]
p arr3
# => [[1, 2, 3, 1]]

在你的情况下,使用这个map

class Test
  def initialize
    @board = [[1,2],[3,4], [5,6]]
  end
  def modify
    temp = @board.map(&:dup) # dup all
    print 'temp: ';p temp
    print '@board: ';p @board
    @board.each do |x|
      x << "x"
    end
    print "ntemp: ";p temp
    print '@board: ';p @board
  end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# @board: [[1, 2], [3, 4], [5, 6]]
# 
# temp: [[1, 2], [3, 4], [5, 6]]
# @board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]

原因是clonedup产生一个浅拷贝。因此,内部数组的对象id仍然是相同的:它们是相同的数组。

你需要的是深度克隆。在标准库中没有内置方法能够做到这一点。

From the docs:

dup生成obj的浅拷贝——obj的实例变量被复制,但不复制它们引用的对象。

也就是说:你需要对你的数组做一个深度拷贝。

当你使用ActiveSupport gem (Rails)时,你可以使用它的deep_dup方法,而不仅仅是调用dup:

temp = @board.deep_dup

如果没有gem, mashashing可能是一种简单的解决方案,可以在不了解对象内部的情况下深入处理几乎所有内容:

temp = Marshal.load(Marshal.dump(@board))  

您可以为嵌套数组添加deep_dup方法:

class Array
  def deep_dup
    map {|x| x.deep_dup}
  end
end
# To handle the exception when deepest array contains numeric value
class Numeric
  def deep_dup
    self
  end
end
class Test
    def initialize
        @board = [[1,2], [3,4], [5,6]]
    end
    def modify
        temp = @board.deep_dup
        ...
    end
end
x = Test.new
x.modify
# temp: [[1, 2], [3, 4], [5, 6]]
# @board: [[1, 2], [3, 4], [5, 6]]
# temp: [[1, 2], [3, 4], [5, 6]]
# @board: [[1, 2, "x"], [3, 4, "x"], [5, 6, "x"]]

最新更新