can solve easy sudokus, get's stuck on harder ones (two-step logic)

This commit is contained in:
Joseph Hopfmüller
2023-02-09 09:46:36 +01:00
parent 6262a7c836
commit 6bbbf72b1e

234
sudoku.py
View File

@@ -1,10 +1,59 @@
from copy import deepcopy # import colorama
import random # from termcolor import colored
class cell():
def __init__(self, row, col):
self.possible = set([i for i in range(1,10)])
self.solved = False
self.solution = 0
self.prefilled = False
self.row = row
self.col = col
def __str__(self):
retstr = 'solved ' if self.solved else 'unsolved '
retstr += f'sudoku_cell @ ({self.row},{self.col}) '
if self.solved:
retstr += f'with value {self.solution}'
else:
retstr += f'with possible values {self.possible}'
return retstr
def set_value(self, value):
self.possible = {value}
self.solution = value
self.solved = True
self.prefilled = True
def remove_value(self, value):
if not self.solved:
self.possible.discard(value)
if len(self.possible) == 1:
self.solution = list(self.possible)[0]
self.solved = True
pass # for debugging
def collapse(self, values):
if not self.possible:
return False
a = self.possible - self.possible.intersection(values)
if not a:
return False
self.possible = a
if len(self.possible) == 1:
self.solution = list(self.possible)[0]
self.solved = True
return True
def __len__(self):
return len(self.possible)
@property
def values(self):
return list(self.possible)
class sudoku_grid(): class sudoku_grid():
def __init__(self, prefilled_cells): def __init__(self, prefilled_cells):
# cell = {'possible': set([i for i in range(1,10)]), 'solution': 0} self.grid = [[cell(i,j) for j in range(9)] for i in range(9)]
self.grid = [[{'possible': set([i for i in range(1,10)]), 'solution': 0} for _ in range(9)] for _ in range(9)]
# prefilled_cells is a nested list (9x9) of values (1-9), 0 specifies an empty cell # prefilled_cells is a nested list (9x9) of values (1-9), 0 specifies an empty cell
try: try:
@@ -17,8 +66,7 @@ class sudoku_grid():
for j in range(9): for j in range(9):
# if prefilled_cell in valid range: fill in matching cell an mark as solved # if prefilled_cell in valid range: fill in matching cell an mark as solved
if 1 <= prefilled_cells[i][j] <= 9: if 1 <= prefilled_cells[i][j] <= 9:
self.grid[i][j]['possible'] = {prefilled_cells[i][j]} self.grid[i][j].set_value(prefilled_cells[i][j])
self.grid[i][j]['solution'] = prefilled_cells[i][j]
ctr += 1 ctr += 1
if ctr == 0: if ctr == 0:
@@ -27,63 +75,90 @@ class sudoku_grid():
print(f'{e}') print(f'{e}')
def __str__(self): def __str__(self):
retstr = 'sudoku: \n' # retstr = ' Sudoku\n'
retstr = ''
retstr += ' 0 1 2 3 4 5 6 7 8\n'
retstr += ' ╔═══╤═══╤═══╦═══╤═══╤═══╦═══╤═══╤═══╗\n'
for i in range(9): for i in range(9):
for j in range(9): for j in range(9):
if self.grid[i][j]['solution']: if j == 0:
current_val = self.grid[i][j]['solution'] retstr += f'{i}'
retstr += f'{current_val} ' if self.grid[i][j].solved:
current_val = self.grid[i][j].solution
if self.grid[i][j].prefilled:
retstr += f'{current_val}'
else:
retstr += f'\033[92m{current_val}\033[00m'
else: else:
retstr += '_ ' retstr += ' '
if j == 2 or j == 5: if j == 2 or j == 5 or j == 8:
retstr += '# ' retstr += ' '
else:
retstr += ''
if i == 2 or i == 5: if i == 2 or i == 5:
retstr += '\n# # # # # # # # # # # ' retstr += '\n ╠═══╪═══╪═══╬═══╪═══╪═══╬═══╪═══╪═══╣\n'
retstr += '\n' elif i == 8:
pass
else:
retstr += '\n ╟───┼───┼───╫───┼───┼───╫───┼───┼───╢\n'
retstr += '\n ╚═══╧═══╧═══╩═══╧═══╧═══╩═══╧═══╧═══╝'
return retstr return retstr
def removeValue(self, row, col, value):
if self.grid[row][col]['possible']:
self.grid[row][col]['possible'].discard(value)
pass
def iterate(self):
def iterate1(self):
# iterate over all cells # iterate over all cells
for i in range(9): for i in range(9):
for j in range(9): for j in range(9):
# # remove posibble values based on solved cells
# get the value current_value = self.grid[i][j].solution
current_value = self.grid[i][j]['solution']
if current_value: if current_value:
for k in range(9): for k in range(9):
self.removeValue(i, k, current_value) # remove value from current row current_cell = self.grid[i][k]
self.removeValue(k, j, current_value) # remove value from current column current_cell.remove_value(current_value) # remove value from current row
current_cell = self.grid[k][j]
current_cell.remove_value(current_value) # remove value from current column
for k in range(3): for k in range(3):
for l in range(3): for l in range(3):
self.removeValue((i//3)*3+(i+k)%3, (j//3)*3+(j+l)%3, current_value) # remove value from current 3x3 box current_cell = self.grid[(i//3)*3+(i+k)%3][(j//3)*3+(j+l)%3]
current_cell.remove_value(current_value) # remove value from current 3x3 box
def iterate2(self):
for i in range(9):
for j in range(9):
current_cell = self.grid[i][j]
if current_cell.solved:
continue
current_set = set() current_set = set()
add = 0
for k in range(8): for k in range(8):
current_set = current_set and self.grid[i][(j+k)%9]['possible'] if len(current_set) == 9:
current_set = current_set and self.grid[(i+k)%9][j]['possible'] continue
current_cell2 = self.grid[i][(j+k+1)%9]
current_set = current_set.union(current_cell2.possible)
current_cell.collapse(current_set)
current_set = set()
for k in range(8):
if len(current_set) == 9:
continue
current_cell2 = self.grid[(i+k+1)%9][j]
current_set = current_set.union(current_cell2.possible)
current_cell.collapse(current_set)
add = 0
current_set = set()
for k in range(8):
if len(current_set) == 9:
continue
if (i//3)*3+k//3 == i and (j//3)*3+k%3 == j: if (i//3)*3+k//3 == i and (j//3)*3+k%3 == j:
add = 1 add = 1
l = k + add l = k + add
row = (i//3)*3+l//3 row = (i//3)*3+l//3
col = (j//3)*3+l%3 col = (j//3)*3+l%3
current_set = current_set and self.grid[row][col]['possible'] current_cell2 = self.grid[row][col]
current_set = current_set.union(current_cell2.possible)
new_set = self.grid[i][j]['possible']-self.grid[i][j]['possible'].intersection(current_set) current_cell.collapse(current_set)
if new_set:
self.grid[i][j]['possible'] = new_set
for i in range(9):
for j in range(9):
if len(self.grid[i][j]['possible']) == 1:
self.grid[i][j]['solution'] = list(self.grid[i][j]['possible'])[0]
@@ -94,11 +169,12 @@ class sudoku_grid():
lowest_e = 10 lowest_e = 10
for i in range(9): for i in range(9):
for j in range(9): for j in range(9):
if self.grid[i][j]['solution']: current_cell = self.grid[i][j]
if current_cell.solved:
continue continue
e = len(self.grid[i][j]['possible']) e = len(current_cell)
if e < lowest_e: if e < lowest_e:
sols = list(self.grid[i][j]['possible']) sols = current_cell.values
lowest_i = i lowest_i = i
lowest_j = j lowest_j = j
lowest_e = e lowest_e = e
@@ -111,55 +187,63 @@ class sudoku_grid():
return (lowest_i, lowest_j, lowest_e, sols) return (lowest_i, lowest_j, lowest_e, sols)
def collapse_cell(self, row, col): def collapse_cell(self, row, col):
if self.grid[row][col]['solution']: if self.grid[row][col].solved:
return None return None
possible = self.grid[row][col]['possible'] possible = self.grid[row][col].possible
if len(possible) == 1: if len(possible) == 1:
self.grid[row][col]['solution'] = list(possible)[0] self.grid[row][col].solved = list(possible)[0]
def single_solutions_exist(self): def single_solutions_exist(self):
for i in range(9): for i in range(9):
for j in range(9): for j in range(9):
if self.grid[i][j]['possible']: if self.grid[i][j].possible:
if len(self.grid[i][j]['possible']) == 1: if len(self.grid[i][j].possible) == 1:
return True return True
return False return False
def is_solved(self): def is_solved(self):
for i in range(9): for i in range(9):
for j in range(9): for j in range(9):
if not self.grid[i][j]['solution']: if not self.grid[i][j].solved:
return False return False
return True return True
iteration = 1 iteration = 1
if __name__ == '__main__': if __name__ == '__main__':
prefilled = [ [0,4,9,7,0,5,0,0,0], # colorama.init()
[0,0,0,0,0,4,0,0,3], # prefilled = [ [0,4,9, 7,0,5, 0,0,0],
[6,0,1,2,0,0,0,7,0], # [0,0,0, 0,0,4, 0,0,3],
[0,0,0,0,9,1,0,0,5], # [6,0,1, 2,0,0, 0,7,0],
[0,2,4,0,6,8,7,3,1],
[1,5,8,0,2,7,4,9,0], # [0,0,0, 0,9,1, 0,0,5],
[0,0,0,0,0,2,6,4,0], # [0,2,4, 0,6,8, 7,3,1],
[0,6,0,1,0,0,0,0,0], # [1,5,8, 0,2,7, 4,9,0],
[4,0,5,0,0,0,3,0,2]]
# [0,0,0, 0,0,2, 6,4,0],
# [0,6,0, 1,0,0, 0,0,0],
# [4,0,5, 0,0,0, 3,0,2]]
prefilled = [ [0,0,7, 0,0,5, 0,0,3],
[0,0,9, 0,6,0, 0,0,0],
[3,6,0, 0,0,8, 2,0,0],
[0,0,6, 0,0,0, 0,0,0],
[5,1,0, 0,8,0, 0,0,9],
[0,0,0, 0,0,2, 0,4,0],
[0,0,0, 5,0,0, 9,0,0],
[8,3,0, 0,1,0, 0,0,5],
[7,0,0, 0,0,0, 0,0,0]]
sudoku = sudoku_grid(prefilled) sudoku = sudoku_grid(prefilled)
print(sudoku) print(sudoku)
while not sudoku.is_solved(): while not sudoku.is_solved():
sudoku.iterate() sudoku.iterate1()
# cnt = 0 print(f'Iteration {iteration}a')
# [row, col, entropy, solutions] = sudoku.find_lowest_entropy() print(sudoku)
# if row == -1: sudoku.iterate2()
# print(f'No solution found! Iteration {iteration}') print(f'Iteration {iteration}b')
# break print(sudoku)
# print(f'Lowest Entropy in Iteration {iteration} ({cnt}): {entropy} in ({row},{col}) with solutions {solutions}')
# sudoku.collapse_cell(row, col)
# while sudoku.single_solutions_exist():
# cnt += 1
# [row, col, entropy, solutions] = sudoku.find_lowest_entropy()
# print(f'Lowest Entropy in Iteration {iteration} ({cnt}): {entropy} in ({row},{col}) with solutions {solutions}')
# sudoku.collapse_cell(row,col)
# print(sudoku)
iteration += 1 iteration += 1
print(sudoku) print(sudoku)