From fde087dec76466e6b5fb92ec44c75f10c1375392 Mon Sep 17 00:00:00 2001 From: Maxime Vorwerk Date: Thu, 3 Oct 2024 15:34:05 +0200 Subject: [PATCH] python version functional --- .gitignore | 1 + cell.py | 29 ++++++++++ grid.py | 89 ++++++++++++++++++++++++++++ solver.py | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ sudokusolver.py | 22 +++++++ 5 files changed, 292 insertions(+) create mode 100644 .gitignore create mode 100644 cell.py create mode 100644 grid.py create mode 100644 solver.py create mode 100755 sudokusolver.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bee8a64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ diff --git a/cell.py b/cell.py new file mode 100644 index 0000000..107ebe4 --- /dev/null +++ b/cell.py @@ -0,0 +1,29 @@ +class cell: + row = [] + col = [] + square = [] + candidates = [] + is_set = False + n = -1 + + def __init__(self, n): + self.candidates = [1,2,3,4,5,6,7,8,9] + self.n = n + + def set(self, n): + assert n in self.candidates + self.candidates = [n] + self._prune(n) + self.is_set = True + + def _prune(self, n): + for c in self.row: + if n in c.candidates: + c.candidates.remove(n) + for c in self.col: + if n in c.candidates: + c.candidates.remove(n) + for c in self.square: + if n in c.candidates: + c.candidates.remove(n) + diff --git a/grid.py b/grid.py new file mode 100644 index 0000000..e6a5e09 --- /dev/null +++ b/grid.py @@ -0,0 +1,89 @@ +from cell import * + +class grid: + cells = [] + rows = [] + cols = [] + squares = [] + + def __init__(self): + self.cells = [cell(n) for n in range(81)] + + for i in range(9): + self.rows.append([_find_i(i, j) for j in range(9)]) + self.cols.append([_find_i(j, i) for j in range(9)]) + self.squares.append([_find_i((i//3)*3 +j, (i//3)*3 +k) for j in range(3) for k in range(3)]) + + for i, c in enumerate(self.cells): + i_row, i_col = _find_coords(i) + + row = [_find_i(i_row, j) for j in range(9)] + col = [_find_i(j, i_col) for j in range(9)] + square = [_find_i((i_row//3)*3 +j, (i_col//3)*3 +k) for j in range(3) for k in range(3)] + + row.remove(i) + col.remove(i) + square.remove(i) + + c.row = [self.cells[j] for j in row] + c.col = [self.cells[j] for j in col] + c.square = [self.cells[j] for j in square] + + def get_cell(self, i_row, i_col): + return self.cells[_find_i(i_row, i_col)] + + def set_cells(self, *tuples): + for t in tuples: + a, b, n = t + self.cells[_find_i(a, b)].set(n) + + def parse_string(s): + assert len(s) == 81+8 + tuples = [] + lines = s.split('.') + for i, line in enumerate(lines): + assert len(line) == 9 + for j, char in enumerate(line): + if char == " ": + pass + elif char in "123456789": + tuples.append((i, j, int(char))) + else: + print("incorrent string!") + exit(1) + G = grid() + G.set_cells(*tuples) + return G + + def __repr__(self): + row_str = "{}{}{} {}{}{} {}{}{} | {}{}{} {}{}{} {}{}{} | {}{}{} {}{}{} {}{}{}" + format = lambda a, n: row_str.format(*[cell.candidates[i] if i < len(cell.candidates) else " " for cell in self.cells[9*a:9*(a+1)] for i in range(3*n,3*(n+1))]) + return "\n".join([ + format(0, 0), format(0, 1), format(0, 2), + " "*12+"|"+" "*13+"|", + format(1, 0), format(1, 1), format(1, 2), + " "*12+"|"+" "*13+"|", + format(2, 0), format(2, 1), format(2, 2), + "-"*39, + format(3, 0), format(3, 1), format(3, 2), + " "*12+"|"+" "*13+"|", + format(4, 0), format(4, 1), format(4, 2), + " "*12+"|"+" "*13+"|", + format(5, 0), format(5, 1), format(5, 2), + "-"*39, + format(6, 0), format(6, 1), format(6, 2), + " "*12+"|"+" "*13+"|", + format(7, 0), format(7, 1), format(7, 2), + " "*12+"|"+" "*13+"|", + format(8, 0), format(8, 1), format(8, 2), + "" + ]) + +def _find_coords(i): + i_row = i//9 + i_col = i%9 + return i_row, i_col + +def _find_i(i_row, i_col): + return 9*i_row + i_col + diff --git a/solver.py b/solver.py new file mode 100644 index 0000000..7bdd5a7 --- /dev/null +++ b/solver.py @@ -0,0 +1,151 @@ +from grid import * +from copy import deepcopy + +def solve(G): + stack = [G] + while len(stack) > 0: + grid = stack.pop() + ret = simplify(grid) + if ret == 0: + branch(grid, stack) + print(G) + printn("unsolvable!") + +def simplify(G): + sum = 1 + while sum > 0: + sum = 0 + ret = check_rows(G) + if ret < 0: + return -1 + else: + sum += ret + + ret = check_cols(G) + if ret < 0: + return -1 + else: + sum += ret + + ret = check_squares(G) + if ret < 0: + return -1 + else: + sum += ret + + ret = reduce(G) + if ret < 0: + return -1 + else: + sum += ret + return 0 + +def branch(G, stack): + min_cell = None + min_cell_size = 10 + for cell in G.cells: + if cell.is_set == False and len(cell.candidates) < min_cell_size: + min_cell = cell + min_cell_size = len(cell.candidates) + candidates = min_cell.candidates + for candidate in candidates: + copy = deepcopy(G) + copy.cells[min_cell.n].set(candidate) + stack.append(copy) + +def reduce(G): + n_steps = 0 + while True: + val = reduce_step(G) + if val == 0: + print(G) + print("done") + exit(0) + elif val == 1: + return n_steps + n_steps += 1 + +def reduce_step(G): + all_set = True + nochange = True + for cell in G.cells: + l = len(cell.candidates) + if not cell.is_set: + if l == 1: + cell.set(cell.candidates[0]) + nochange = False + else: + all_set = False + if all_set: + return 0 + elif nochange: + return 1 + else: + return 2 + +def check_rows(G): + res = 0 + for row in G.rows: + finds = [0]*9 + to_set = [] + for cell in row: + cell = G.cells[cell] + for candidate in cell.candidates: + finds[candidate-1] += 1 + for i, find in enumerate(finds): + if find == 1: + to_set.append(i+1) + elif find == 0: + return -1 + for cell in row: + cell = G.cells[cell] + for i in to_set: + if i in cell.candidates and not cell.is_set: + cell.set(i) + res += 1 + return res + +def check_cols(G): + res = 0 + for col in G.cols: + finds = [0]*9 + to_set = [] + for cell in col: + cell = G.cells[cell] + for candidate in cell.candidates: + finds[candidate-1] += 1 + for i, find in enumerate(finds): + if find == 1: + to_set.append(i+1) + elif find == 0: + return -1 + for cell in col: + cell = G.cells[cell] + for i in to_set: + if i in cell.candidates and not cell.is_set: + cell.set(i) + res += 1 + return res + +def check_squares(G): + res = 0 + for square in G.squares: + finds = [0]*9 + to_set = [] + for cell in square: + cell = G.cells[cell] + for candidate in cell.candidates: + finds[candidate-1] += 1 + for i, find in enumerate(finds): + if find == 1: + to_set.append(i+1) + elif find == 0: + return -1 + for cell in square: + cell = G.cells[cell] + for i in to_set: + if i in cell.candidates and not cell.is_set: + cell.set(i) + res += 1 + return res + diff --git a/sudokusolver.py b/sudokusolver.py new file mode 100755 index 0000000..8fa20ff --- /dev/null +++ b/sudokusolver.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python3 +from grid import grid +from solver import solve + +medium = [ + (0, 3, 7), (0, 4, 9), (0, 5, 3), + (1, 1, 9), (1, 3, 5), (1, 4, 2), (1, 5, 1), (1, 6, 4), + (2, 0, 7), (2, 8, 9), + (3, 0, 2), (3, 3, 4), (3, 4, 3), (3, 6, 7), + (4, 2, 9), (4, 3, 8), (4, 4, 1), (4, 5, 7), (4, 6, 3), (4, 7, 5), + (5, 0, 3), (5, 4, 5), (5, 8, 8), + (6, 1, 6), (6, 4, 8), (6, 5, 5), (6, 7, 7), + (7, 4, 6), (7, 5, 2), (7, 6, 8), (7, 7, 9), (7, 8, 1), + (8, 1, 8), (8, 3, 9) +] + +hard = " 5 1 6.3 5 8 9 . 7 4 . 2 . 9 31 . 1 9. 8 36 9 5.92 .6 7 8 " +extreme = " 1 7 . 1 485.84 6 3 .5 19 . 35 6. 5 . 593 64 .18 72 3.3 " + +G = grid.parse_string(extreme) +solve(G) +