Ruby - local variable gets modified when changing instance variable

半城伤御伤魂 提交于 2021-02-08 10:44:23

问题


temp gets @board.dup, and @board array is modified. However, temp gets modified as well! I have tried reading all the related documentations but still couldn't figure out an explanation.

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

Output:

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

What can I do to ensure temp doesn't get modified?


回答1:


You have Array with Arrays, so you dup the first array but, inside object point to the same instance. In this case you just modify the same source.

like here:

arr = [[1, 2, 3]]
arr2 = arr.dup

arr2[0] << 1

p arr
# => [[1, 2, 3, 1]]
p arr2
# => [[1, 2, 3, 1]]

So you must use dup for all array instance like this.

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

p arr
# => [[1, 2, 3]]
p arr3
# => [[1, 2, 3, 1]]

In your case use this 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:


The reason is clone and dup produce a shallow copy. Thus the object ids of the inner arrays are still the same: those are the same arrays.

What you need is a deep clone. There is no built-in method in the standard library able to do that.




回答3:


From the docs:

dup produces a shallow copy of obj—the instance variables of obj are copied, but not the objects they reference.

That said: You will need to make a deep-copy of your array.

When you are using the ActiveSupport gem (Rails) you can use its deep_dup method instead of just calling dup:

temp = @board.deep_dup

Without gems mashaling might be a easy solution to deep dup almost everything, without knowing about the internals of an object:

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



回答4:


You can add a deep_dup method for nested arrays:

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


来源:https://stackoverflow.com/questions/38917008/ruby-local-variable-gets-modified-when-changing-instance-variable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!