def impossible_test(self): # impossible puzzles = [ # From https://norvig.com/sudoku.html -> column 4, no 1 possible because of triple 5-6 doubles and triple 1s '.....5.8....6.1.43..........1.5........1.6...3.......553.....61........4.........', # obvious doubles '12.......34...............5...........5..........................................', '11.......34...............5......................................................', ] for puzzle in puzzles: puzzle = str2grid(puzzle) s = Sudoku(puzzle) s.flush_candidates() self.assertFalse(s.check_possible()[0]) # possible puzzles = [ '280070309600104007745080006064830100102009800000201930006050701508090020070402050', '000010030009005008804006025000000600008004000120087000300900200065008000900000000', '1.....................................5..........................................', ] for puzzle in [puzzles[1]]: puzzle = str2grid(puzzle) s = Sudoku(puzzle) s.flush_candidates() self.assertTrue(s.check_possible()[0])
def test_impossible(self): # impossible puzzles = [ '.....5.8....6.1.43..........1.5........1.6...3.......553.....61........4.........', '12.......34...............5...........5..........................................', '11.......34...............5...........5..........................................', ] for puzzle in puzzles: puzzle = SudokuGrid(puzzle) s = Sudoku(puzzle) s.flush_candidates() self.assertFalse(s.check_possible()[0]) # possible puzzles = [ '280070309600104007745080006064830100102009800000201930006050701508090020070402050', '000010030009005008804006025000000600008004000120087000300900200065008000900000000', '1.....................................5..........................................', ] for puzzle in [puzzles[1]]: puzzle = SudokuGrid(puzzle) s = Sudoku(puzzle) s.flush_candidates() self.assertTrue(s.check_possible()[0])
def solveSudoku(grid, num_boxes=SIZE, verbose=True, all_solutions=False): """ idea based on https://dev.to/aspittel/how-i-finally-wrote-a-sudoku-solver-177g Try each step until failure, and repeat: 1) write numbers with only have 1 option 2) write candidates with only 1 option/ 2 pairs 3) with multiple options, take a guess and branch (backtrack) """ def solve(game, depth=0, progress_factor=1): nonlocal calls, depth_max, progress, progress_update, update_increment calls += 1 depth_max = max(depth, depth_max) solved = False while not solved: solved = True # assume solved edited = False # if no edits, either done or stuck for i in range(game.n): for j in range(game.n): if game.grid[i][j] == 0: solved = False options = game.candidates[i][j] if len(options) == 0: progress += progress_factor return False # this call is going nowhere elif len(options) == 1: # Step 1 game.place_and_erase(i, j, list(options)[0]) # Step 2 # game.flush_candidates() # full grid cleaning edited = True if not edited: # changed nothing in this round -> either done or stuck if solved: progress += progress_factor solution_set.append(grid2str(game.grid.copy())) return True else: # Find the box with the least number of options and take a guess # The place_and_erase() call changes this dynamically min_guesses = (game.n + 1, -1) for i in range(game.n): for j in range(game.n): options = game.candidates[i][j] if len(options) < min_guesses[0] and len( options) > 1: min_guesses = (len(options), (i, j)) i, j = min_guesses[1] options = game.candidates[i][j] # backtracking check point: progress_factor *= (1 / len(options)) for y in options: game_next = deepcopy(game) game_next.place_and_erase(i, j, y) # game_next.flush_candidates() # full grid cleaning solved = solve(game_next, depth=depth + 1, progress_factor=progress_factor) if solved and not all_solutions: break # return 1 solution if verbose and progress > progress_update: print("%.1f" % (progress * 100), end='...') progress_update = ((progress // update_increment) + 1) * update_increment return solved return solved calls, depth_max = 0, 0 progress, update_increment, progress_update = 0.0, 0.01, 0.01 solution_set = [] game = Sudoku(grid) game.flush_candidates() # check for obvious candidates possible, message = game.check_possible() if not possible: print('Error on board. %s' % message) info = { 'calls': calls, 'max depth': depth_max, 'nsolutions': len(solution_set), } return solution_set, False, info if verbose: print("solving: ", end='') solve(game, depth=0) if verbose: print("100.0") solved = (len(solution_set) >= 1) info = { 'calls': calls, 'max depth': depth_max, 'nsolutions': len(solution_set), } return solution_set, solved, info