Compare commits

...
Sign in to create a new pull request.

22 commits
master ... old

Author SHA1 Message Date
e004d319e3 IDK what this WIP is 2017-10-06 20:27:01 -07:00
6cc3fed4b3 Implement search
- Remove __setitem__; it was getting in the way...
- deepcopy the board for each trial in search()
2013-08-30 17:09:41 -07:00
cc5e35ff81 Working on search() 2013-08-30 10:11:44 -07:00
81e3296f6f Create keys then assign values in __init__ 2013-08-29 22:46:38 -07:00
fdee9ed716 Couple tweaks to help with parsing Euler and Norvig puzzles
- Make xy_key and xy_kwargs_key classmethods
2013-08-29 22:32:15 -07:00
09c6c4287f Convert Euler puzzles into Norvig format 2013-08-29 22:32:05 -07:00
6a5e13e345 Add quick routines for parsing the puzzles in puzzles/ 2013-08-29 22:31:49 -07:00
3d69b67185 Fix printing -- had the x and y coordinates flipped 2013-08-29 22:05:16 -07:00
05976b51fb TODO and use self.possible_values instead of explicit range 2013-08-29 21:44:19 -07:00
Eryn Wells
9f4a775472 Add puzzles
Both via http://norvig.com/sudoku.html
- euler.txt: http://projecteuler.net/project/sudoku.txt
- norvig.txt: http://magictour.free.fr/top95
2013-08-29 16:32:24 -07:00
Eryn Wells
5a2df50e02 Add string test 2013-08-29 16:14:35 -07:00
Eryn Wells
7e43451eb5 Tests yay! 2013-08-29 15:58:43 -07:00
Eryn Wells
3c21549ee9 Bare bones tests module 2013-08-29 15:05:38 -07:00
Eryn Wells
a926bc5a99 Board.solve() and Board.assign() 2013-08-29 15:05:30 -07:00
Eryn Wells
d20a0bbdf7 Fix the divider printing logic (again) 2013-08-29 12:05:34 -07:00
cac812e46f Fix divider length computation 2013-08-29 11:57:31 -07:00
8540f60381 Use length of str(self.size) to compute size of each square in the printed grid 2013-08-29 11:49:34 -07:00
6a753a8dac Docstring updates 2013-08-29 09:52:52 -07:00
7fd051802c Implement the solved property 2013-08-29 09:42:06 -07:00
31415f8175 Ignore __pycache__/ 2013-08-28 23:10:27 -07:00
af92bb89f8 Ignore *.pyc 2013-08-28 23:10:15 -07:00
c120ddc4fa Add sudoku module -- work thus far 2013-08-28 23:09:55 -07:00
6 changed files with 571 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.pyc
__pycache__/

58
puzzles.py Normal file
View file

@ -0,0 +1,58 @@
#!/usr/bin/env python3
# Eryn Wells <eryn@erynwells.me>
'''
Parser for puzzles in the ./puzzles directory.
'''
import sudoku
euler = []
norvig = []
def parse_euler(filename='./puzzles/euler.txt'):
with open(filename, 'r') as f:
puzzle_lines = f.readlines()
for puzzle in puzzle_lines:
# Chop the newline
if puzzle[-1] == '\n':
puzzle = puzzle[:-1]
print('Parsing puzzle: {}'.format(puzzle))
if len(puzzle) != 81:
continue
kwargs = {}
for idx in range(len(puzzle)):
sq = puzzle[idx]
if sq not in '1234567890.':
continue
sq_int = 0 if sq == '.' else int(sq)
x, y = int(idx % 9), int(idx / 9)
if sq_int != 0:
kwargs[sudoku.Board.xy_kwargs_key(x, y)] = sq_int
euler.append(sudoku.Board(**kwargs))
def parse_norvig(filename='./puzzles/norvig.txt'):
with open(filename, 'r') as f:
puzzle_lines = f.readlines()
for puzzle in puzzle_lines:
# Chop the newline
if puzzle[-1] == '\n':
puzzle = puzzle[:-1]
print('Parsing puzzle: {}'.format(puzzle))
if len(puzzle) != 81:
continue
kwargs = {}
for idx in range(len(puzzle)):
sq = puzzle[idx]
if sq not in '1234567890.':
continue
sq_int = 0 if sq == '.' else int(sq)
x, y = int(idx % 9), int(idx / 9)
if sq_int != 0:
kwargs[sudoku.Board.xy_kwargs_key(x, y)] = sq_int
norvig.append(sudoku.Board(**kwargs))

50
puzzles/euler.txt Normal file
View file

@ -0,0 +1,50 @@
003020600900305001001806400008102900700000008006708200002609500800203009005010300
200080300060070084030500209000105408000000000402706000301007040720040060004010003
000000907000420180000705026100904000050000040000507009920108000034059000507000000
030050040008010500460000012070502080000603000040109030250000098001020600080060020
020810740700003100090002805009040087400208003160030200302700060005600008076051090
100920000524010000000000070050008102000000000402700090060000000000030945000071006
043080250600000000000001094900004070000608000010200003820500000000000005034090710
480006902002008001900370060840010200003704100001060049020085007700900600609200018
000900002050123400030000160908000000070000090000000205091000050007439020400007000
001900003900700160030005007050000009004302600200000070600100030042007006500006800
000125400008400000420800000030000095060902010510000060000003049000007200001298000
062340750100005600570000040000094800400000006005830000030000091006400007059083260
300000000005009000200504000020000700160000058704310600000890100000067080000005437
630000000000500008005674000000020000003401020000000345000007004080300902947100080
000020040008035000000070602031046970200000000000501203049000730000000010800004000
361025900080960010400000057008000471000603000259000800740000005020018060005470329
050807020600010090702540006070020301504000908103080070900076205060090003080103040
080005000000003457000070809060400903007010500408007020901020000842300000000100080
003502900000040000106000305900251008070408030800763001308000104000020000005104800
000000000009805100051907420290401065000000000140508093026709580005103600000000000
020030090000907000900208005004806500607000208003102900800605007000309000030020050
005000006070009020000500107804150000000803000000092805907006000030400010200000600
040000050001943600009000300600050002103000506800020007005000200002436700030000040
004000000000030002390700080400009001209801307600200008010008053900040000000000800
360020089000361000000000000803000602400603007607000108000000000000418000970030014
500400060009000800640020000000001008208000501700500000000090084003000600060003002
007256400400000005010030060000508000008060200000107000030070090200000004006312700
000000000079050180800000007007306800450708096003502700700000005016030420000000000
030000080009000500007509200700105008020090030900402001004207100002000800070000090
200170603050000100000006079000040700000801000009050000310400000005000060906037002
000000080800701040040020030374000900000030000005000321010060050050802006080000000
000000085000210009960080100500800016000000000890006007009070052300054000480000000
608070502050608070002000300500090006040302050800050003005000200010704090409060701
050010040107000602000905000208030501040070020901080406000401000304000709020060010
053000790009753400100000002090080010000907000080030070500000003007641200061000940
006080300049070250000405000600317004007000800100826009000702000075040190003090600
005080700700204005320000084060105040008000500070803010450000091600508007003010600
000900800128006400070800060800430007500000009600079008090004010003600284001007000
000080000270000054095000810009806400020403060006905100017000620460000038000090000
000602000400050001085010620038206710000000000019407350026040530900020007000809000
000900002050123400030000160908000000070000090000000205091000050007439020400007000
380000000000400785009020300060090000800302009000040070001070500495006000000000092
000158000002060800030000040027030510000000000046080790050000080004070100000325000
010500200900001000002008030500030007008000500600080004040100700000700006003004050
080000040000469000400000007005904600070608030008502100900000005000781000060000010
904200007010000000000706500000800090020904060040002000001607000000000030300005702
000700800006000031040002000024070000010030080000060290000800070860000500002006000
001007090590080001030000080000005800050060020004100000080000030100020079020700400
000003017015009008060000000100007000009000200000500004000000020500600340340200000
300200000000107000706030500070009080900020004010800050009040301000702000000008006

96
puzzles/norvig.txt Normal file
View file

@ -0,0 +1,96 @@
4.....8.5.3..........7......2.....6.....8.4......1.......6.3.7.5..2.....1.4......
52...6.........7.13...........4..8..6......5...........418.........3..2...87.....
6.....8.3.4.7.................5.4.7.3..2.....1.6.......2.....5.....8.6......1....
48.3............71.2.......7.5....6....2..8.............1.76...3.....4......5....
....14....3....2...7..........9...3.6.1.............8.2.....1.4....5.6.....7.8...
......52..8.4......3...9...5.1...6..2..7........3.....6...1..........7.4.......3.
6.2.5.........3.4..........43...8....1....2........7..5..27...........81...6.....
.524.........7.1..............8.2...3.....6...9.5.....1.6.3...........897........
6.2.5.........4.3..........43...8....1....2........7..5..27...........81...6.....
.923.........8.1...........1.7.4...........658.........6.5.2...4.....7.....9.....
6..3.2....5.....1..........7.26............543.........8.15........4.2........7..
.6.5.1.9.1...9..539....7....4.8...7.......5.8.817.5.3.....5.2............76..8...
..5...987.4..5...1..7......2...48....9.1.....6..2.....3..6..2.......9.7.......5..
3.6.7...........518.........1.4.5...7.....6.....2......2.....4.....8.3.....5.....
1.....3.8.7.4..............2.3.1...........958.........5.6...7.....8.2...4.......
6..3.2....4.....1..........7.26............543.........8.15........4.2........7..
....3..9....2....1.5.9..............1.2.8.4.6.8.5...2..75......4.1..6..3.....4.6.
45.....3....8.1....9...........5..9.2..7.....8.........1..4..........7.2...6..8..
.237....68...6.59.9.....7......4.97.3.7.96..2.........5..47.........2....8.......
..84...3....3.....9....157479...8........7..514.....2...9.6...2.5....4......9..56
.98.1....2......6.............3.2.5..84.........6.........4.8.93..5...........1..
..247..58..............1.4.....2...9528.9.4....9...1.........3.3....75..685..2...
4.....8.5.3..........7......2.....6.....5.4......1.......6.3.7.5..2.....1.9......
.2.3......63.....58.......15....9.3....7........1....8.879..26......6.7...6..7..4
1.....7.9.4...72..8.........7..1..6.3.......5.6..4..2.........8..53...7.7.2....46
4.....3.....8.2......7........1...8734.......6........5...6........1.4...82......
.......71.2.8........4.3...7...6..5....2..3..9........6...7.....8....4......5....
6..3.2....4.....8..........7.26............543.........8.15........8.2........7..
.47.8...1............6..7..6....357......5....1..6....28..4.....9.1...4.....2.69.
......8.17..2........5.6......7...5..1....3...8.......5......2..4..8....6...3....
38.6.......9.......2..3.51......5....3..1..6....4......17.5..8.......9.......7.32
...5...........5.697.....2...48.2...25.1...3..8..3.........4.7..13.5..9..2...31..
.2.......3.5.62..9.68...3...5..........64.8.2..47..9....3.....1.....6...17.43....
.8..4....3......1........2...5...4.69..1..8..2...........3.9....6....5.....2.....
..8.9.1...6.5...2......6....3.1.7.5.........9..4...3...5....2...7...3.8.2..7....4
4.....5.8.3..........7......2.....6.....5.8......1.......6.3.7.5..2.....1.8......
1.....3.8.6.4..............2.3.1...........958.........5.6...7.....8.2...4.......
1....6.8..64..........4...7....9.6...7.4..5..5...7.1...5....32.3....8...4........
249.6...3.3....2..8.......5.....6......2......1..4.82..9.5..7....4.....1.7...3...
...8....9.873...4.6..7.......85..97...........43..75.......3....3...145.4....2..1
...5.1....9....8...6.......4.1..........7..9........3.8.....1.5...2..4.....36....
......8.16..2........7.5......6...2..1....3...8.......2......7..3..8....5...4....
.476...5.8.3.....2.....9......8.5..6...1.....6.24......78...51...6....4..9...4..7
.....7.95.....1...86..2.....2..73..85......6...3..49..3.5...41724................
.4.5.....8...9..3..76.2.....146..........9..7.....36....1..4.5..6......3..71..2..
.834.........7..5...........4.1.8..........27...3.....2.6.5....5.....8........1..
..9.....3.....9...7.....5.6..65..4.....3......28......3..75.6..6...........12.3.8
.26.39......6....19.....7.......4..9.5....2....85.....3..2..9..4....762.........4
2.3.8....8..7...........1...6.5.7...4......3....1............82.5....6...1.......
6..3.2....1.....5..........7.26............843.........8.15........8.2........7..
1.....9...64..1.7..7..4.......3.....3.89..5....7....2.....6.7.9.....4.1....129.3.
.........9......84.623...5....6...453...1...6...9...7....1.....4.5..2....3.8....9
.2....5938..5..46.94..6...8..2.3.....6..8.73.7..2.........4.38..7....6..........5
9.4..5...25.6..1..31......8.7...9...4..26......147....7.......2...3..8.6.4.....9.
...52.....9...3..4......7...1.....4..8..453..6...1...87.2........8....32.4..8..1.
53..2.9...24.3..5...9..........1.827...7.........981.............64....91.2.5.43.
1....786...7..8.1.8..2....9........24...1......9..5...6.8..........5.9.......93.4
....5...11......7..6.....8......4.....9.1.3.....596.2..8..62..7..7......3.5.7.2..
.47.2....8....1....3....9.2.....5...6..81..5.....4.....7....3.4...9...1.4..27.8..
......94.....9...53....5.7..8.4..1..463...........7.8.8..7.....7......28.5.26....
.2......6....41.....78....1......7....37.....6..412....1..74..5..8.5..7......39..
1.....3.8.6.4..............2.3.1...........758.........7.5...6.....8.2...4.......
2....1.9..1..3.7..9..8...2.......85..6.4.........7...3.2.3...6....5.....1.9...2.5
..7..8.....6.2.3...3......9.1..5..6.....1.....7.9....2........4.83..4...26....51.
...36....85.......9.4..8........68.........17..9..45...1.5...6.4....9..2.....3...
34.6.......7.......2..8.57......5....7..1..2....4......36.2..1.......9.......7.82
......4.18..2........6.7......8...6..4....3...1.......6......2..5..1....7...3....
.4..5..67...1...4....2.....1..8..3........2...6...........4..5.3.....8..2........
.......4...2..4..1.7..5..9...3..7....4..6....6..1..8...2....1..85.9...6.....8...3
8..7....4.5....6............3.97...8....43..5....2.9....6......2...6...7.71..83.2
.8...4.5....7..3............1..85...6.....2......4....3.26............417........
....7..8...6...5...2...3.61.1...7..2..8..534.2..9.......2......58...6.3.4...1....
......8.16..2........7.5......6...2..1....3...8.......2......7..4..8....5...3....
.2..........6....3.74.8.........3..2.8..4..1.6..5.........1.78.5....9..........4.
.52..68.......7.2.......6....48..9..2..41......1.....8..61..38.....9...63..6..1.9
....1.78.5....9..........4..2..........6....3.74.8.........3..2.8..4..1.6..5.....
1.......3.6.3..7...7...5..121.7...9...7........8.1..2....8.64....9.2..6....4.....
4...7.1....19.46.5.....1......7....2..2.3....847..6....14...8.6.2....3..6...9....
......8.17..2........5.6......7...5..1....3...8.......5......2..3..8....6...4....
963......1....8......2.5....4.8......1....7......3..257......3...9.2.4.7......9..
15.3......7..4.2....4.72.....8.........9..1.8.1..8.79......38...........6....7423
..........5724...98....947...9..3...5..9..12...3.1.9...6....25....56.....7......6
....75....1..2.....4...3...5.....3.2...8...1.......6.....1..48.2........7........
6.....7.3.4.8.................5.4.8.7..2.....1.3.......2.....5.....7.9......1....
....6...4..6.3....1..4..5.77.....8.5...8.....6.8....9...2.9....4....32....97..1..
.32.....58..3.....9.428...1...4...39...6...5.....1.....2...67.8.....4....95....6.
...5.3.......6.7..5.8....1636..2.......4.1.......3...567....2.8..4.7.......2..5..
.5.3.7.4.1.........3.......5.8.3.61....8..5.9.6..1........4...6...6927....2...9..
..5..8..18......9.......78....4.....64....9......53..2.6.........138..5....9.714.
..........72.6.1....51...82.8...13..4.........37.9..1.....238..5.4..9.........79.
...658.....4......12............96.7...3..5....2.8...3..19..8..3.6.....4....473..
.2.3.......6..8.9.83.5........2...8.7.9..5........6..4.......1...1...4.22..7..8.9
.5..9....1.....6.....3.8.....8.4...9514.......3....2..........4.8...6..77..15..6.
.....2.......7...17..3...9.8..7......2.89.6...13..6....9..5.824.....891..........
3...8.......7....51..............36...2..4....7...........6.13..452...........8..


237
sudoku.py Normal file
View file

@ -0,0 +1,237 @@
#!/usr/bin/env python3
# Eryn Wells <eryn@erynwells.me>
'''
A Sudoku solver.
'''
import copy
import logging
import math
LOG = logging.getLogger(__name__)
class Board(dict):
def __init__(self, size=9, **kwargs):
self.size = size
# Verify size is a perfect square. The sqrt(size) is also the dimension of each box on the
# Sudoku grid.
self.box_size = int(math.sqrt(size))
assert self.box_size == int(self.box_size), 'Invalid size; value must be a perfect square'
# The range of possible values for a square.
self.possible_values = range(1, self.size + 1)
# Make the grid.
super(Board, self).__init__([(Board.xy_key(x, y), list(self.possible_values))
for x in range(self.size)
for y in range(self.size)])
for square in self:
initial_value = kwargs.get(Board.xy_kwargs_key(*square))
if initial_value is None:
continue
if initial_value not in self.possible_values:
raise ValueError('Invalid initial value for square ({}, {}): {}'.format(x, y, initial_value))
LOG.debug('Setting initial value of {} to {}'.format(square, initial_value))
self.assign(square, [initial_value])
@classmethod
def xy_key(self, x, y):
'''Given {x} and {y}, generate a key to refer to the square at coordinate (x, y) in the grid.'''
return (int(x), int(y))
@classmethod
def xy_kwargs_key(self, x, y):
'''
Given {x} and {y}, generate a key to refer to the square in the __init__ kwargs at coordinate (x, y) in the
grid.
'''
return 'x{}y{}'.format(x, y)
@property
def solved(self):
'''
Determines if the board has been solved. First, determine if all squares have no more than one value (i.e. they
have had values assigned to them). If not, return False. If so, check each unit (rows, columns, and boxes) to
make sure that each has one and only one of each value in the range of possible values for a unit. If not,
return False; otherwise, return True.
'''
if not all(len(s) == 1 for s in self.values()):
return False
def validate_unit(unit):
'''
Validate {unit} by ensuring it has exactly 1 of each value in the range of possible values.
'''
necessary_values = list(self.possible_values)
for square, values in unit.items():
v = values[0]
if v in necessary_values:
necessary_values.remove(v)
else:
return False
if len(necessary_values) != 0:
return False
return True
return ( all(validate_unit(self.row(r)) for r in range(self.size))
and all(validate_unit(self.col(c)) for c in range(self.size))
and all(validate_unit(self.box(x, y))
for x in range(0, self.size, self.box_size)
for y in range(0, self.size, self.box_size)))
def row(self, idx):
'''
Return a dict of all squares in the {idx}'th row.
'''
assert idx >= 0 and idx < self.size, 'Invalid row index; value must be >= 0 and < {}'.format(self.size)
# key[1] is Y coordinate in the grid.
return {k: v for k, v in self.items() if k[1] == idx}
def col(self, idx):
'''
Return a dict of all squares in the {idx}'th column.
'''
assert idx >= 0 and idx < self.size, 'Invalid column index; value must be >= 0 and < {}'.format(self.size)
# key[0] is X coordinate in the grid.
return {k: v for k, v in self.items() if k[0] == idx}
def box(self, x, y):
'''
Given a square at coordinates ({x}, {y}), return a dictionary containing the squares that make up the box that
contains the point.
'''
bx = int(x / self.box_size) * self.box_size
by = int(y / self.box_size) * self.box_size
bx_range = range(bx, bx + self.box_size)
by_range = range(by, by + self.box_size)
return {k: v for k, v in self.items() if k[0] in bx_range and k[1] in by_range}
def peers(self, x, y):
'''
Generate and return a list of peers for the square at coordinates ({x}, {y}). Peers are defined as all the
squares in the same row, column, and box as the given square.
'''
# Generate a dictionary of all the squares in the row, column, and box containing the given square.
peers = dict(list(self.row(y).items()) + list(self.col(x).items()) + list(self.box(x, y).items()))
# Remove the given square.
del peers[Board.xy_key(x, y)]
return peers
def clear(self, squares=None):
'''
Clear the board. Resets each square's possible value list to the full range of possible values for this board.
'''
for square in (self if squares is None else squares):
self.assign(square, list(self.possible_values))
def solve(self):
return self.search()
def search(self):
'''
Search for a solution, depth-first. Return the solved Board as a new instance of this class.
'''
if self.solved:
return self
# Chose the square with the fewest possible values.
_, smallest = min((len(self[sq]), sq) for sq in self if len(self[sq]) > 1)
# Deepcopy the board.
trials = []
for v in self[smallest]:
trial_board = copy.deepcopy(self)
if trial_board.assign(smallest, [v]):
trials.append(trial_board)
trial_board.search()
else:
trials.append(None)
for t in trials:
if t is not None:
return t
return False
def assign(self, square, value):
'''
Assign {value} to {square}. {value} is expected to be an iterable (a list). {key} should be a valid (x, y)
coordinate pair for referencing a square on the board.
'''
LOG.debug('Assigning values {} to square {}.'.format(value, square))
removed_values = set(self[square]) - set(value)
for v in removed_values:
if not self.eliminate(square, v):
return False
return True
def eliminate(self, square, value):
'''
Eliminate {value} from {square}, propagating changes to the squares peers as necessary. This process involves
two logical rules of Sudoku. First, if a square is reduced to a single possible value, assign that value to the
square and remove the value from the possible value lists of its peers. Second, if a unit has only one square
that can hold a value, put the value in that square and propagate that change to its peers.
'''
if value not in self[square]:
LOG.debug('Value {} not in square {}; skipping'.format(value, square))
return True
# Update peer value list.
super(Board, self).__setitem__(square, [v for v in self[square] if v != value])
LOG.debug('Eliminating {} from square {}'.format(value, square))
# (1) If a square is reduced to one value, eliminate that value from its peers.
if len(self[square]) == 0:
# Whoops. Removed the last value... We have a contradiction now.
LOG.error('Removed last value from square {}'.format(square))
return False
elif len(self[square]) == 1:
# One value left in this square. Propagate changes to its peers.
LOG.debug('One value left in square {}; eliminating {} from its peers'.format(square, self[square][0]))
for peer in self.peers(*square):
if not self.eliminate(peer, self[square][0]):
return False
# (2) If a unit has only one square for a value, put it there.
for unit in (self.row(square[1]), self.col(square[0]), self.box(*square)):
places = [sq for sq in unit if value in unit[sq]]
if len(places) == 0:
LOG.error('No place for value {} to go in unit {}'.format(value, unit))
return False
elif len(places) == 1:
LOG.debug('One place for value {} to be in unit {}; setting'.format(value, unit))
self.assign(places[0], [value])
return True
def __delitem__(self, key):
# Don't allow deleting keys from self.
pass
def __str__(self):
lines = []
box_lines = []
square_size = len(str(self.size))
for y in range(self.size):
row_squares = []
box_squares = []
for x in range(self.size):
square = self.get(Board.xy_key(x, y))
if len(square) == 1:
box_squares.append(str(square[0]).center(square_size))
else:
box_squares.append('.'.center(square_size))
if len(box_squares) == self.box_size:
row_squares.append(' '.join(box_squares))
box_squares = []
# Print a divider between boxes.
box_lines.append(' | '.join(row_squares))
if len(box_lines) == self.box_size:
lines.append('\n'.join(box_lines))
box_lines = []
if y < self.size - 1:
# Don't print a divider on the last line.
box_dividers = ['-' * ((square_size + 1) * self.box_size - 1) for box in range(self.box_size)]
lines.append('\n{}\n'.format('-+-'.join(box_dividers)))
return ''.join(lines)

128
tests.py Normal file
View file

@ -0,0 +1,128 @@
#!/usr/bin/env python3
# Eryn Wells <eryn@erynwells.me>
'''
Tests for sudoku.
'''
import nose
import sudoku
import unittest
def test_9x9_dimensions():
'''
Test dimenions of a 9x9 Sudoku board.
'''
b = sudoku.Board()
assert len(b.keys()) == 81
assert b.size == 9
assert b.box_size == 3
for square, values in b.items():
assert len(values) == 9
def test_9x9_units():
'''
Test units of a 9x9 Sudoku board.
'''
b = sudoku.Board()
for r in range(b.size):
row = b.row(r)
assert len(row) == 9
for sq in row:
assert sq[1] == r
for c in range(b.size):
col = b.col(c)
assert len(col) == 9
for sq in col:
assert sq[0] == c
for x in range(0, b.size, b.box_size):
for y in range(0, b.size, b.box_size):
box = b.box(x, y)
assert len(box) == 9
# TODO: Finish this test
def test_9x9_row():
'''
A few tests on rows of a 9x9 Sudoku board.
'''
b = sudoku.Board()
row = b.row(1)
expected_keys = ((x, 1) for x in range(b.size))
for ekey in expected_keys:
assert ekey in row
# No negative numbers
assert (-1, 1) not in row
# Only squares with the right y-coordinate
assert (0, 0) not in row
# No keys above the size of the board
assert (b.size, 1) not in row
def test_9x9_col():
'''
A few tests on rows of a 9x9 Sudoku board.
'''
b = sudoku.Board()
col = b.col(3)
expected_keys = ((3, y) for y in range(b.size))
for ekey in expected_keys:
assert ekey in col
# No negative numbers
assert (3, -1) not in col
# Only squares with the right x-coordinate
assert (0, 0) not in col
# No keys above the size of the board
assert (3, b.size) not in col
def test_9x9_box():
'''
A few tests on boxes of a 9x9 Sudoku board.
'''
b = sudoku.Board()
assert True
def test_9x9_peers():
'''
Test peers.
'''
b = sudoku.Board()
peers = b.peers(3, 3)
expected_peers = set( [(x, 3) for x in range(b.size)]
+ [(3, y) for y in range(b.size)]
+ [(3, 3), (3, 4), (3, 5), (4, 3), (4, 4), (4, 5), (5, 3), (5, 4), (5, 5)])
expected_peers.remove((3, 3))
for epeer in expected_peers:
assert epeer in peers, '{} not in peers of (3, 3)'.format(epeer)
def test_9x9_str():
'''
Test string generation/printing.
'''
b = sudoku.Board()
expected_str = '\n'.join(['. . . | . . . | . . .',
'. . . | . . . | . . .',
'. . . | . . . | . . .',
'------+-------+------',
'. . . | . . . | . . .',
'. . . | . . . | . . .',
'. . . | . . . | . . .',
'------+-------+------',
'. . . | . . . | . . .',
'. . . | . . . | . . .',
'. . . | . . . | . . .'])
assert str(b) == expected_str
def main():
nose.main()
if __name__ == '__main__':
main()