Change the meaning of size

- size is the size of one side of a square
- row_size is the length of the grid on one side
- grid_size is the area of the grid
This commit is contained in:
Eryn Wells 2017-10-10 12:43:18 -07:00
parent a9a01d3fd4
commit 35647fc8c9
2 changed files with 65 additions and 55 deletions

View file

@ -8,20 +8,37 @@ import itertools
import math import math
class Sudoku: class Sudoku:
def __init__(self, size=9, board=None): def __init__(self, size=3, board=None):
dim = math.sqrt(size) self._size = size
if dim != int(dim): sz4 = size ** 4
raise ValueError('Board size must have an integral square root.')
self._dimension = int(dim)
self.size = size
if board: if board:
self._board = bytearray(board)[:size**2] self._board = bytes(board)[:sz4]
else: else:
self._board = bytearray(b'\x00' * (size * size)) self._board = bytes(sz4)
@property @property
def dimension(self): def size(self):
return self._dimension 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 @property
def rows(self): def rows(self):
@ -40,58 +57,52 @@ class Sudoku:
''' '''
Return an iterable of ranges of indexes into the board, each defining a row. Return an iterable of ranges of indexes into the board, each defining a row.
''' '''
return (self._row(i, self.size) for i in range(self.size)) return (self._row(i) for i in range(self.row_size))
@property @property
def index_columns(self): def index_columns(self):
''' '''
Return an iterable of ranges of indexes into the board, each defining a column. Return an iterable of ranges of indexes into the board, each defining a column.
''' '''
sz = self.size return (self._column(i) for i in range(self.row_size))
sz2 = sz ** 2
return (self._column(i, sz, sz2) for i in range(sz))
@property @property
def index_squares(self): def index_squares(self):
''' '''
Return an iterable of ranges of indexes into the board, each defining a square. Return an iterable of ranges of indexes into the board, each defining a square.
''' '''
dim = self.dimension return (self._square(x, y) for (x,y) in self.all_squares)
return (self._square(x, y, dim) for y in range(dim) for x in range(dim))
def peers(self, x, y): def peers(self, x, y):
return {self._board[i] for i in self.index_peers(x, y) if self._board[i] != 0} return {self._board[i] for i in self.index_peers(x, y) if self._board[i] != 0}
def index_peers(self, x, y): def index_peers(self, x, y):
dim = self.dimension
sz = self.size sz = self.size
sz2 = sz ** 2 sqx, sqy = int(x / sz), int(y / sz)
sq_x, sq_y = int(x / dim), int(y / dim) return set(self._row(y)) | set(self._column(x)) | set(self._square(sqx, sqy))
return set(self._row(y, sz)) | set(self._column(x, sz, sz2)) | set(self._square(sq_x, sq_y, dim))
# TODO: Break the above into helper methods that produce a single thing given an index. def _row(self, r):
def _row(self, r, size): row_size = self.row_size
return range(r * size, r * size + size) return range(r * row_size, r * row_size + row_size)
def _column(self, c, size, size2): def _column(self, c):
return range(c, size2, size) return range(c, self.grid_size, self.row_size)
def _square(self, x, y, dim): def _square(self, x, y):
if (x < 0 or x >= dim) or (y < 0 or y >= dim): size = self.size
raise IndexError('Invalid coordinates for square: ({}, {})'.format(x, y)) row_size = self.row_size
offx, offy = (x * size, y * size * row_size)
offset = (x * dim, y * dim * self.size)
def _range(i): def _range(i):
start = (offset[1] + i * self.size) + offset[0] start = (offy + i * row_size) + offx
return range(start, start + dim) return range(start, start + size)
ranges = itertools.chain(*[_range(i) for i in range(dim)]) ranges = itertools.chain(*[_range(i) for i in range(size)])
return ranges return ranges
@property @property
def solved(self): def solved(self):
expected = set(range(1, self.size + 1)) expected = self.possible_values
all_groups = itertools.chain(self.rows, self.columns, self.squares) all_groups = itertools.chain(self.rows, self.columns, self.squares)
return all(expected == set(g) for g in all_groups) return all(expected == set(g) for g in all_groups)
@ -99,19 +110,19 @@ class Sudoku:
return ((self._board[i] for i in r) for r in ranges) return ((self._board[i] for i in r) for r in ranges)
def __str__(self): def __str__(self):
field_width = len(str(self.size)) field_width = len(str(max(self.possible_values)))
dim = self.dimension sz = self.size
lines = [] lines = []
spacer = '{0}{1}{0}'.format('+', '+'.join(['-' * (field_width * dim) for _ in range(dim)])) spacer = '{0}{1}{0}'.format('+', '+'.join(['-' * (field_width * sz) for _ in range(sz)]))
for line in range(self.size): for line in range(self.size):
chunks = [] chunks = []
for i in range(dim): for i in range(sz):
fields = [] fields = []
for j in range(dim): for j in range(sz):
idx = line * self.size + i * dim + j idx = line * self.size + i * sz + j
fields.append('{{board[{i}]:^{width}}}'.format(i=idx, width=field_width)) fields.append('{{board[{i}]:^{width}}}'.format(i=idx, width=field_width))
chunks.append(''.join(fields)) chunks.append(''.join(fields))
if (line % dim) == 0: if (line % sz) == 0:
lines.append(spacer) lines.append(spacer)
lines.append('{0}{1}{0}'.format('|', '|'.join(chunks))) lines.append('{0}{1}{0}'.format('|', '|'.join(chunks)))
lines.append(spacer) lines.append(spacer)

27
test.py
View file

@ -9,13 +9,12 @@ import sudoku
class Sudoku4TestCase(unittest.TestCase): class Sudoku4TestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.board = sudoku.Sudoku(size=4) self.board = sudoku.Sudoku(size=2)
class Sudoku4BasicTests(Sudoku4TestCase): class Sudoku4BasicTests(Sudoku4TestCase):
def test_that_board_is_sane(self): def test_that_board_is_sane(self):
self.assertEqual(self.board.size, 4) self.assertEqual(self.board.size, 2)
self.assertEqual(len(self.board._board), 4**2) self.assertEqual(len(self.board._board), 2**4)
self.assertEqual(self.board.dimension, 2)
def test_rows(self): def test_rows(self):
expected_rows = [ expected_rows = [
@ -42,16 +41,16 @@ class Sudoku4BasicTests(Sudoku4TestCase):
self.assertEqual(col_list, excol) self.assertEqual(col_list, excol)
def test_squares(self): def test_squares(self):
expected_squares = [ expected_squares = {
[ 0, 1, 4, 5], (0,0): set([ 0, 1, 4, 5]),
[ 2, 3, 6, 7], (1,0): set([ 2, 3, 6, 7]),
[ 8, 9, 12, 13], (0,1): set([ 8, 9, 12, 13]),
[10, 11, 14, 15] (1,1): set([10, 11, 14, 15]),
] }
for (sq, exsq) in zip(self.board.index_squares, expected_squares): for (coord, exsq) in expected_squares.items():
sq_list = list(sq) with self.subTest(sq=coord, ex=exsq):
with self.subTest(sq=sq_list, ex=exsq): sq = set(self.board._square(*coord))
self.assertEqual(sq_list, exsq) self.assertEqual(sq, exsq)
def test_peers(self): def test_peers(self):
expected_peers = { expected_peers = {