Sudoku Solver
Sudoku is a puzzle game where you fill a grid with numbers. In the classical version, the puzzle is a 9x9 grid. The task is to fill this grid, such that in each row, each column and each of the nine 3x3 subgrids, the numbers of 1 to 9 appear only once.
The starting point of each puzzle is a set of prefilled numbers, where the count of given numbers usually determines the degree of difficulty. In a good sudoku, the initial numbers can only result in a unique solution. The following 3 puzzles are such examples:
Solver
When solving a Sudoku puzzle by hand, one has to apply certain logics to infer which number can be placed in which field with absolute certainty. A brute-force approach trying all combinations of numbers is not feasible for a human. Also, reversing wrongly placed numbers is very cumbersome.
Whereas a machine could easily brute-force all combinations, it is not efficient to do so. Defining all possible logics a human would apply to achieve absolute certainty about the positioning of numbers is probably also not efficient and will not make the code understandable.
A good solution for a machine is to make best guesses. And if these guesses do not end up in a solution, there needs to be a logic to reverse them. This concept is called backtracking.
Algorithm
Define the initial grid for the Sudoku puzzle.
For all empty fields in the grid, try:
- Place a valid number in the field and continue with the next empty field
- If there is no valid number, return to an earlier stage
- If there are no empty fields left, it is a solution
(See the code at the end of the post)
Code
# Check if a number x can be placed in field[r, c] of the grid
def check_field(r, c, x):
global m
# Check the row
for i in range(0,9):
if m[r][i] == x:
return False
# Check the column
for i in range(0,9):
if m[i][c] == x:
return False
# Check the box
r_ = (r // 3) * 3
c_ = (c // 3) * 3
for i in range(0,3):
for j in range(0,3):
if m[r_+i][c_+j] == x:
return False
return True
# The solver function - refer to sketched algortihm
def solve_Sudoku():
global m
global i
# Loop trough all rows and columns
for r in range(9):
for c in range(9):
# If the current field is empty
if m[r][c] == 0:
# Find a first number that can be placed in the empty field
for n in range(1, 10):
if check_field(r, c, n):
# Enter value
m[r][c] = n
# Check next cells
solve_Sudoku()
# Reached dead end
m[r][c] = 0
# Could not find a valid number to place in the field
return
# Keep track of the number of solutions
i = i + 1
if print_m:
print(np.matrix(m))
print(i)
# A wrapper function to easily call the solver on multiple puzzles
def solve_Sudoku_bench(m_, print_matrix = False):
global m
global i
global print_m
print_m = print_matrix
m = m_.copy()
i = 0
solve_Sudoku()
# Solve the easy puzzle
m_easy = [
[0, 8, 6, 0, 1, 0, 0, 4, 3],
[0, 0, 9, 0, 0, 7, 0, 6, 5],
[0, 5, 2, 6, 0, 3, 0, 0, 0],
[0, 7, 0, 0, 3, 0, 5, 0, 0],
[0, 0, 0, 5, 9, 0, 4, 3, 0],
[0, 3, 5, 0, 0, 4, 0, 1, 0],
[0, 0, 0, 8, 0, 1, 6, 0, 0],
[6, 2, 7, 3, 0, 0, 1, 0, 8],
[0, 9, 8, 0, 0, 6, 3, 7, 0]
]
solve_Sudoku_bench(m_easy, print_matrix = True)