Глубокое копирование объектов 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

и это должно работать.

Другие вопросы по тегам