Skip to content

Instantly share code, notes, and snippets.

@4ft35t
Last active August 29, 2024 13:45
Show Gist options
  • Save 4ft35t/d284860ff96ddc4d7aae5a4262af907a to your computer and use it in GitHub Desktop.
Save 4ft35t/d284860ff96ddc4d7aae5a4262af907a to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
# coding: utf-8
# @2024-07-25 22:54:41
# vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4:
'''
resolve sudoku
'''
from copy import deepcopy
class Cell:
def __init__(self, x, y, value=None):
self.x = x
self.y = y
self.init_value(value)
def init_value(self, value):
self.value = 0
if value:
self.values = [value]
else:
# possible values of cell
self.values = list(range(1, 10))
def get_near_cells(self, all_cells):
'''
(0,0) ... (8,0)
...
(0,8) ... (8,8)
9*9 cells
'''
row = [i for i in all_cells if i.y == self.y]
col = [i for i in all_cells if i.x == self.x]
block = [i for i in all_cells if i.x // 3 == self.x // 3 and i.y // 3 == self.y // 3]
return row + col + block
def del_near_value(self, all_cells):
'''delete current cell value from near cells'''
near_cells = self.get_near_cells(all_cells)
for cell in near_cells:
if self.value in cell.values:
cell.values.remove(self.value)
def set_value(self, all_cells):
'''cell has only one value, set it'''
value = self.values[0]
self.value = value
self.values = []
# del value from near cells
self.del_near_value(all_cells)
class Sudoku:
def __init__(self, matrix):
self.cells = []
for y, row in enumerate(matrix):
for x, value in enumerate(row):
cell = Cell(x, y, value)
self.cells.append(cell)
def set_value_in_block(self):
'''in a 3*3 block, if a number only in one cell's values, find and set it
eg in matrix5:
in the top center block, 5 only (3,2) cell's values, while this cell's values is [3,5,6]
so set (3,2) cell's value to 5
'''
# 3*3 block
for x in range(0, 9, 3):
for y in range(0, 9, 3):
block = [i for i in self.cells if i.x // 3 == x // 3 and i.y // 3 == y // 3]
for n in range(1, 10):
cells = [i for i in block if n in i.values]
if len(cells) == 1:
cell = cells[0]
cell.values = [n]
def resolve_with_backtrack(self):
'''when no cell has only one value, use backtrack to resolve sudoku'''
shortest = sorted([i for i in self.cells if i.values], key=lambda x: len(x.values))[0]
current_state = deepcopy(self.cells)
# try each value of cell
cell = shortest
for value in cell.values:
new_sudoku = Sudoku([])
new_sudoku.cells = deepcopy(current_state)
new_cell = [i for i in new_sudoku.cells if i.x == cell.x and i.y == cell.y][0]
new_cell.values = [value]
result = new_sudoku.resolve()
if result:
print('cell:', (cell.x, cell.y), 'value:', value, cell.values)
return new_sudoku
def resolve(self):
while True:
# set value for cells which has only one value in a 3*3 block
self.set_value_in_block()
count = 0
# set value for cells which has only one value
for cell in self.cells:
if len(cell.values) == 1:
cell.set_value(self.cells)
count += 1
# check if all cells have value, success
if all([cell.value for cell in self.cells]):
break
# check if there is a cell which has no value, failed
if any([not cell.value and not cell.values for cell in self.cells]):
return []
# hard level, no cell has only one value
# use backtrack to resolve
if count == 0:
result = self.resolve_with_backtrack()
self.cells = result.cells
return [[cell.value for cell in self.cells[i:i+9]] for i in range(0, 81, 9)]
def show(self):
print('-' * 23)
for i, cell in enumerate(self.cells):
print(cell.value, end=' ')
if i % 3 == 2:
print('|', end=' ')
if i % 9 == 8:
print()
if i % 27 == 26:
print('-' * 23)
def test():
matrix1 = [
[0, 6, 1, 0, 0, 4, 7, 0, 0],
[0, 0, 9, 0, 0, 0, 6, 0, 0],
[0, 0, 8, 0, 1, 0, 0, 9, 0],
[0, 0, 7, 0, 2, 0, 3, 8, 0],
[0, 0, 0, 8, 3, 9, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 3, 7, 0, 0, 0, 6],
[0, 2, 0, 1, 0, 5, 8, 3, 0],
[0, 0, 0, 0, 8, 0, 2, 0, 0]
]
matrix2 = [
[0, 0, 0, 0, 0, 0, 4, 5, 6],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 5, 0, 0, 4, 6, 0, 9, 2],
[2, 8, 0, 7, 0, 0, 0, 0, 3],
[6, 0, 0, 0, 1, 0, 5, 0, 0],
[0, 0, 0, 0, 0, 8, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 3, 0, 0],
[8, 0, 0, 9, 0, 4, 6, 0, 0],
[0, 2, 0, 8, 0, 0, 0, 0, 1]
]
matrix3 = [
[0, 0, 0, 0, 0, 0, 0, 1, 2],
[0, 0, 0, 0, 3, 5, 0, 0, 0],
[0, 0, 0, 6, 0, 0, 0, 7, 0],
[7, 0, 0, 0, 0, 0, 3, 0, 0],
[0, 0, 0, 4, 0, 0, 8, 0, 0],
[1, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 2, 0, 0, 0, 0],
[0, 8, 0, 0, 0, 0, 0, 4, 0],
[0, 5, 0, 0, 0, 0, 6, 0, 0]
]
matrix4 = [
[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]
]
matrix5 = [
[0, 0, 6, 0, 8, 0, 5, 7, 0],
[0, 0, 0, 4, 0, 0, 3, 0, 0],
[0, 0, 0, 0, 2, 7, 1, 0, 0],
[0, 7, 0, 0, 5, 0, 0, 4, 0],
[0, 4, 9, 0, 0, 8, 6, 0, 0],
[0, 0, 3, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 2, 3, 5, 9, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 9, 0, 0, 0, 2, 0]
]
matrix_list = [matrix1, matrix2, matrix3, matrix4, matrix5]
# matrix_list = [ matrix5]
for matrix in matrix_list:
print('matrix:')
for row in matrix:
print(row)
print('result:')
sudoku = Sudoku(matrix)
result = sudoku.resolve()
if result:
sudoku.show()
if __name__ == '__main__':
test()
@4ft35t
Copy link
Author

4ft35t commented Aug 29, 2024

add resolve_with_backtrack to solve matrix5

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment