Compare commits
53 commits
Author | SHA1 | Date | |
---|---|---|---|
2529533661 | |||
3689ac6974 | |||
c9a86d0fdc | |||
137e4e6436 | |||
fba52c7f09 | |||
1186305712 | |||
a7dda24015 | |||
3b380b17d2 | |||
a9a331df91 | |||
387ccacb8d | |||
d25a320c30 | |||
65f4453e78 | |||
189589c0e6 | |||
9ebabd4b56 | |||
7a0e31858f | |||
181bc7d61e | |||
f7b6fb053f | |||
1549269661 | |||
e329208db9 | |||
77456d5114 | |||
301a56904c | |||
0976ec8bb5 | |||
7809c38104 | |||
ed41930092 | |||
c0021ac2c2 | |||
f12263b197 | |||
b8eda77093 | |||
9534f04a08 | |||
6da7c2ecd6 | |||
a34987e3db | |||
e2eadf9da7 | |||
ab927ee41d | |||
a0594d1561 | |||
fb1066f716 | |||
e5d00debc7 | |||
35647fc8c9 | |||
a9a01d3fd4 | |||
cf43b02aa9 | |||
00225aca44 | |||
19a9bc9d37 | |||
93a4fecb26 | |||
3017b1e923 | |||
88f0e07c06 | |||
5a32743d7b | |||
eae05487b9 | |||
5696c087d8 | |||
b5ac48bc10 | |||
3fabd4b420 | |||
5ebe1f58ff | |||
01428e3972 | |||
f98a0f4f75 | |||
2feef04ad9 | |||
53629f4d56 |
8 changed files with 735 additions and 0 deletions
67
puzzles.py
Executable file
67
puzzles.py
Executable file
|
@ -0,0 +1,67 @@
|
||||||
|
#!env python3
|
||||||
|
# Eryn Wells <eryn@erynwells.me>
|
||||||
|
'''
|
||||||
|
Parser for puzzles in the ./puzzles directory.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from sudoku import Sudoku, solvers
|
||||||
|
|
||||||
|
euler = []
|
||||||
|
norvig = []
|
||||||
|
|
||||||
|
def parse_puzzle_library(path, quiet=True):
|
||||||
|
if not quiet:
|
||||||
|
print('Parsing puzzles in {}'.format(path))
|
||||||
|
puzzles = _get_puzzles(path, quiet)
|
||||||
|
return puzzles
|
||||||
|
|
||||||
|
def _get_puzzles(filename, quiet):
|
||||||
|
with open(filename, 'r') as f:
|
||||||
|
puzzles = f.readlines()
|
||||||
|
return (_parse_puzzle(p, quiet) for p in puzzles if p)
|
||||||
|
|
||||||
|
def _parse_puzzle(puzzle, quiet):
|
||||||
|
puzzle = puzzle.strip()
|
||||||
|
if len(puzzle) == 81:
|
||||||
|
if not quiet:
|
||||||
|
print("Parsing '{}'".format(puzzle))
|
||||||
|
board = (int('0' if x == '.' else x) for x in puzzle)
|
||||||
|
return Sudoku(board=board)
|
||||||
|
else:
|
||||||
|
if not quiet:
|
||||||
|
print("Skipping '{}'".format(puzzle))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def parse_args(args):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument('--solver', '-s', default=None,
|
||||||
|
help='The solver to use to solve this puzzle.')
|
||||||
|
parser.add_argument('--verbose', '-v', action='store_true', default=False,
|
||||||
|
help='Print extra information when parsing puzzle libraries.')
|
||||||
|
parser.add_argument('library',
|
||||||
|
help='A library file containing puzzles, one per line.')
|
||||||
|
parser.add_argument('indexes', metavar='N', nargs='+', type=int,
|
||||||
|
help='0-based indexes of puzzles in the library')
|
||||||
|
return parser.parse_args(args)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = parse_args(sys.argv[1:])
|
||||||
|
puzzle_library = list(parse_puzzle_library(args.library, quiet=not args.verbose))
|
||||||
|
for i in args.indexes:
|
||||||
|
puzzle = puzzle_library[i]
|
||||||
|
print(puzzle)
|
||||||
|
if args.solver is not None:
|
||||||
|
try:
|
||||||
|
solver = getattr(solvers, args.solver)
|
||||||
|
puzzle.solve(solver.solve)
|
||||||
|
except AttributeError:
|
||||||
|
print('No solver named {}'.format(args.solver))
|
||||||
|
print(puzzle)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
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
|
95
puzzles/norvig.txt
Normal file
95
puzzles/norvig.txt
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
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..
|
230
sudoku/__init__.py
Normal file
230
sudoku/__init__.py
Normal file
|
@ -0,0 +1,230 @@
|
||||||
|
#!env python3
|
||||||
|
# Eryn Wells <eryn@erynwells.me>
|
||||||
|
'''
|
||||||
|
A Sudoku puzzle solver.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import math
|
||||||
|
|
||||||
|
BOLD_SEQUENCE = '\x1B[1m'
|
||||||
|
UNBOLD_SEQUENCE = '\x1B[0m'
|
||||||
|
|
||||||
|
class Sudoku:
|
||||||
|
def __init__(self, size=3, board=None):
|
||||||
|
self._size = size
|
||||||
|
sz4 = size ** 4
|
||||||
|
if board:
|
||||||
|
self._board = bytearray(board)[:sz4]
|
||||||
|
self._clues = frozenset(i for i in range(len(self._board)) if self._board[i] != 0)
|
||||||
|
else:
|
||||||
|
self._board = bytearray(sz4)
|
||||||
|
self._clues = frozenset()
|
||||||
|
self._possible_values = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def size(self):
|
||||||
|
'''
|
||||||
|
The size of the board. This dictates the length of one side of the boxes that the board is subdivided into.
|
||||||
|
'''
|
||||||
|
return self._size
|
||||||
|
|
||||||
|
@property
|
||||||
|
def row_size(self):
|
||||||
|
'''
|
||||||
|
The length of a row or column, or the area of a box in the grid.
|
||||||
|
'''
|
||||||
|
return self.size ** 2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def grid_size(self):
|
||||||
|
'''
|
||||||
|
The total number of squares in the grid.
|
||||||
|
'''
|
||||||
|
return self.size ** 4
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_squares(self):
|
||||||
|
'''
|
||||||
|
Iterator of xy-coordinates for every square in the grid.
|
||||||
|
'''
|
||||||
|
return itertools.product(range(self.row_size), repeat=2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_boxes(self):
|
||||||
|
'''
|
||||||
|
Iterator of xy-coordinates for every box in the grid.
|
||||||
|
'''
|
||||||
|
return itertools.product(range(self.size), repeat=2)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def possible_values(self):
|
||||||
|
'''
|
||||||
|
The set of valid values for any grid square. This method does not account for values made invalid by already
|
||||||
|
being present in a peer of a given square.
|
||||||
|
'''
|
||||||
|
if not self._possible_values:
|
||||||
|
self._possible_values = set(range(1, self.row_size + 1))
|
||||||
|
return self._possible_values
|
||||||
|
|
||||||
|
def possible_values_for_square(self, x, y):
|
||||||
|
value = self.get(x, y)
|
||||||
|
if value:
|
||||||
|
return {value}
|
||||||
|
else:
|
||||||
|
peers = self.peers(x, y)
|
||||||
|
return self.possible_values - peers
|
||||||
|
|
||||||
|
@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 boxes(self):
|
||||||
|
return self._apply_index_ranges(self.index_boxes)
|
||||||
|
|
||||||
|
def peers(self, x, y):
|
||||||
|
'''
|
||||||
|
Return a set of values of the peers for a given square.
|
||||||
|
'''
|
||||||
|
return {self._board[i] for i in self.index_peers(x, y) if self._board[i] != 0}
|
||||||
|
|
||||||
|
@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_boxes(self):
|
||||||
|
'''
|
||||||
|
Return an iterable of ranges of indexes into the board, each defining a box.
|
||||||
|
'''
|
||||||
|
return (self._box(x, y) for (x,y) in self.all_boxes)
|
||||||
|
|
||||||
|
def index_peers(self, x, y):
|
||||||
|
'''
|
||||||
|
Return a set of the peers, indexes into the board, for a given square.
|
||||||
|
'''
|
||||||
|
idx = self._xy_to_idx(x, y)
|
||||||
|
box = int(x / self.size), int(y / self.size)
|
||||||
|
return (set(self._row(y)) | set(self._column(x)) | set(self._box(*box))) - {idx}
|
||||||
|
|
||||||
|
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 _box(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.boxes)
|
||||||
|
return all(expected == set(g) for g in all_groups)
|
||||||
|
|
||||||
|
def solve(self, solver):
|
||||||
|
return solver(self)
|
||||||
|
|
||||||
|
def get(self, x, y):
|
||||||
|
idx = self._xy_to_idx(x, y)
|
||||||
|
value = self._board[idx]
|
||||||
|
return None if value == 0 else value
|
||||||
|
|
||||||
|
def set(self, x, y, value):
|
||||||
|
if value not in self.possible_values:
|
||||||
|
raise ValueError('{} not in set of possible values {}'.format(value, self.possible_values))
|
||||||
|
|
||||||
|
peers = self.peers(x, y)
|
||||||
|
if peers == self.possible_values:
|
||||||
|
raise NoPossibleValues('Peer set for ({},{}) contains all possible values'.format(x, y))
|
||||||
|
if value in peers:
|
||||||
|
raise ValueExistsInPeers('{} already exists in the peer set for ({},{})'.format(value, x, y))
|
||||||
|
|
||||||
|
self._set(x, y, value)
|
||||||
|
|
||||||
|
def unset(self, x, y):
|
||||||
|
self._set(x, y, 0)
|
||||||
|
|
||||||
|
def _set(self, x, y, value):
|
||||||
|
idx = self._xy_to_idx(x, y)
|
||||||
|
if idx in self._clues:
|
||||||
|
raise SquareIsClue('Cannot set clue square ({},{})'.format(x, y))
|
||||||
|
self._board[idx] = value
|
||||||
|
|
||||||
|
def _xy_to_idx(self, x, y):
|
||||||
|
return y * self.row_size + x
|
||||||
|
|
||||||
|
def _apply_index_ranges(self, ranges):
|
||||||
|
return ((self._board[i] for i in r) for r in ranges)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "{}(size={}, board='{}')".format(self.__class__.__name__,
|
||||||
|
self.size,
|
||||||
|
''.join(str(i) for i in self._board))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
field_width = len(str(max(self.possible_values)))
|
||||||
|
spacer = '{0}{1}{0}'.format('+', '+'.join(['-' * (field_width * self.size) for _ in range(self.size)]))
|
||||||
|
|
||||||
|
fmt = ''
|
||||||
|
for (y,x) in self.all_squares:
|
||||||
|
if x == 0:
|
||||||
|
if y % self.size == 0:
|
||||||
|
if y != 0:
|
||||||
|
fmt += '\n'
|
||||||
|
fmt += '{spacer}'
|
||||||
|
fmt += '\n'
|
||||||
|
|
||||||
|
if x % self.size == 0:
|
||||||
|
fmt += '|'
|
||||||
|
|
||||||
|
idx = self._xy_to_idx(x,y)
|
||||||
|
if idx in self._clues:
|
||||||
|
bold = BOLD_SEQUENCE
|
||||||
|
unbold = UNBOLD_SEQUENCE
|
||||||
|
else:
|
||||||
|
bold = unbold = ''
|
||||||
|
fmt += '{bold}{{board[{i}]:^{{width}}}}{unbold}'.format(i=idx, bold=bold, unbold=unbold)
|
||||||
|
|
||||||
|
if x == (self.row_size - 1):
|
||||||
|
fmt += '|'
|
||||||
|
fmt += '\n{spacer}'
|
||||||
|
|
||||||
|
return fmt.format(board=[str(i) if i != 0 else ' ' for i in self._board], spacer=spacer, width=field_width)
|
||||||
|
|
||||||
|
class SudokuError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class SquareIsClue(SudokuError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class NoPossibleValues(SudokuError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ValueExistsInPeers(SudokuError):
|
||||||
|
pass
|
6
sudoku/solvers/__init__.py
Normal file
6
sudoku/solvers/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Eryn Wells <eryn@erynwells.me>
|
||||||
|
'''
|
||||||
|
A library of Sudoku solvers.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from . import backtracker, dlx
|
61
sudoku/solvers/backtracker.py
Normal file
61
sudoku/solvers/backtracker.py
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
# Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
from .. import SquareIsClue, ValueExistsInPeers, NoPossibleValues
|
||||||
|
|
||||||
|
class Backtrack(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def solve(sudoku):
|
||||||
|
'''
|
||||||
|
Implements a recursive backtracking Sudoku solver.
|
||||||
|
'''
|
||||||
|
return _solve_square(sudoku, 0, 0)
|
||||||
|
|
||||||
|
def _solve_square(sudoku, x, y):
|
||||||
|
should_backtrack = False
|
||||||
|
for value in sudoku.possible_values:
|
||||||
|
should_backtrack = False
|
||||||
|
try:
|
||||||
|
sudoku.set(x, y, value)
|
||||||
|
except SquareIsClue:
|
||||||
|
# Do nothing with this square; continue on to the next square below.
|
||||||
|
pass
|
||||||
|
except ValueExistsInPeers:
|
||||||
|
# Try next value.
|
||||||
|
should_backtrack = True
|
||||||
|
continue
|
||||||
|
except NoPossibleValues:
|
||||||
|
# Need to backtrack.
|
||||||
|
should_backtrack = True
|
||||||
|
break
|
||||||
|
|
||||||
|
print('\r{!r}'.format(sudoku), end='', flush=True)
|
||||||
|
|
||||||
|
next_coord = _next_coord(sudoku, x, y)
|
||||||
|
if not next_coord:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
return _solve_square(sudoku, *next_coord)
|
||||||
|
except Backtrack:
|
||||||
|
should_backtrack = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if should_backtrack:
|
||||||
|
try:
|
||||||
|
sudoku.unset(x, y)
|
||||||
|
except SquareIsClue:
|
||||||
|
pass
|
||||||
|
raise Backtrack()
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
return sudoku
|
||||||
|
|
||||||
|
def _next_coord(sudoku, x, y):
|
||||||
|
x += 1
|
||||||
|
if x >= sudoku.row_size:
|
||||||
|
(x, y) = (0, y + 1)
|
||||||
|
if y >= sudoku.row_size:
|
||||||
|
return None
|
||||||
|
return (x, y)
|
137
sudoku/solvers/dlx.py
Normal file
137
sudoku/solvers/dlx.py
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
# Eryn Wells <eryn@erynwells.me>
|
||||||
|
|
||||||
|
from collections import namedtuple
|
||||||
|
from enum import Enum
|
||||||
|
from .. import SquareIsClue, ValueExistsInPeers, NoPossibleValues
|
||||||
|
|
||||||
|
class Backtrack(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConstraintKind(Enum):
|
||||||
|
CELL = 1
|
||||||
|
ROW = 2
|
||||||
|
COL = 3
|
||||||
|
BOX = 4
|
||||||
|
|
||||||
|
Constraint = namedtuple('CellConstraint', ['kind', 'index', 'value'])
|
||||||
|
Possibility= namedtuple('Possibility', ['row', 'col', 'value'])
|
||||||
|
|
||||||
|
class Node:
|
||||||
|
'''
|
||||||
|
A doubly-linked list node that is a member of two distinct lists. One list is the row it is a member of. The other
|
||||||
|
list is the column it is a member of.
|
||||||
|
'''
|
||||||
|
def __init__(self):
|
||||||
|
# Horizontal linked list. West is "previous", east is "next".
|
||||||
|
self.west = self.east = self
|
||||||
|
# Vertical linked list. North is "previous", south is "next".
|
||||||
|
self.north = self.south = self
|
||||||
|
|
||||||
|
def insert_after_in_row(self, node):
|
||||||
|
self._insert(node, 'west', 'east')
|
||||||
|
|
||||||
|
def insert_before_in_col(self, node):
|
||||||
|
self._insert(node, 'south', 'north')
|
||||||
|
|
||||||
|
def iterate_row(self):
|
||||||
|
return self._iterate('east')
|
||||||
|
|
||||||
|
def iterate_col(self):
|
||||||
|
return self._iterate('south')
|
||||||
|
|
||||||
|
def _insert(self, node, prev_attr, next_attr):
|
||||||
|
self_east = getattr(self, next_attr) # Save my old next node
|
||||||
|
setattr(self, next_attr, node) # My next points to the new node
|
||||||
|
setattr(node, next_attr, self_east) # New node's next points to the old next
|
||||||
|
setattr(node, prev_attr, self) # New node's prev points to me
|
||||||
|
if self_east:
|
||||||
|
setattr(self_east, prev_attr, node) # Old next's prev points to the new node
|
||||||
|
|
||||||
|
def _iterate(self, next_attr):
|
||||||
|
cur = self
|
||||||
|
while cur:
|
||||||
|
yield cur
|
||||||
|
cur = getattr(cur, next_attr)
|
||||||
|
if cur == self:
|
||||||
|
break
|
||||||
|
|
||||||
|
class Header(Node):
|
||||||
|
'''
|
||||||
|
A column header, including a count of the number of rows in this column.
|
||||||
|
'''
|
||||||
|
def __init__(self, constraint):
|
||||||
|
Node.__init__(self)
|
||||||
|
self.constraint = constraint
|
||||||
|
self.number_of_rows = 0
|
||||||
|
|
||||||
|
def append(self, node):
|
||||||
|
self.insert_before_in_col(node)
|
||||||
|
self.number_of_rows += 1
|
||||||
|
node.header = self
|
||||||
|
|
||||||
|
class Cell(Node):
|
||||||
|
'''
|
||||||
|
A cell in the DLX matrix.
|
||||||
|
'''
|
||||||
|
def __init__(self, possibility):
|
||||||
|
super(Cell, self).__init__()
|
||||||
|
self.header = None
|
||||||
|
self.possibility = possibility
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '{}({})'.format(self.__class__.__name__, self.possibility)
|
||||||
|
|
||||||
|
def solve(sudoku):
|
||||||
|
'''
|
||||||
|
Implements DLX, Don Knuth's Dancing Links version of Algorithm X, for solving exact cover problems.
|
||||||
|
'''
|
||||||
|
# TODO: Construct the matrix based on the provided sudoku.
|
||||||
|
# TODO: Perform the algorithm on the matrix.
|
||||||
|
# TODO: With the solution from running the algorithm above, fill in the sudoku.
|
||||||
|
return sudoku
|
||||||
|
|
||||||
|
def _build_matrix(sudoku):
|
||||||
|
# 1. Create headers for all columns.
|
||||||
|
headers = _build_headers(sudoku)
|
||||||
|
_build_rows(sudoku, headers)
|
||||||
|
return headers
|
||||||
|
|
||||||
|
def _build_headers(sudoku):
|
||||||
|
head = None
|
||||||
|
cur = None
|
||||||
|
|
||||||
|
def _insert_header(data):
|
||||||
|
header = Header(data)
|
||||||
|
if cur:
|
||||||
|
cur.insert_after_in_row(header)
|
||||||
|
return header
|
||||||
|
|
||||||
|
# Cell constraints
|
||||||
|
for i in range(sudoku.grid_size):
|
||||||
|
cur = _insert_header(Constraint(ConstraintKind.CELL, i, None))
|
||||||
|
|
||||||
|
# Row, Col, and Box constraints
|
||||||
|
for kind in (ConstraintKind.ROW, ConstraintKind.COL, ConstraintKind.BOX):
|
||||||
|
for i in range(sudoku.row_size):
|
||||||
|
for value in sudoku.possible_values:
|
||||||
|
cur = _insert_header(Constraint(kind, i, value))
|
||||||
|
|
||||||
|
# Head points to the first column header
|
||||||
|
head = cur.east
|
||||||
|
|
||||||
|
return head
|
||||||
|
|
||||||
|
def _build_rows(sudoku, headers):
|
||||||
|
for (index, coords) in enumerate(sudoku.all_squares):
|
||||||
|
board_value = sudoku.get(*coords)
|
||||||
|
possibilities = sudoku.possible_values_for_square(*coords)
|
||||||
|
for value in possibilities:
|
||||||
|
cur = None
|
||||||
|
for col in headers.iterate_row():
|
||||||
|
if col.constraint.index != index:
|
||||||
|
continue
|
||||||
|
cell = Cell(Possibility(*coords, value))
|
||||||
|
col.append(cell)
|
||||||
|
if cur:
|
||||||
|
cur.insert_after_in_row(cell)
|
||||||
|
cur = cell
|
89
sudoku/test.py
Normal file
89
sudoku/test.py
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
#!env python3
|
||||||
|
# Eryn Wells <eryn@erynwells.me>
|
||||||
|
'''
|
||||||
|
Unit tests for the Sudoku module.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import sudoku
|
||||||
|
|
||||||
|
class Sudoku4TestCase(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.board = sudoku.Sudoku(size=2)
|
||||||
|
|
||||||
|
class Sudoku4BasicTests(Sudoku4TestCase):
|
||||||
|
def test_that_board_is_sane(self):
|
||||||
|
self.assertEqual(self.board.size, 2)
|
||||||
|
self.assertEqual(len(self.board._board), 2**4)
|
||||||
|
|
||||||
|
def test_rows(self):
|
||||||
|
expected_rows = [
|
||||||
|
[ 0, 1, 2, 3],
|
||||||
|
[ 4, 5, 6, 7],
|
||||||
|
[ 8, 9, 10, 11],
|
||||||
|
[12, 13, 14, 15]
|
||||||
|
]
|
||||||
|
for (row, exrow) in zip(self.board.index_rows, expected_rows):
|
||||||
|
row_list = list(row)
|
||||||
|
with self.subTest(row=row_list, ex=exrow):
|
||||||
|
self.assertEqual(row_list, exrow)
|
||||||
|
|
||||||
|
def test_columns(self):
|
||||||
|
expected_columns = [
|
||||||
|
[0, 4, 8, 12],
|
||||||
|
[1, 5, 9, 13],
|
||||||
|
[2, 6, 10, 14],
|
||||||
|
[3, 7, 11, 15]
|
||||||
|
]
|
||||||
|
for (col, excol) in zip(self.board.index_columns, expected_columns):
|
||||||
|
col_list = list(col)
|
||||||
|
with self.subTest(col=col_list, ex=excol):
|
||||||
|
self.assertEqual(col_list, excol)
|
||||||
|
|
||||||
|
def test_boxes(self):
|
||||||
|
expected_boxes = {
|
||||||
|
(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, exbox) in expected_boxes.items():
|
||||||
|
with self.subTest(sq=coord, ex=exbox):
|
||||||
|
sq = set(self.board._box(*coord))
|
||||||
|
self.assertEqual(sq, exbox)
|
||||||
|
|
||||||
|
def test_peers(self):
|
||||||
|
expected_peers = {
|
||||||
|
(0,0): set([1, 2, 3, 4, 8, 12, 5]),
|
||||||
|
(1,0): set([0, 2, 3, 5, 9, 13, 4]),
|
||||||
|
(2,0): set([0, 1, 3, 6, 10, 14, 7]),
|
||||||
|
(3,0): set([0, 1, 2, 7, 11, 15, 6]),
|
||||||
|
|
||||||
|
(0,1): set([5, 6, 7, 0, 8, 12, 1]),
|
||||||
|
(1,1): set([4, 6, 7, 1, 9, 13, 0]),
|
||||||
|
(2,1): set([4, 5, 7, 2, 10, 14, 3]),
|
||||||
|
(3,1): set([4, 5, 6, 3, 11, 15, 2]),
|
||||||
|
|
||||||
|
(0,2): set([9, 10, 11, 0, 4, 12, 13]),
|
||||||
|
(1,2): set([8, 10, 11, 1, 5, 13, 12]),
|
||||||
|
(2,2): set([8, 9, 11, 2, 6, 14, 15]),
|
||||||
|
(3,2): set([8, 9, 10, 3, 7, 15, 14]),
|
||||||
|
|
||||||
|
(0,3): set([13, 14, 15, 0, 4, 8, 9]),
|
||||||
|
(1,3): set([12, 14, 15, 1, 5, 9, 8]),
|
||||||
|
(2,3): set([12, 13, 15, 2, 6, 10, 11]),
|
||||||
|
(3,3): set([12, 13, 14, 3, 7, 11, 10]),
|
||||||
|
}
|
||||||
|
for (coords, expeers) in expected_peers.items():
|
||||||
|
with self.subTest(coord=coords, ex=expeers):
|
||||||
|
peers = self.board.index_peers(*coords)
|
||||||
|
self.assertEqual(peers, expeers)
|
||||||
|
|
||||||
|
class Sudoku4SolvedTests(Sudoku4TestCase):
|
||||||
|
def test_that_an_empty_board_is_not_solved(self):
|
||||||
|
self.assertFalse(self.board.solved)
|
||||||
|
|
||||||
|
def test_simple_solution_is_solved(self):
|
||||||
|
board = (int(i) for i in '1234341221434321')
|
||||||
|
self.board = sudoku.Sudoku(2, board)
|
||||||
|
self.assertTrue(self.board.solved)
|
Loading…
Add table
Add a link
Reference in a new issue