I want to write a code in python to solve a sudoku puzzle. Do you guys have any idea about a good algorithm for this purpose. I read somewhere in net about a algorithm which
Here is a much faster solution based on hari's answer. The basic difference is that we keep a set of possible values for cells that don't have a value assigned. So when we try a new value, we only try valid values and we also propagate what this choice means for the rest of the sudoku. In the propagation step, we remove from the set of valid values for each cell the values that already appear in the row, column, or the same block. If only one number is left in the set, we know that the position (cell) has to have that value.
This method is known as forward checking and look ahead (http://ktiml.mff.cuni.cz/~bartak/constraints/propagation.html).
The implementation below needs one iteration (calls of solve) while hari's implementation needs 487. Of course my code is a bit longer. The propagate method is also not optimal.
import sys
from copy import deepcopy
def output(a):
sys.stdout.write(str(a))
N = 9
field = [[5,1,7,6,0,0,0,3,4],
[2,8,9,0,0,4,0,0,0],
[3,4,6,2,0,5,0,9,0],
[6,0,2,0,0,0,0,1,0],
[0,3,8,0,0,6,0,4,7],
[0,0,0,0,0,0,0,0,0],
[0,9,0,0,0,0,0,7,8],
[7,0,3,4,0,0,5,6,0],
[0,0,0,0,0,0,0,0,0]]
def print_field(field):
if not field:
output("No solution")
return
for i in range(N):
for j in range(N):
cell = field[i][j]
if cell == 0 or isinstance(cell, set):
output('.')
else:
output(cell)
if (j + 1) % 3 == 0 and j < 8:
output(' |')
if j != 8:
output(' ')
output('\n')
if (i + 1) % 3 == 0 and i < 8:
output("- - - + - - - + - - -\n")
def read(field):
""" Read field into state (replace 0 with set of possible values) """
state = deepcopy(field)
for i in range(N):
for j in range(N):
cell = state[i][j]
if cell == 0:
state[i][j] = set(range(1,10))
return state
state = read(field)
def done(state):
""" Are we done? """
for row in state:
for cell in row:
if isinstance(cell, set):
return False
return True
def propagate_step(state):
"""
Propagate one step.
@return: A two-tuple that says whether the configuration
is solvable and whether the propagation changed
the state.
"""
new_units = False
# propagate row rule
for i in range(N):
row = state[i]
values = set([x for x in row if not isinstance(x, set)])
for j in range(N):
if isinstance(state[i][j], set):
state[i][j] -= values
if len(state[i][j]) == 1:
val = state[i][j].pop()
state[i][j] = val
values.add(val)
new_units = True
elif len(state[i][j]) == 0:
return False, None
# propagate column rule
for j in range(N):
column = [state[x][j] for x in range(N)]
values = set([x for x in column if not isinstance(x, set)])
for i in range(N):
if isinstance(state[i][j], set):
state[i][j] -= values
if len(state[i][j]) == 1:
val = state[i][j].pop()
state[i][j] = val
values.add(val)
new_units = True
elif len(state[i][j]) == 0:
return False, None
# propagate cell rule
for x in range(3):
for y in range(3):
values = set()
for i in range(3 * x, 3 * x + 3):
for j in range(3 * y, 3 * y + 3):
cell = state[i][j]
if not isinstance(cell, set):
values.add(cell)
for i in range(3 * x, 3 * x + 3):
for j in range(3 * y, 3 * y + 3):
if isinstance(state[i][j], set):
state[i][j] -= values
if len(state[i][j]) == 1:
val = state[i][j].pop()
state[i][j] = val
values.add(val)
new_units = True
elif len(state[i][j]) == 0:
return False, None
return True, new_units
def propagate(state):
""" Propagate until we reach a fixpoint """
while True:
solvable, new_unit = propagate_step(state)
if not solvable:
return False
if not new_unit:
return True
def solve(state):
""" Solve sudoku """
solvable = propagate(state)
if not solvable:
return None
if done(state):
return state
for i in range(N):
for j in range(N):
cell = state[i][j]
if isinstance(cell, set):
for value in cell:
new_state = deepcopy(state)
new_state[i][j] = value
solved = solve(new_state)
if solved is not None:
return solved
return None
print_field(solve(state))
Using google ortools - the following will either generate a dummy sudoku array or will solve a candidate. The code is probably more verbose than required, any feedback is appreciated.
The idea is to solve a constraint-programming problem that involves
In addition, when trying to solve existing sudoku, we add additional constraints on variables that already have assigned value.
from ortools.constraint_solver import pywrapcp
import numpy as np
def sudoku_solver(candidate = None):
solver = pywrapcp.Solver("Sudoku")
variables = [solver.IntVar(1,9,f"x{i}") for i in range(81)]
if len(candidate)>0:
candidate = np.int64(candidate)
for i in range(81):
val = candidate[i]
if val !=0:
solver.Add(variables[i] == int(val))
def set_constraints():
for i in range(9):
# All columns should be different
q=[variables[j] for j in list(range(i,81,9))]
solver.Add(solver.AllDifferent(q))
#All rows should be different
q2=[variables[j] for j in list(range(i*9,(i+1)*9))]
solver.Add(solver.AllDifferent(q2))
#All values in the sub-matrix should be different
a = list(range(81))
sub_blocks = a[3*i:3*(i+9):9] + a[3*i+1:3*(i+9)+1:9] + a[3*i+2:3*(i+9)+2:9]
q3 = [variables[j] for j in sub_blocks]
solver.Add(solver.AllDifferent(q3))
set_constraints()
db = solver.Phase(variables, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE)
solver.NewSearch(db)
results_store =[]
num_solutions =0
total_solutions = 5
while solver.NextSolution() and num_solutions<total_solutions:
results = [j.Value() for j in variables]
results_store.append(results)
num_solutions +=1
return results_store
candidate = np.array([0, 2, 0, 4, 5, 6, 0, 8, 0, 0, 5, 6, 7, 8, 9, 0, 0, 3, 7, 0, 9, 0,
2, 0, 4, 5, 6, 2, 0, 1, 5, 0, 4, 8, 9, 7, 5, 0, 4, 8, 0, 0, 0, 0,
0, 3, 1, 0, 6, 4, 5, 9, 7, 0, 0, 0, 5, 0, 7, 8, 3, 1, 2, 8, 0, 7,
0, 1, 0, 5, 0, 4, 9, 7, 8, 0, 3, 0, 0, 0, 5])
results_store = sudoku_solver(candidate)
There are four steps to solve a sudoku puzzle:
If still not solved then do it for next possible value and run it in recursion.
import math
import sys
def is_solved(l):
for x, i in enumerate(l):
for y, j in enumerate(i):
if j == 0:
# Incomplete
return None
for p in range(9):
if p != x and j == l[p][y]:
# Error
print('horizontal issue detected!', (x, y))
return False
if p != y and j == l[x][p]:
# Error
print('vertical issue detected!', (x, y))
return False
i_n, j_n = get_box_start_coordinate(x, y)
for (i, j) in [(i, j) for p in range(i_n, i_n + 3) for q in range(j_n, j_n + 3)
if (p, q) != (x, y) and j == l[p][q]]:
# Error
print('box issue detected!', (x, y))
return False
# Solved
return True
def is_valid(l):
for x, i in enumerate(l):
for y, j in enumerate(i):
if j != 0:
for p in range(9):
if p != x and j == l[p][y]:
# Error
print('horizontal issue detected!', (x, y))
return False
if p != y and j == l[x][p]:
# Error
print('vertical issue detected!', (x, y))
return False
i_n, j_n = get_box_start_coordinate(x, y)
for (i, j) in [(i, j) for p in range(i_n, i_n + 3) for q in range(j_n, j_n + 3)
if (p, q) != (x, y) and j == l[p][q]]:
# Error
print('box issue detected!', (x, y))
return False
# Solved
return True
def get_box_start_coordinate(x, y):
return 3 * int(math.floor(x/3)), 3 * int(math.floor(y/3))
def get_horizontal(x, y, l):
return [l[x][i] for i in range(9) if l[x][i] > 0]
def get_vertical(x, y, l):
return [l[i][y] for i in range(9) if l[i][y] > 0]
def get_box(x, y, l):
existing = []
i_n, j_n = get_box_start_coordinate(x, y)
for (i, j) in [(i, j) for i in range(i_n, i_n + 3) for j in range(j_n, j_n + 3)]:
existing.append(l[i][j]) if l[i][j] > 0 else None
return existing
def detect_and_simplify_double_pairs(l, pl):
for (i, j) in [(i, j) for i in range(9) for j in range(9) if len(pl[i][j]) == 2]:
temp_pair = pl[i][j]
for p in (p for p in range(j+1, 9) if len(pl[i][p]) == 2 and len(set(pl[i][p]) & set(temp_pair)) == 2):
for q in (q for q in range(9) if q != j and q != p):
pl[i][q] = list(set(pl[i][q]) - set(temp_pair))
if len(pl[i][q]) == 1:
l[i][q] = pl[i][q].pop()
return True
for p in (p for p in range(i+1, 9) if len(pl[p][j]) == 2 and len(set(pl[p][j]) & set(temp_pair)) == 2):
for q in (q for q in range(9) if q != i and p != q):
pl[q][j] = list(set(pl[q][j]) - set(temp_pair))
if len(pl[q][j]) == 1:
l[q][j] = pl[q][j].pop()
return True
i_n, j_n = get_box_start_coordinate(i, j)
for (a, b) in [(a, b) for a in range(i_n, i_n+3) for b in range(j_n, j_n+3)
if (a, b) != (i, j) and len(pl[a][b]) == 2 and len(set(pl[a][b]) & set(temp_pair)) == 2]:
for (c, d) in [(c, d) for c in range(i_n, i_n+3) for d in range(j_n, j_n+3)
if (c, d) != (a, b) and (c, d) != (i, j)]:
pl[c][d] = list(set(pl[c][d]) - set(temp_pair))
if len(pl[c][d]) == 1:
l[c][d] = pl[c][d].pop()
return True
return False
def update_unique_horizontal(x, y, l, pl):
tl = pl[x][y]
for i in (i for i in range(9) if i != y):
tl = list(set(tl) - set(pl[x][i]))
if len(tl) == 1:
l[x][y] = tl.pop()
return True
return False
def update_unique_vertical(x, y, l, pl):
tl = pl[x][y]
for i in (i for i in range(9) if i != x):
tl = list(set(tl) - set(pl[i][y]))
if len(tl) == 1:
l[x][y] = tl.pop()
return True
return False
def update_unique_box(x, y, l, pl):
tl = pl[x][y]
i_n, j_n = get_box_start_coordinate(x, y)
for (i, j) in [(i, j) for i in range(i_n, i_n+3) for j in range(j_n, j_n+3) if (i, j) != (x, y)]:
tl = list(set(tl) - set(pl[i][j]))
if len(tl) == 1:
l[x][y] = tl.pop()
return True
return False
def find_and_place_possibles(l):
while True:
pl = populate_possibles(l)
if pl != False:
return pl
def populate_possibles(l):
pl = [[[]for j in i] for i in l]
for (i, j) in [(i, j) for i in range(9) for j in range(9) if l[i][j] == 0]:
p = list(set(range(1, 10)) - set(get_horizontal(i, j, l) +
get_vertical(i, j, l) + get_box(i, j, l)))
if len(p) == 1:
l[i][j] = p.pop()
return False
else:
pl[i][j] = p
return pl
def find_and_remove_uniques(l, pl):
for (i, j) in [(i, j) for i in range(9) for j in range(9) if l[i][j] == 0]:
if update_unique_horizontal(i, j, l, pl) == True:
return True
if update_unique_vertical(i, j, l, pl) == True:
return True
if update_unique_box(i, j, l, pl) == True:
return True
return False
def try_with_possibilities(l):
while True:
improv = False
pl = find_and_place_possibles(l)
if detect_and_simplify_double_pairs(
l, pl) == True:
continue
if find_and_remove_uniques(
l, pl) == True:
continue
if improv == False:
break
return pl
def get_first_conflict(pl):
for (x, y) in [(x, y) for x, i in enumerate(pl) for y, j in enumerate(i) if len(j) > 0]:
return (x, y)
def get_deep_copy(l):
new_list = [i[:] for i in l]
return new_list
def run_assumption(l, pl):
try:
c = get_first_conflict(pl)
fl = pl[c[0]
][c[1]]
# print('Assumption Index : ', c)
# print('Assumption List: ', fl)
except:
return False
for i in fl:
new_list = get_deep_copy(l)
new_list[c[0]][c[1]] = i
new_pl = try_with_possibilities(new_list)
is_done = is_solved(new_list)
if is_done == True:
l = new_list
return new_list
else:
new_list = run_assumption(new_list, new_pl)
if new_list != False and is_solved(new_list) == True:
return new_list
return False
if __name__ == "__main__":
l = [
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 8, 0, 0, 0, 0, 4, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 6, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[2, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 2, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0]
]
# This puzzle copied from Hacked rank test case
if is_valid(l) == False:
print("Sorry! Invalid.")
sys.exit()
pl = try_with_possibilities(l)
is_done = is_solved(l)
if is_done == True:
for i in l:
print(i)
print("Solved!!!")
sys.exit()
print("Unable to solve by traditional ways")
print("Starting assumption based solving")
new_list = run_assumption(l, pl)
if new_list != False:
is_done = is_solved(new_list)
print('is solved ? - ', is_done)
for i in new_list:
print(i)
if is_done == True:
print("Solved!!! with assumptions.")
sys.exit()
print(l)
print("Sorry! No Solution. Need to fix the valid function :(")
sys.exit()
Here is my sudoku solver in python. It uses simple backtracking algorithm to solve the puzzle. For simplicity no input validations or fancy output is done. It's the bare minimum code which solves the problem.
It takes 9X9 grid partially filled with numbers. A cell with value 0 indicates that it is not filled.
def findNextCellToFill(grid, i, j):
for x in range(i,9):
for y in range(j,9):
if grid[x][y] == 0:
return x,y
for x in range(0,9):
for y in range(0,9):
if grid[x][y] == 0:
return x,y
return -1,-1
def isValid(grid, i, j, e):
rowOk = all([e != grid[i][x] for x in range(9)])
if rowOk:
columnOk = all([e != grid[x][j] for x in range(9)])
if columnOk:
# finding the top left x,y co-ordinates of the section containing the i,j cell
secTopX, secTopY = 3 *(i//3), 3 *(j//3) #floored quotient should be used here.
for x in range(secTopX, secTopX+3):
for y in range(secTopY, secTopY+3):
if grid[x][y] == e:
return False
return True
return False
def solveSudoku(grid, i=0, j=0):
i,j = findNextCellToFill(grid, i, j)
if i == -1:
return True
for e in range(1,10):
if isValid(grid,i,j,e):
grid[i][j] = e
if solveSudoku(grid, i, j):
return True
# Undo the current cell for backtracking
grid[i][j] = 0
return False
>>> input = [[5,1,7,6,0,0,0,3,4],[2,8,9,0,0,4,0,0,0],[3,4,6,2,0,5,0,9,0],[6,0,2,0,0,0,0,1,0],[0,3,8,0,0,6,0,4,7],[0,0,0,0,0,0,0,0,0],[0,9,0,0,0,0,0,7,8],[7,0,3,4,0,0,5,6,0],[0,0,0,0,0,0,0,0,0]]
>>> solveSudoku(input)
True
>>> input
[[5, 1, 7, 6, 9, 8, 2, 3, 4], [2, 8, 9, 1, 3, 4, 7, 5, 6], [3, 4, 6, 2, 7, 5, 8, 9, 1], [6, 7, 2, 8, 4, 9, 3, 1, 5], [1, 3, 8, 5, 2, 6, 9, 4, 7], [9, 5, 4, 7, 1, 3, 6, 8, 2], [4, 9, 5, 3, 6, 2, 1, 7, 8], [7, 2, 3, 4, 8, 1, 5, 6, 9], [8, 6, 1, 9, 5, 7, 4, 2, 3]]
The above one is very basic backtracking algorithm which is explained at many places. But the most interesting and natural of the sudoku solving strategies I came across is this one from here
Not gonna write full code, but I did a sudoku solver a long time ago. I found that it didn't always solve it (the thing people do when they have a newspaper is incomplete!), but now think I know how to do it.
Hi I've blogged about writing a sudoku solver from scratch in Python and currently writing a whole series about writing a constraint programming solver in Julia (another high level but faster language) You can read the sudoku problem from a file which seems to be easier more handy than a gui or cli way. The general idea it uses is constraint programming. I use the all different / unique constraint but I coded it myself instead of using a constraint programming solver.
If someone is interested: