Search code examples
pythonrecursiongenerator

How to stop yielding sudoku boards, once the puzzle is solved?


This is my recursive sudoku solver. Pretty self-explanatory:

def is_valid(board, num, row, col):
    # check row
    for i in range(9):
        if board[i][col] == num:
            return False

    # check column
    for i in range(9):
        if board[row][i] == num:
            return False

    # check square
    square_w = (row // 3) * 3
    square_h = (col // 3) * 3
    for sRow in range(square_w, square_w + 3):
        for sCol in range(square_h, square_h + 3):
            if board[sRow][sCol] == num:
                return False

    return True

def solve(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == 0:
                for num in range(1, 10):
                    if is_valid(board, num, row, col):
                        board[row][col] = num
                        yield board
                        yield from solve(board)

                        board[row][col] = 0
                        yield board
                return
    yield board

And this is the board I am running the solver on:

bo = [
    [7,8,0,4,0,0,1,2,0],
    [6,0,0,0,7,5,0,0,9],
    [0,0,0,6,0,1,0,7,8],
    [0,0,7,0,4,0,2,6,0],
    [0,0,1,0,5,0,9,3,0],
    [9,0,4,0,6,0,0,0,5],
    [0,7,0,3,0,0,0,1,2],
    [1,2,0,0,0,7,4,0,0],
    [0,4,9,2,0,6,0,0,7]
]

The problem is that when I call solve and iterate the yielded boards, the boards continue to generate even after getting the result (at the bottom most yield board in the solve function).

How can I make sure that the last value in the generator is the solved board?


Solution

  • There are some indentation problems in your code. At the time of writing both the return and yield from statements should be indented one level more.

    To stop the generation of more boards once you have it solved, you should check that the board is solved. The code does not do this anywhere.

    As you correctly indicate, the board is solved when you reach the last statement in the solve function, and the function returns there. You could use this moment to mark the board so to make it easier for the caller to also know that the board was solved. As you already mutate the same board throughout the algorithm (instead of yielding copies), I suppose you could just mutate the board once more in order to indicate it was solved. For instance, you could store a 0 in the top-left corner. This can be checked by the caller, who can then immediately return:

    def solve(board):
        for row in range(9):
            for col in range(9):
                if board[row][col] == 0:
                    for num in range(1, 10):
                        if is_valid(board, num, row, col):
                            board[row][col] = num
                            yield board
                            yield from solve( board)  # corrected wrong indentation
                            if board[0][0] == 0:  # was the board already solved?
                                return  # then get out of here!
    
                            board[row][col] = 0  # better placed at this indentation
                            yield board  # better placed at this indentation
                    return  # corrected wrong indentation
        # the board is the solution: no need to yield it as it was already yielded
        board[0][0] = 0  # mark that no more boards should be yielded