Python Sudoku Solver

Writing a sudoku solver is a great example to show how recursion works in Python. This particular technique is called ‘Backtracking’. Let’s see a possible implementation.

How Sudoku Works

There are 3 rules you have to follow when solving a sudoku:

  • All the numbers in a row must be different;
  • All the numbers in a column must be different;
  • All the numbers in one of the 9 highlighted 3X3 squares must be different.

So this means that at each step we’ll have to check whether it’s possible to put a number into a certain empty box.

Code Implementation

Let’s start to see some code now. At first we transfer our sudoku into a Python-readable numpy array.

import numpy as np

sudoku = np.array([ [0,0,0,2,6,0,7,0,1],
[6,8,0,0,7,0,0,9,0],
[1,9,0,0,0,4,5,0,0],
[8,2,0,1,0,0,0,4,0],
[0,0,4,6,0,2,9,0,0],
[0,5,0,0,0,3,0,2,8],
[0,0,9,3,0,0,0,7,4],
[0,4,0,0,5,0,0,3,6],
[7,0,3,0,1,8,0,0,0]]
)

You can see that it’s the exact copy of the photo above. Now let’s see the checker.

def checker(x, y, n):
global sudoku

# checks row and column
for i in range(9):
if (sudoku[y][i] == n) or (sudoku[i][x] == n):
return False

# checks the square
xi = (x // 3) * 3
yi = (y // 3) * 3
for i in range(3):
for j in range(3):
if sudoku[yi + i][xi + j] == n:
return False

return True

To access a box in column x and row y we need to call sudoku[y][x]. Also, remember a numpy array is 0-indexed, which means that its first element has index 0. The values of xi and yi represent the box which is found in the lower left corner of each of the nine squares. So our checker will return True if it’s possible to put the number n in the box with coordinates (x, y).

Now the fun begins. We’re ready to write the solver:

def solver():
    global sudoku
    for y in range(9):
        for x in range(9):
            if sudoku[y][x] == 0:
                for n in range(1, 10):
                    if checker(x, y, n):
                        sudoku[y][x] = n
                        solver()
                        sudoku[y][x] = 0
                return
    print(sudoku)
    input('Find more solutions if exist')

This program finds each empty box, puts a number in it and then calls itself again until the puzzle is completed. If no numbers can be put in an empty box, the ‘return’ allows to go one recursive step back and continue from there.

Notice that we’re working on the global variable sudoku. If we tried to pass sudoku into the function as a parameter, then the recursive trick wouldn’t work. In fact, by doing so we’re directly working on the same variable without creating any copy of it that would eventually blow up after a recursive step.

[[4 3 5 2 6 9 7 8 1]
[6 8 2 5 7 1 4 9 3]
[1 9 7 8 3 4 5 6 2]
[8 2 6 1 9 5 3 4 7]
[3 7 4 6 8 2 9 1 5]
[9 5 1 7 4 3 6 2 8]
[5 1 9 3 2 6 8 7 4]
[2 4 8 9 5 7 1 3 6]
[7 6 3 4 1 8 2 5 9]]

Here’s a solution. Anyway, let’s say we have more than one solution. Then, the code would print all of them at once. The last line of code allows you to see them one by one. You just need to press ‘Enter’ to make the code keep going after one solution (if found). Let’s try to remove a couple of numbers from our sudoku to see the result.

sudoku = np.array([ [0,0,0,2,6,0,7,0,1],
[6,8,0,0,7,0,0,9,0],
[1,9,0,0,0,4,5,0,0],
[8,2,0,1,0,0,0,4,0],
[0,0,4,6,0,2,9,0,0],
[0,5,0,0,0,3,0,2,8],
[0,0,9,3,0,0,0,7,4],
[0,4,0,0,5,0,0,3,6],
[7,0,3,0,0,0,0,0,0]]
)

So the numbers 1 and 8 were removed from the last row.

[[4 3 5 2 6 9 7 8 1]
[6 8 2 5 7 1 4 9 3]
[1 9 7 8 3 4 5 6 2]
[8 2 6 1 9 5 3 4 7]
[3 7 4 6 8 2 9 1 5]
[9 5 1 7 4 3 6 2 8]
[5 1 9 3 2 6 8 7 4]
[2 4 8 9 5 7 1 3 6]
[7 6 3 4 1 8 2 5 9]]
Find more solutions if exist
[[4 3 5 2 6 9 7 8 1]
[6 8 2 5 7 1 4 9 3]
[1 9 7 8 3 4 5 6 2]
[8 2 6 1 9 5 3 4 7]
[3 7 4 6 8 2 9 1 5]
[9 5 1 7 4 3 6 2 8]
[5 6 9 3 1 8 2 7 4]
[2 4 8 9 5 7 1 3 6]
[7 1 3 4 2 6 8 5 9]]
Find more solutions if exist
Process finished with exit code 0

Nice. But now let’s make an easier to use version.

Class SudokuSolver

import numpy as np


class SudokuSolver:

    def __init__(self, sudoku):
        self.start_sudoku = sudoku
        self.sudoku = sudoku
        self.solutions = {}
        self.counter = 0
        self.solver()

    def checker(self, x, y, n):

        #     checks row and column
        for i in range(9):
            if (self.sudoku[y][i] == n) or (self.sudoku[i][x] == n):
                return False

        #     checks the square
        xi = (x // 3) * 3
        yi = (y // 3) * 3
        for i in range(3):
            for j in range(3):
                if self.sudoku[yi + i][xi + j] == n:
                    return False

        return True

    def solver(self):
        for y in range(9):
            for x in range(9):
                if self.sudoku[y][x] == 0:
                    for n in range(1, 10):
                        if self.checker(x, y, n):
                            self.sudoku[y][x] = n
                            self.solver()
                            self.sudoku[y][x] = 0
                    return
        # print(self.sudoku)
        self.counter = self.counter + 1
        self.solutions[self.counter] = self.sudoku
        # input('Find more solutions if exist')

    def all_solutions(self):
        return self.solutions



sudoku = np.array([ [0,0,0,2,6,0,7,0,1],
                    [6,8,0,0,7,0,0,9,0],
                    [1,9,0,0,0,4,5,0,0],
                    [8,2,0,1,0,0,0,4,0],
                    [0,0,4,6,0,2,9,0,0],
                    [0,5,0,0,0,3,0,2,8],
                    [0,0,9,3,0,0,0,7,4],
                    [0,4,0,0,5,0,0,3,6],
                    [7,0,3,0,0,0,0,0,0]]
                 )

sudoku_master = SudokuSolver(sudoku)
sudoku_solutions = sudoku_master.all_solutions()

# print(sudoku_solutions)
# print(sudoku_master.start_sudoku)

This way you’ll have all of your solutions nicely collected in a dictionary as well as the initial sudoku stored in a private variable.

Scroll to top