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:
parent
a9a01d3fd4
commit
35647fc8c9
2 changed files with 65 additions and 55 deletions
93
sudoku.py
93
sudoku.py
|
@ -8,20 +8,37 @@ import itertools
|
|||
import math
|
||||
|
||||
class Sudoku:
|
||||
def __init__(self, size=9, board=None):
|
||||
dim = math.sqrt(size)
|
||||
if dim != int(dim):
|
||||
raise ValueError('Board size must have an integral square root.')
|
||||
self._dimension = int(dim)
|
||||
self.size = size
|
||||
def __init__(self, size=3, board=None):
|
||||
self._size = size
|
||||
sz4 = size ** 4
|
||||
if board:
|
||||
self._board = bytearray(board)[:size**2]
|
||||
self._board = bytes(board)[:sz4]
|
||||
else:
|
||||
self._board = bytearray(b'\x00' * (size * size))
|
||||
self._board = bytes(sz4)
|
||||
|
||||
@property
|
||||
def dimension(self):
|
||||
return self._dimension
|
||||
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):
|
||||
|
@ -40,58 +57,52 @@ class Sudoku:
|
|||
'''
|
||||
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
|
||||
def index_columns(self):
|
||||
'''
|
||||
Return an iterable of ranges of indexes into the board, each defining a column.
|
||||
'''
|
||||
sz = self.size
|
||||
sz2 = sz ** 2
|
||||
return (self._column(i, sz, sz2) for i in range(sz))
|
||||
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.
|
||||
'''
|
||||
dim = self.dimension
|
||||
return (self._square(x, y, dim) for y in range(dim) for x in range(dim))
|
||||
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):
|
||||
dim = self.dimension
|
||||
sz = self.size
|
||||
sz2 = sz ** 2
|
||||
sq_x, sq_y = int(x / dim), int(y / dim)
|
||||
return set(self._row(y, sz)) | set(self._column(x, sz, sz2)) | set(self._square(sq_x, sq_y, dim))
|
||||
sqx, sqy = int(x / sz), int(y / sz)
|
||||
return set(self._row(y)) | set(self._column(x)) | set(self._square(sqx, sqy))
|
||||
|
||||
# TODO: Break the above into helper methods that produce a single thing given an index.
|
||||
def _row(self, r, size):
|
||||
return range(r * size, r * size + size)
|
||||
def _row(self, r):
|
||||
row_size = self.row_size
|
||||
return range(r * row_size, r * row_size + row_size)
|
||||
|
||||
def _column(self, c, size, size2):
|
||||
return range(c, size2, size)
|
||||
def _column(self, c):
|
||||
return range(c, self.grid_size, self.row_size)
|
||||
|
||||
def _square(self, x, y, dim):
|
||||
if (x < 0 or x >= dim) or (y < 0 or y >= dim):
|
||||
raise IndexError('Invalid coordinates for square: ({}, {})'.format(x, y))
|
||||
|
||||
offset = (x * dim, y * dim * self.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 = (offset[1] + i * self.size) + offset[0]
|
||||
return range(start, start + dim)
|
||||
start = (offy + i * row_size) + offx
|
||||
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
|
||||
|
||||
@property
|
||||
def solved(self):
|
||||
expected = set(range(1, self.size + 1))
|
||||
expected = self.possible_values
|
||||
all_groups = itertools.chain(self.rows, self.columns, self.squares)
|
||||
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)
|
||||
|
||||
def __str__(self):
|
||||
field_width = len(str(self.size))
|
||||
dim = self.dimension
|
||||
field_width = len(str(max(self.possible_values)))
|
||||
sz = self.size
|
||||
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):
|
||||
chunks = []
|
||||
for i in range(dim):
|
||||
for i in range(sz):
|
||||
fields = []
|
||||
for j in range(dim):
|
||||
idx = line * self.size + i * dim + j
|
||||
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 % dim) == 0:
|
||||
if (line % sz) == 0:
|
||||
lines.append(spacer)
|
||||
lines.append('{0}{1}{0}'.format('|', '|'.join(chunks)))
|
||||
lines.append(spacer)
|
||||
|
|
27
test.py
27
test.py
|
@ -9,13 +9,12 @@ import sudoku
|
|||
|
||||
class Sudoku4TestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.board = sudoku.Sudoku(size=4)
|
||||
self.board = sudoku.Sudoku(size=2)
|
||||
|
||||
class Sudoku4BasicTests(Sudoku4TestCase):
|
||||
def test_that_board_is_sane(self):
|
||||
self.assertEqual(self.board.size, 4)
|
||||
self.assertEqual(len(self.board._board), 4**2)
|
||||
self.assertEqual(self.board.dimension, 2)
|
||||
self.assertEqual(self.board.size, 2)
|
||||
self.assertEqual(len(self.board._board), 2**4)
|
||||
|
||||
def test_rows(self):
|
||||
expected_rows = [
|
||||
|
@ -42,16 +41,16 @@ class Sudoku4BasicTests(Sudoku4TestCase):
|
|||
self.assertEqual(col_list, excol)
|
||||
|
||||
def test_squares(self):
|
||||
expected_squares = [
|
||||
[ 0, 1, 4, 5],
|
||||
[ 2, 3, 6, 7],
|
||||
[ 8, 9, 12, 13],
|
||||
[10, 11, 14, 15]
|
||||
]
|
||||
for (sq, exsq) in zip(self.board.index_squares, expected_squares):
|
||||
sq_list = list(sq)
|
||||
with self.subTest(sq=sq_list, ex=exsq):
|
||||
self.assertEqual(sq_list, exsq)
|
||||
expected_squares = {
|
||||
(0,0): set([ 0, 1, 4, 5]),
|
||||
(1,0): set([ 2, 3, 6, 7]),
|
||||
(0,1): set([ 8, 9, 12, 13]),
|
||||
(1,1): set([10, 11, 14, 15]),
|
||||
}
|
||||
for (coord, exsq) in expected_squares.items():
|
||||
with self.subTest(sq=coord, ex=exsq):
|
||||
sq = set(self.board._square(*coord))
|
||||
self.assertEqual(sq, exsq)
|
||||
|
||||
def test_peers(self):
|
||||
expected_peers = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue