"""Sudoku.py PADS-based command-line application for generating and solving Sudoku puzzles. These puzzles are given as a 9x9 grid of cells, some of which are filled with digits in the range 1-9. The task is to fill the remaining cells in such a way that each row of the grid, each column of the grid, and each of nine 3x3 squares into which the grid is partitioned, all have one copy of each of the nine digits. A proper Sudoku puzzle must have a unique solution, and it should be possible to reach that solution by a sequence of logical deductions without trial and error. To the extent possible, we strive to keep the same ethic in our automated solver, by mimicking human rule-based reasoning, rather than resorting to brute force backtracking search. D. Eppstein, July 2005. """ import random import sys from optparse import OptionParser from BipartiteMatching import imperfections from StrongConnectivity import StronglyConnectedComponents from Repetitivity import NonrepetitiveGraph from Wrap import wrap from Not import Not from TwoSatisfiability import Forced try: set except NameError: from sets import Set as set class BadSudoku(Exception): pass # raised when we discover that a puzzle has no solutions # ====================================================================== # Bitmaps and patterns # ====================================================================== digits = range(1,10) class group: def __init__(self, i, j, x, y, name): mask = 0 h,k = [q for q in range(4) if q != i and q != j] for w in range(3): for z in range(3): mask |= 1L << (x*3**i + y*3**j + w*3**h + z*3**k) self.mask = mask self.pos = [None]*9 self.name = "%s %d" % (name,x+3*y+1) cols = [group(0,1,x,y,"column") for x in range(3) for y in range(3)] rows = [group(2,3,x,y,"row") for x in range(3) for y in range(3)] sqrs = [group(1,3,x,y,"square") for x in range(3) for y in range(3)] groups = sqrs+rows+cols neighbors = [0]*81 for i in range(81): b = 1L<>self.logstream, line print >>self.logstream def place(self,digit,cell,explanation=None): """Change the puzzle by filling the given cell with the given digit.""" if digit != int(digit) or not 1 <= digit <= 9: raise ValueError("place(%d,%d): digit out of range" % (digit,cell)) if self.contents[cell] == digit: return if self.contents[cell]: self.log(["Unable to place",digit,"in",cellnames[cell], "as it already contains",str(self.contents[cell])+"."]) raise BadSudoku("place(%d,%d): cell already contains %d" % (digit,cell,self.contents[cell])) if (1L< 1: that = "would leave too few remaining cells" \ " to place those digits." if expls: expls[-1] += ',' if force == forces[-1]: expls[-1] += ' and' forcedigs = [str(x) for x in force] forcedigs.sort() forcemask = 0 for dig in force: for cell in force[dig]: forcemask |= 1L< 2; # in this case multiple cycles go through the same edge # and cell must be filled with the digit labeling that edge. # But for simplicity's sake we ignore that possibility; # it doesn't happen very often and when it does the repetitive # cycle rule will find it instead. mask = 1L< 1: for d in grid.choices(cell): T[(cell,d)] = [Not((cell,e)) for e in grid.choices(cell) if d != e] T[Not((cell,d))] = [] # If a cell has value d, its neighbors can't have the same value for cell in range(81): if len(grid.choices(cell)) > 1: for neighbor in range(81): if cell != neighbor and (1L<" for a in range(3): print "" for b in range(3): print "" for c in range(3): print "" for d in range(3): row = 3*a+c col = 3*b+d cell = 9*row+col if grid.contents[cell]: print '' % grid.contents[cell] # sty = '; color:black' # val = ' value="%d" readonly' % grid.contents[cell] else: print '' # sty = '; color:gray' # val = '' # print '' % (sty,val) print "" print "
%d
" print "" print "" return False def svg_format(grid): print ''' ''' print ' ' print ' ' for i in [3,6]: print ' ' % (30*i+2,30*i+2) print ' ' % (30*i+2,30*i+2) print ' ' print ' ' for i in [1,2,4,5,7,8]: print ' ' % (30*i+2,30*i+2) print ' ' % (30*i+2,30*i+2) print ' ' print ' ' for row in range(9): for col in range(9): cell = row*9+col if grid.contents[cell]: print ' %d' % \ (30*col+17, 30*row+25, grid.contents[cell]) print ' ' print '' return False output_formats = { "text": text_format, "txt": text_format, "t": text_format, "numeric": numeric_format, "num": numeric_format, "n": numeric_format, "html": html_format, "h": html_format, "svg": svg_format, "s": svg_format, } # ====================================================================== # Backtracking search for all solutions # ====================================================================== def all_solutions(grid, fastrules = True): """Generate sequence of completed Sudoku grids from initial puzzle.""" while True: # first try the usual non-backtracking rules try: while step(grid,fastrules): pass except BadSudoku: grid.log("A contradiction was found," " so this branch has no solutions.") return # no solutions # if they finished off the puzzle, there's only one solution if grid.complete(): grid.log("A solution to the puzzle has been found.") yield grid return # find a cell with few remaining possibilities def choices(c): ch = grid.choices(c) if len(ch) < 2: return (10,0,0) return (len(ch),c,ch[0]) L,c,d = min([choices(c) for c in range(81)]) # try it both ways branch = Sudoku(grid) grid.log("Failed to progress, " "creating a new backtracking search branch.") branch.logstream = grid.logstream branch.steps = grid.steps branch.original_cells = grid.original_cells branch.place(d,c,"The backtracking search will try this placement" " first. Then, after returning from this branch," " it will try preventing this placement.") for sol in all_solutions(branch,fastrules): yield sol grid.log(["Returned from backtracking branch; undoing placement of", d,"in",cellnames[c],"and all subsequent decisions."]) grid.rules_used.update(branch.rules_used) grid.rules_used.add("backtrack") grid.steps = branch.steps grid.unplace(d,1L<>sys.stderr, "Unrecognized command line syntax, use --help for input documentation" sys.exit(0) if options.show_rules: print """This solver knows the following rules. Rules occurring later in the list are attempted only when all earlier rules have failed to make progress. """ for name,rule,difficulty in rules: print name + ":" + rule.__doc__ sys.exit(0) if options.show_levels: print """ Puzzles are classified by difficulty, according to a weighted combination of the set of rules needed to solve each puzzle. There are six levels, in order by difficulty: easy, moderate, tricky, difficult, evil, and fiendish. In addition, a puzzle is classified as impossible if this program cannot find a solution for it, or if backtracking is needed to find the solution. """ sys.exit(0) if options.translate: if options.generate: print "Can not simultaneously generate and translate puzzles." sys.exit(0) try: outputter = output_formats[options.format.lower()] except KeyError: print "Unrecognized output format." sys.exit(0) if options.empty: outputter(Sudoku()) sys.exit(0) # ====================================================================== # Initial puzzle setup # ====================================================================== def random_puzzle(generate_symmetric = True): """Generate and return a randomly constructed Sudoku puzzle instance.""" puzzle = [] grid = Sudoku() def choices(cell): c = grid.choices(cell) return len(c) > 1 and c or [] while True: try: while not grid.complete(): d,c = random.choice([(d,c) for c in range(81) for d in choices(c)]) grid.place(d,c) while step(grid,True): pass puzzle.append((d,c)) if generate_symmetric: c = 80-c ch = grid.choices(c) if not ch: # avoid IndexError from random.choice raise BadSudoku("Placement invalidated symmetric cell") d = random.choice(ch) grid.place(d,c) while step(grid,True): pass puzzle.append((d,c)) except BadSudoku: puzzle = [] grid = Sudoku() continue break # find redundant information in initial state q = 0 while q < len(puzzle): grid = Sudoku(puzzle[:q] + puzzle[q+1+generate_symmetric:]) if not unisolvent(grid): q += 1+generate_symmetric else: del puzzle[q] if generate_symmetric: del puzzle[q] return Sudoku(puzzle) def read_puzzle(empty = ".0"): """Read and return a Sudoku instance from standard input.""" def digits(): for digit in sys.stdin.read(): if digit in empty: yield 0 elif '1' <= digit <= '9': yield int(digit) return Sudoku(digits()) if __name__ == '__main__': if options.generate: puzzle = random_puzzle(not options.asymmetric) print_puzzle = True print_solution = options.output_both else: puzzle = read_puzzle(options.emptychars) print_puzzle = options.output_both or options.translate print_solution = options.output_both or not options.translate if options.permute: puzzle = permute(puzzle, not options.asymmetric) if options.verbose: puzzle.logstream = sys.stderr if options.assume_unique: puzzle.assume_unique = True if options.twosat: puzzle.twosat = True # ====================================================================== # Main program: print and solve puzzle # ====================================================================== if __name__ == '__main__': print_level = True if print_puzzle: print_level = outputter(puzzle) if options.output_both and print_level: print if options.backtrack: solns = all_solutions(puzzle,False) else: while step(puzzle): pass solns = [puzzle] nsolns = 0 for soln in solns: if print_solution: print_level = outputter(soln) nsolns += 1 difficulty = 0 used_names = [] for name,rule,level in rules: if name in puzzle.rules_used: used_names.append(name) difficulty += 1<