Рекурсия Python Sudoku с ошибкой возврата грубой силы

Я решаю головоломку судоку в качестве домашнего задания, но сталкиваюсь с некоторыми трудностями. Код прямо сейчас циклически проходит мимо решения, хотя он достигает его для простых головоломок, а для более сложных головоломок он застревает с несколькими девятками без видимой причины. Буду признателен за любую помощь в этом. (check_cell определяет, является ли место размещения действительным или нет.)

  1. Правильно ли реализован возврат в этом коде, и если нет, то как это исправить?
  2. Как я могу предотвратить зависание солвера? Он решает около 3 строк, а затем зависает, изменяя большинство значений на 9.

Некоторый код:

def solve_helper(self, row, col):
# Try placing a number in each column of current row
    board = self.the_board
    if board[row][col] != 0:
        ?????
    elif board[row][col] == 0:
        for i in range(1,10):
            print("Setting value at i with ") + str (i) + (" located at " ) + str(row) + str(col)
            self.set_cell(row, col, i)
            self.guesses = self.guesses + 1
            if self.check_cell(row, col):
                if self.solve_helper(row, col): return True
            else:
                self.set_cell(row, col, 0)
    else:
        return self.mover(row,col)
    return False

def mover(self, row, col):
    if col + 1 != 9:
        return self.solve_helper(row, (col+1))
    elif row + 1 != 9:
        print "Moving to row" + str(row + 1)
        return self.solve_helper((row+1),0)
    else:
        print "SOLUTION FOUND"
        return True

person Clueless Coder    schedule 13.11.2012    source источник


Ответы (1)


Проблема, с которой вы сталкиваетесь, заключается в том, что некоторые из ваших рекурсивных вызовов не возвращают результаты должным образом, поэтому ваше решение, когда оно найдено, забывается о нескольких уровнях рекурсивного стека. Вот первое необходимое вам исправление, добавляющее return к рекурсивным вызовам, сделанным в mover:

def mover(self, row, col):
    if col + 1 != 9:
        return self.solve_helper(row, (col+1))  # added return
    elif row + 1 != 9:
        print "Moving to row" + str(row + 1)
        return self.solve_helper((row+1),0)     # here too
    else:
        print "SOLUTION FOUND"
        return True

Вам также нужно что-то подобное в особом случае вашей функции solve_helper, когда вы пропускаете предварительно решенные ячейки. Конец функции должен быть:

else:
    return self.mover(row, col)  # added return
return False

Изменить:

Хорошо, я нашел еще несколько проблем в коде. Две из них — логические проблемы с решателем, а одна — проблема с отображением, которая не вызывает никаких реальных проблем, кроме странного внешнего вида во время решения.

Проблемы:

  1. Во-первых, в вашем последнем коде solve_helper вызывает сам себя, а не mover. Это заставляет выполнять дополнительный вызов функции перед перемещением (хотя я думаю, что на самом деле это может не сломать решатель).
  2. Во-вторых, если solve_helper устанавливает ячейку в 9, но затем при возврате к (после того, как некоторые более поздние ячейки не могут быть решены), 9 не сбрасывается до нуля перед дальнейшим возвратом.
  3. И, наконец, проблема с дисплеем. Установка для ячеек значения 0 не останавливает отображение их старого значения. Это очень похоже на проблему в № 2 (с оставшимися девятками после возврата), но на самом деле это только косметический эффект.

Первую проблему легко исправить. Просто измените вызов solve_helper на вызов mover. На самом деле это то, что у вас было в исходном коде, который вы указали в вопросе. Прямой вызов solve_helper на самом деле не дает неправильного результата (поскольку solve_helper пропустит уже заполненную ячейку во второй раз), но добавляет ненужный дополнительный вызов функции на каждый уровень вашей рекурсии.

Вторая проблема немного сложнее, и здесь вы застреваете на некоторых досках. Что вам нужно сделать, так это переместить строку, выполняющую self.set_cell(row, col, 0), из блока else, в котором она находится в данный момент. На самом деле, вы можете полностью переместить ее за пределы цикла, если хотите (поскольку это действительно необходимо только в том случае, если вы возврат после того, как ни одно из значений для текущей ячейки не сработало). Вот что я думаю, что это наилучшее расположение цикла for (также перемещение оператора return False вверх):

for i in range(1,10):
    print("Setting value ") + str (i) + (" at " ) + str(row) + ", " + str(col)
    self.set_cell(row, col, i)
    self.guesses = self.guesses + 1
    if self.check_cell(row, col):
        if self.mover(row, col):
            return True
print "Backtracking"
self.set_cell(row, col, 0)
return False

Наконец, исправление проблемы с отображением требует двух изменений. Во-первых, избавьтесь от условного оператора в set_cell. Вы хотите всегда обновлять дисплей. Затем в update_textfield переместите вызов delete за пределы блока if, чтобы он всегда происходил (оставьте insert под if). Это делает так, что установка ячейки на ноль удалит предыдущее значение, но не заставит ее отображать фактический символ 0 (она ничего не покажет).

Я думаю, что это должно сделать это. Обратите внимание, что используемый вами алгоритм все еще довольно медленный. Решение доски, которую я нашел в Интернете в результате быстрого поиска в Google, потребовало 122482 предположений и более 5 минут, но это, наконец, сработало. Другие доски (особенно те, которые требуют 8 или 9 в первых нескольких открытых местах) могут занять еще больше времени.

person Blckknght    schedule 13.11.2012
comment
Разве функция, поскольку она уже пропускает ненулевые значения, оператором else как частьюsolve_helper? - person Clueless Coder; 13.11.2012
comment
@CluelessCoder: проблема в том, что после того, как вы пропустите ненулевое значение, перейдя к блоку else, вы всегда возвращаете False, даже если решение было найдено. Каждый раз, когда вы выполняете рекурсию, вы должны быть готовы вернуть True, если этот вызов приведет к поиску решения. Вы хотите вернуть False только тогда, когда вы отказались от текущего состояния доски и вам нужно вернуться. - person Blckknght; 13.11.2012
comment
Я все еще не уверен в том, как передается False, и я не уверен, как правильно направить ненулевые значения. Код, похоже, выполняет откат, но передает правильное значение, в результате чего пустые символы в трех строках превращаются в 9. - person Clueless Coder; 13.11.2012
comment
@CluelessCoder Я думаю, что ваши изменения в операторе if и рекурсии в solve_helper неверны. Все, что вам нужно, это добавить return к строкам, где вы уже выполняли вызовы функций, как в mover, так и в случае else ближе к концу solve_helper. Я думаю, этого должно быть достаточно. - person Blckknght; 13.11.2012
comment
После внесения рекомендованных вами изменений программа все равно не работает. Он будет висеть на месте во 2-й строке, ближе к последнему столбцу, а затем вернется назад, чтобы установить для всех не заданных значений значение 9. Я прикрепил полный код, чтобы узнать, может ли что-то еще быть проблемой. pastebin.com/iNccTXgN - person Clueless Coder; 13.11.2012
comment
@CluelessCoder Я обновил свой ответ дополнительными исправлениями на основе кода, который вы разместили. Есть одна серьезная проблема, одна тривиальная, плюс графический сбой, который выглядит точно так же, как серьезная проблема. С этими исправлениями код может работать с реальными платами, на которых я его тестировал (хотя и медленно). - person Blckknght; 14.11.2012
comment
Спасибо вам за помощь! Код наконец-то работает, спасибо вам! Я искренне благодарен! - person Clueless Coder; 14.11.2012