【问题标题】:Ruby - local variable gets modified when changing instance variableRuby - 更改实例变量时修改局部变量
【发布时间】:2016-12-19 09:52:29
【问题描述】:

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 不被修改?

【问题讨论】:

  • 这是同一个引用,例如,temp 指向同一个数组 @board 是。如果您想保留原始版本,则必须克隆它。
  • 抱歉,编辑了我的问题。我确实在我的代码中做了@board.dup。但是温度也变了。 (也尝试了.clone,仍然是相同的输出)
  • 你必须克隆很深。

标签: ruby variables scope instance-variables


【解决方案1】:

你有数组和数组,所以你复制了第一个数组,但是内部对象指向同一个实例。在这种情况下,您只需修改相同的源即可。

喜欢这里:

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

【讨论】:

    【解决方案2】:

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

    您需要的是深度克隆。标准库中没有内置方法可以做到这一点。

    【讨论】:

      【解决方案3】:

      来自docs

      dup 生成 obj 的浅表副本——复制 obj 的实例变量,但不复制它们引用的对象。

      也就是说:您需要对数组进行深层复制。

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

      temp = @board.deep_dup
      

      在不了解对象内部结构的情况下,如果不使用 gem 混编可能是对几乎所有内容进行深度复制的简单解决方案:

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

      【讨论】:

      • Marshal 在这种情况下是 OP。
      • @LukasBaliak:但香草红宝石没有比这更好的了。
      • @SergioTulentsev 对于完全转储所有深层对象,是的,但在这种情况下,我认为有点 OP。
      • @LukasBaliak:我有点同意你的看法。但我更喜欢编写不仅适用于特定示例的答案。未来的其他读者可能会遇到稍微不同的问题,并且可能对更通用的解决方案感兴趣。
      【解决方案4】:

      您可以为嵌套数组添加 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"]]
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2016-05-07
        • 2020-04-07
        • 1970-01-01
        • 1970-01-01
        • 2023-03-08
        • 1970-01-01
        相关资源
        最近更新 更多