Exemplo n.º 1
0
 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])
Exemplo n.º 2
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])
Exemplo n.º 3
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