Глубокое копирование объектов Ruby
Я закончил pecs tic tac toe, и работал над улучшением моего компьютерного плеера. Это требует создания копий новых объектов доски из старых объектов доски. У меня проблемы с созданием глубоких копий доски.
Вот код, о котором идет речь:
Class Board
attr_accessor :grid
def initialize(grid = Array.new(3){ Array.new(3)})
@grid = grid
end
def place_mark(cords, sym)
self[cords] = sym
@grid
end
def [](pos)
row, col = pos
@grid[row][col]
end
def []=(pos, mark)
row, col = pos
@grid[row][col] = mark
end
def new_state
grid = @grid.dup
Board.new(grid)
end
end
board = Board.new
new_state = board.new_state # ==> A different object
new_state.place_mark([0,0], :X) # ==> Object with x placed at 0,0
board # ==> Object with x placed at 0,0
Теперь, когда я реализую new_state и затем помещаю метку в new_state, она также ставит метку в состоянии, из которого она была продублирована.
Я понимаю, почему, если я настрою свой new_state с помощью простого дублирования объекта, это не сработает. Но я не понимаю, почему моя текущая реализация не работает. Я должен сохранить сетку текущего объекта, а затем создать новый объект с той же сеткой. Какие-нибудь мысли?
1 ответ
В рубине dup
производит мелкий клон и не копирует объекты, на которые они ссылаются. поскольку @grid
является массивом массивов, каждый массив в @grid
массив не копируется. Это, возможно, не было ясно, так что, надеюсь, поможет код:
grid = [ [:first_row], [:second_row] ]
copy = grid.dup
grid.object_id == copy.object_id # => false, so you have a different array
grid[0].object_id == copy[0].object_id # => true, so the inner array is the same
copy[0][0] = :test_change
grid # => [[:test_change], [:second_row]]
copy # => [[:test_change], [:second_row]]
таким образом, внутренние массивы - это один и тот же объект, и изменение его из одного места изменяет все, что на него ссылается. Но если вы измените внешний массив, он будет работать так, как вы ожидаете:
copy[0] = [:updates_correctly]
copy # => [[:updates_correctly], [:second_row]]
grid # => [[:test_change], [:second_row]]
Итак, зная все это, мы надеемся, немного яснее, как бы вы это исправить, вам просто нужно позвонить dup
на "внутренних" массивах. Мы будем использовать collect
здесь также, который возвращает новый массив, так что нам не нужно вызывать dup
на внешнем массиве вообще:
copy = grid.collect(&:dup) # => [[:test_change], [:second_row]]
copy[0][0] = :another_change
copy # => [[:another_change], [:second_row]]
grid # => [[:test_change], [:second_row]]
но элементы этих массивов все еще не разные объекты, если бы вместо этого у нас были строки:
grid = [ ['first row'] ]
copy = grid.collect(&:dup)
и изменив строку на месте, в итоге мы изменили оба:
copy[0][0].upcase!
copy # => [["FIRST ROW"]]
grid # => [["FIRST ROW"]]
насколько глубоко вам нужно идти, зависит от ваших конкретных потребностей.
Итак, TL; DR нужно поменять new_state
выглядеть как:
def new_state
grid = @grid.collect(&:dup)
Board.new(grid)
end
и это должно работать.