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