问题
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