diff --git a/sudoku.py b/sudoku.py new file mode 100644 index 0000000..c80ecdd --- /dev/null +++ b/sudoku.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# vim:set fileencoding=utf-8: + +""" +A backtracking Sudoku solver. +""" + +from __future__ import division, print_function +import numpy as np +import time + + +puzzle = [3, 7, 0, 0, 0, 4, 9, 5, 1, + 0, 0, 2, 5, 0, 0, 0, 0, 8, + 0, 0, 0, 0, 0, 0, 0, 0, 7, + 0, 0, 0, 0, 0, 3, 0, 2, 0, + 0, 1, 0, 0, 2, 0, 0, 4, 0, + 0, 5, 0, 9, 0, 0, 0, 0, 0, + 8, 0, 0, 0, 0, 0, 0, 0, 0, + 7, 0, 0, 0, 0, 5, 3, 0, 0, + 5, 9, 4, 6, 0, 0, 0, 7, 2] +puzzle = np.array(puzzle).reshape(9, 9) + +visual = True + + +def valid(): + rows = np.vsplit(puzzle, 9) + cols = np.hsplit(puzzle, 9) + grids = [grid for h in np.hsplit(puzzle, 3) for grid in np.vsplit(h, 3)] + + units = rows + cols + grids + return all(np.max(np.bincount(unit[unit != 0])) == 1 for unit in units) + + +def print_puzzle(clear=True): + ANSI_CPL = "\033[%dF" # CPL = Cursor Previous Line + + if clear: + print(ANSI_CPL % 10) + + for row in np.vsplit(puzzle, 9): + for element in row[0]: + if element != 0: + print(element, end=' ') + else: + print('_', end=' ') + print() + + +def set_next_number(): + position = find_first(puzzle == 0) + for number in range(1, 10): + puzzle[position] = number + if valid(): + if visual: + print_puzzle() + time.sleep(0.005) + if 0 not in puzzle: + # Solved + return True + else: + if set_next_number(): + return True + + # dead end ⇒ backtrack + puzzle[position] = 0 + return False + + +def find_first(condition): + return tuple(p[0] for p in np.where(condition)) + + +print(__doc__) +print_puzzle(clear=False) + +if set_next_number(): + print_puzzle()