0

This is a working code that solves the sudoku:

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

    for i in range(9):
        if board[i][col] == num:
            return False

    box_row = (row - row % 3)
    box_col = (col - col % 3)

    for i in range(3):
        for j in range(3):
            if board[box_row + i][box_col + j] == 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, row, col, num):
                        board[row][col] = num
                        solve(board)
                        board[row][col] = 0

                return False

print(np.matrix(board))

solve(board)

The part that confuses me is:

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

    return False

how is this part working? I know it will assign the number, to the current row and col THEN, run the solve() function again, so, when will the program run this:

board[row][col] = 0

because as I understand it, the code won't run unless there's a 0 already. Then the program will check if the number is valid or not. also if there's no num [1~9] that is valid, won't it return false and quit the function?

talking about it makes my head spin, I know it's even hard to explain, I googled it.

Edit:

this is the board I'm dealing with:

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

Output:

[[3 1 6 5 7 8 4 9 2]
 [5 2 9 1 3 4 7 6 8]
 [4 8 7 6 2 9 5 3 1]
 [2 6 3 4 1 5 9 8 7]
 [9 7 4 8 6 3 1 2 5]
 [8 5 1 7 9 2 6 4 3]
 [1 3 8 9 4 7 2 5 6]
 [6 9 2 3 5 1 8 7 4]
 [7 4 5 2 8 6 3 1 9]]
abdurion
  • 9
  • 2
  • @jarmod I just did, thank you – abdurion Sep 23 '21 at 14:16
  • 1
    As it's written, `solve` either returns `False` or `None` and doesn't print anything. I suspect that the `print` is supposed to be indented to some level? Probably 1 level of indentation? – SpoonMeiser Sep 23 '21 at 14:28
  • Can you share the output of the print statement? That one shows what the board looks like before. I suspect that empty fields are represented by 0, in which case there would be many zeros. – Joooeey Sep 23 '21 at 17:13

2 Answers2

0

There are two cases where the solve function returns. I'll rewrite the code to make it sweeter (but yours works too)*:

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, row, col, num):
                        board[row][col] = num
                        solve(board)
                        board[row][col] = 0

                return False
    return True

So, whenever the solve method reaches one of those return statements, it returns to the method that called it. Note that there are a two if-statements outside the call to solve. These can evaluate to False. And if they evaluate to False often enough, one of the return statements will be reached. That's how you get to board[row][col] = 0

If you want to see the solved Sudoku, you have to repeat print(np.matrix(board)) after the call to solve at the very end too.


*Side notes:

  • As you wrote it, the solve method can return either None or False which is bad style because bool(None) evaluates to False too. Why does it return None? That's because no return statement at the end of a function is equivalent to return None.
  • You could also just use return unless you need the True/False later. That's because solve(board) is not assigned to anything.
  • If the Sudoku doesn't have a solution, you'll enter a convoluted infinite loop that will only end once the maximum recursion depth is reached (in CPython).
Joooeey
  • 3,394
  • 1
  • 35
  • 49
  • I just edited the post and posted the output along with the original board. I'm starting to get the grasp of it, the return False as I tried is in fact, not needed in this case. so my question is. where does the program "go" when he reaches the return? the beginning or as you said the method that called it? thank you – abdurion Sep 23 '21 at 17:21
  • Whenever a function reaches a return, it's finished. So the code continues after the function call. That's in the middle of the function that called it. Your Python interpreter remembers how many times the function is nested. Perhaps if you repeat your print statement at the very end of the script after `solve(board)`, you'll understand. You can also put it anywhere inside the function, as SpoonMeiser said. Perhaps together with `sleep(1)` which you can get from the `time` module to slow things down. (You can stop your program with Ctrl+C) – Joooeey Sep 23 '21 at 17:44
  • oh nested functions. it all clicked now, thanks! i can sleep comfortably now – abdurion Sep 23 '21 at 18:07
0

Assuming that there is (at the time of writing) an indentation error in the code in the question, the solver looks like this:

def solve(board):
    for row in range(9):
        for col in range(9):
            if board[row][col] == 0:
                # ignore the code here for now

                return False

    print(np.matrix(board))

Ignore what the code that I've replaced with a comment does for the time being, what this function does is take a board, iterate through every square, and if any is 0, return False, otherwise print the board.

That is, if board is already solved, print it, if it's not solved, return False.

The fact that False is returned is irrelevant; it might as well just be return.

So, for the code that was commented out. What that does is for each 0 that was found, try replacing it with each valid number, and recursively try and solve that.

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

So, if it was the only 0 on the board, then if it finds a valid number, the call to solve will result in the board being printed out.

If there are no valid numbers, then one of the numbers inserted into the board at some point was incorrect; we don't recursively call solve, and just proceed to return.

So the recursive call to solve may or may not have found a valid solution, the code isn't assuming only one solution exists and will continue looking in either case. It needs to undo what it has done, because it is only valid in the context of what previous recursive calls have chosen. Hence setting the square back to 0.

SpoonMeiser
  • 19,918
  • 8
  • 50
  • 68
  • It's getting clearer for me, one more thing if you could clarify it. you said "What that does is for each 0 that was found, try replacing it with each valid number, and recursively try and solve that." - my thought was, try 1 if valid, go to next box, if there's no valid number, go back and change 1 to another valid input. is my understanding of it right? or it does all the possibilities in the same time? thanks! – abdurion Sep 23 '21 at 17:24
  • Say 1 and 2 are both valid, it'll try 1, and then it'll try 2 whether or not 1 led to an actual solution; if you passed an empty board, it would print all possible solutions. Finding a valid solution doesn't stop the algorithm. Does that help? – SpoonMeiser Sep 23 '21 at 17:29
  • very much! thank you! – abdurion Sep 23 '21 at 17:33