Compare commits
22 commits
Author | SHA1 | Date | |
---|---|---|---|
e004d319e3 | |||
6cc3fed4b3 | |||
cc5e35ff81 | |||
81e3296f6f | |||
fdee9ed716 | |||
09c6c4287f | |||
6a5e13e345 | |||
3d69b67185 | |||
05976b51fb | |||
![]() |
9f4a775472 | ||
![]() |
5a2df50e02 | ||
![]() |
7e43451eb5 | ||
![]() |
3c21549ee9 | ||
![]() |
a926bc5a99 | ||
![]() |
d20a0bbdf7 | ||
cac812e46f | |||
8540f60381 | |||
6a753a8dac | |||
7fd051802c | |||
31415f8175 | |||
af92bb89f8 | |||
c120ddc4fa |
6 changed files with 571 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.pyc
|
||||
__pycache__/
|
58
puzzles.py
Normal file
58
puzzles.py
Normal 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
50
puzzles/euler.txt
Normal 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
96
puzzles/norvig.txt
Normal 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
237
sudoku.py
Normal 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
128
tests.py
Normal 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()
|
Loading…
Add table
Add a link
Reference in a new issue