#!env python3 # Eryn Wells ''' A Sudoku puzzle solver. ''' import itertools import math class Sudoku: def __init__(self, size=3, board=None): self._size = size sz4 = size ** 4 if board: self._board = bytes(board)[:sz4] else: self._board = bytes(sz4) @property def size(self): return self._size @property def row_size(self): return self.size ** 2 @property def grid_size(self): return self.size ** 4 @property def all_squares(self): return itertools.product(range(self.size), repeat=2) @property def possible_values(self): ''' The set of valid values for any grid square, not accounting for values made invalid by already being present in a peer of that square. ''' return set(range(1, self.row_size + 1)) @property def rows(self): return self._apply_index_ranges(self.index_rows) @property def columns(self): return self._apply_index_ranges(self.index_columns) @property def squares(self): return self._apply_index_ranges(self.index_squares) @property def index_rows(self): ''' Return an iterable of ranges of indexes into the board, each defining a row. ''' return (self._row(i) for i in range(self.row_size)) @property def index_columns(self): ''' Return an iterable of ranges of indexes into the board, each defining a column. ''' return (self._column(i) for i in range(self.row_size)) @property def index_squares(self): ''' Return an iterable of ranges of indexes into the board, each defining a square. ''' return (self._square(x, y) for (x,y) in self.all_squares) def peers(self, x, y): return {self._board[i] for i in self.index_peers(x, y) if self._board[i] != 0} def index_peers(self, x, y): sz = self.size sqx, sqy = int(x / sz), int(y / sz) return set(self._row(y)) | set(self._column(x)) | set(self._square(sqx, sqy)) def _row(self, r): row_size = self.row_size return range(r * row_size, r * row_size + row_size) def _column(self, c): return range(c, self.grid_size, self.row_size) def _square(self, x, y): size = self.size row_size = self.row_size offx, offy = (x * size, y * size * row_size) def _range(i): start = (offy + i * row_size) + offx return range(start, start + size) ranges = itertools.chain(*[_range(i) for i in range(size)]) return ranges @property def solved(self): expected = self.possible_values all_groups = itertools.chain(self.rows, self.columns, self.squares) return all(expected == set(g) for g in all_groups) def _apply_index_ranges(self, ranges): return ((self._board[i] for i in r) for r in ranges) def __str__(self): field_width = len(str(max(self.possible_values))) sz = self.size lines = [] spacer = '{0}{1}{0}'.format('+', '+'.join(['-' * (field_width * sz) for _ in range(sz)])) for line in range(self.size): chunks = [] for i in range(sz): fields = [] for j in range(sz): idx = line * self.size + i * sz + j fields.append('{{board[{i}]:^{width}}}'.format(i=idx, width=field_width)) chunks.append(''.join(fields)) if (line % sz) == 0: lines.append(spacer) lines.append('{0}{1}{0}'.format('|', '|'.join(chunks))) lines.append(spacer) fmt = '\n'.join(lines) str_board = [str(n) if n != 0 else ' ' for n in self._board] out = fmt.format(board=str_board) return out