2 minutes
Sudoku
In this post, we write a Python script that can solve a Sudoku. It originally started as a solution a Project Euler problem.
The proposed script below proceeds in 2 steps:
- first fill in the "easy" values where only one number is possible
- then explore paths by trying different possibilities.
import numpy as np
class Sudoku:
def __init__(self, grid):
# grid is a np array
self.grid = grid
def is_finished(self):
return np.all(self.grid != 0)
def possible_values(self, i, j):
"""
Lists possible values for square (i, j)
"""
values_rows = self.grid[i,:]
values_columns = self.grid[:,j]
c1 = i//3
c2 = j//3
values_3x3_squares = self.grid[c1*3:(c1+1)*3, c2*3:(c2+1)*3].reshape(-1)
excluded_values = np.hstack([values_rows, values_columns, values_3x3_squares])
possible_values = [i for i in range(1, 10) if i not in excluded_values]
return possible_values
def solve(self):
## PART 1: fill in the easy values
has_changed = True
while has_changed:
has_changed = False
for i in range(9):
for j in range(9):
if self.grid[i,j] == 0:
possible_values = self.possible_values(i, j)
if len(possible_values) == 1:
self.grid[i,j] = possible_values[0]
has_changed = True
elif len(possible_values) == 0: # sudoku not solvable, no possible value for this square
return False
if self.is_finished():
return True
## PART 2: explore paths
# a) find square with minimum amount of possibilities
min_possible_values = 10
for i in range(9):
for j in range(9):
if self.grid[i,j] == 0:
possible_values = self.possible_values(i, j)
if len(possible_values) < min_possible_values:
min_i = i
min_j = j
min_possible_values = len(possible_values)
# b) Loop through possibilities for that square
possible_values = self.possible_values(min_i, min_j)
for r in possible_values:
sudoku_r = self.grid.copy()
sudoku_r[min_i, min_j] = r
sudoku_r = Sudoku(sudoku_r)
res = sudoku_r.solve()
if res:
self.grid = sudoku_r.grid
return True
To solve Problem 96 of Project Euler, you can run:
import numpy as np
def load_sudokus(file):
with open(file, 'r') as f:
sudokus = []
sudoku = []
for line in f:
if 'Grid' in line:
if len(sudoku) > 0:
sudokus.append(np.array(sudoku))
sudoku = []
else:
sudoku.append([int(k) for k in list(line.strip())])
return sudokus
sudokus = load_sudokus("p096_sudoku.txt")
answer = 0
for i in range(len(sudokus)):
s = Sudoku(sudokus[i])
res = s.solve()
if res:
answer += int(str(s.grid[0,0]) + str(s.grid[0,1]) + str(s.grid[0,2]))
print(answer)
346 Words
2024-08-28 17:37