简体   繁体   中英

How to improve this Sudoku Solver?

I had an idea a while ago of making a program that would solve a Sudoku board, so I made the code below. The code receives as input a 9x9 integer list, where an incomplete cell is represented by the number 0.

def checkSolutions(grid, i, j):
    """
    Given a Sudoku board and the position of an
    incomplete cell, it returns a list with all
    the possible numbers that this position can occupy.
    """

    digits = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    solutions = []

    solutions1x9 = [grid[x][j] for x in range(9)]
    solutions9x1 = [grid[i][x] for x in range(9)]

    rowGrid = i // 3
    columnGrid = j // 3
    solutions3x3 = [grid[i][j] for i in range(3*rowGrid, 3*rowGrid+3)
                    for j in range(3*columnGrid, 3*columnGrid+3)]

    solutions = solutions + [i for i in digits if i not in solutions1x9]
    solutions = solutions + [i for i in digits if i not in solutions9x1]
    solutions = solutions + [i for i in digits if i not in solutions3x3]

    solutions = list(set(solutions))
    solutions = [i for i in solutions if i not in solutions1x9]
    solutions = [i for i in solutions if i not in solutions9x1]
    solutions = [i for i in solutions if i not in solutions3x3]

    return solutions

def checkSudoku(grid):
    """
    Given a Sudoku board, it returns True if it is
    a board that follows the rules of the game and
    returns False otherwise.
    """

    digits = [1, 2, 3, 4, 5, 6, 7, 8, 9]
    for i in range(9):
        if sorted(grid[i]) != digits:
            return False

    for i in range(9):
        column = [grid[j][i] for j in range(9)]
        if sorted(column) != digits:
            return False

    for i in range(3):
        for j in range(3):
            grid3x3 = [grid[x][y] for x in range(3*i, 3*i+3)
                    for y in range(3*j, 3*j+3)]
            if sorted(grid3x3) != digits:
                return False

    return True

def sudoku(grid):
    """
    Given an incomplete Sudoku board, it prints on
    the screen the solution of that game.
    """
    for i in range(9):
        for j in range(9):
            if grid[i][j] == 0:
                solutions = checkSolutions(grid, i, j)

                if len(solutions) == 1:
                    grid[i][j] = solutions[0]
                    continue

                for k in solutions:
                    auxGrid = [x.copy() for x in grid]
                    auxGrid[i][j] = k
                    sudoku(auxGrid)

    if checkSudoku(grid):
        print(grid)

My problem is: if the function sudoku receives as input the following list

grid1 = [[0, 3, 7, 6, 0, 1, 5, 8, 4],
         [8, 0, 0, 3, 0, 4, 9, 2, 0],
         [6, 0, 9, 2, 5, 0, 3, 7, 1],
         [9, 8, 0, 5, 6, 7, 1, 0, 0],
         [0, 6, 0, 4, 1, 2, 0, 9, 0],
         [0, 0, 1, 8, 3, 9, 0, 6, 5],
         [7, 9, 6, 0, 4, 3, 8, 0, 2],
         [0, 5, 8, 7, 0, 6, 0, 0, 9],
         [1, 2, 4, 9, 0, 5, 6, 3, 0]]

it returns the result in less than one second, which is

[[2, 3, 7, 6, 9, 1, 5, 8, 4],
 [8, 1, 5, 3, 7, 4, 9, 2, 6],
 [6, 4, 9, 2, 5, 8, 3, 7, 1],
 [9, 8, 2, 5, 6, 7, 1, 4, 3],
 [5, 6, 3, 4, 1, 2, 7, 9, 8],
 [4, 7, 1, 8, 3, 9, 2, 6, 5],
 [7, 9, 6, 1, 4, 3, 8, 5, 2],
 [3, 5, 8, 7, 2, 6, 4, 1, 9],
 [1, 2, 4, 9, 8, 5, 6, 3, 7]]

But if it receives as input the list:

grid2 = [[1, 6, 8, 0, 0, 0, 9, 0, 2],
         [0, 0, 0, 3, 0, 1, 0, 0, 0],
         [0, 3, 0, 6, 2, 0, 0, 0, 0],
         [0, 0, 9, 0, 0, 0, 1, 0, 6],
         [0, 0, 1, 0, 0, 0, 3, 7, 0],
         [0, 4, 3, 5, 0, 0, 0, 0, 9],
         [0, 0, 0, 8, 0, 2, 6, 0, 0],
         [0, 0, 0, 9, 0, 5, 0, 2, 3],
         [2, 0, 6, 0, 3, 0, 7, 0, 0]]

it should returns

[[1, 6, 8, 4, 5, 7, 9, 3, 2],
 [5, 7, 2, 3, 9, 1, 4, 6, 8],
 [9, 3, 4, 6, 2, 8, 5, 1, 7],
 [8, 2, 9, 7, 4, 3, 1, 5, 6],
 [6, 5, 1, 2, 8, 9, 3, 7, 4],
 [7, 4, 3, 5, 1, 6, 2, 8, 9],
 [3, 9, 5, 8, 7, 2, 6, 4, 1],
 [4, 1, 7, 9, 6, 5, 8, 2, 3],
 [2, 8, 6, 1, 3, 4, 7, 9, 5]]

but the program takes so long to run that I don't even know if it returns something (I waited 30 minutes before closing the code execution). So my doubts are:

  • is there a mistake in my code for certain input types?
  • how can I improve my code to accept entries with more empty cells?
  • my code works perfectly fine and is it normal to take longer for entries with more empty cells?

Thanks for any help!

You can get your program to solve the second puzzle by adding a return statement to your sudoku() function at the end of the nested loops. The code below has that fix and some other rework ideas:

DIGITS = [1, 2, 3, 4, 5, 6, 7, 8, 9]

def checkSolutions(grid, i, j):
    """
    Given a Sudoku board, and the position of an
    incomplete cell, it returns a list with all
    the possible numbers that can occupy this position.
    """

    solutions1x9 = [grid[x][j] for x in range(9)]
    solutions9x1 = [grid[i][x] for x in range(9)]

    rowGrid = 3 * (i // 3)
    columnGrid = 3 * (j // 3)
    solutions3x3 = [grid[i][j] for i in range(rowGrid, rowGrid + 3) for j in range(columnGrid, columnGrid + 3)]

    return [digit for digit in DIGITS if digit not in solutions1x9 and digit not in solutions9x1 and digit not in solutions3x3]

def checkSudoku(grid):
    """
    Given a Sudoku board, it returns True if it is
    a board that follows the rules of the game and
    returns False otherwise.
    """

    for i in range(9):
        if sorted(grid[i]) != DIGITS:
            return False

    for j in range(9):
        column = [grid[i][j] for i in range(9)]

        if sorted(column) != DIGITS:
            return False

    for i in range(3):
        for j in range(3):
            grid3x3 = [grid[x][y] for x in range(3 * i, 3 * i + 3) for y in range(3 * j, 3 * j + 3)]

            if sorted(grid3x3) != DIGITS:
                return False
    return True

def sudoku(grid):
    """
    Given an incomplete Sudoku board, it prints on
    the screen the solution of that game.
    """

    for i in range(9):
        for j in range(9):
            if grid[i][j] == 0:
                solutions = checkSolutions(grid, i, j)

                if len(solutions) == 1:
                    grid[i][j] = solutions[0]  # permanent change to *this* reality
                    continue

                for k in solutions:
                    auxGrid = [x.copy() for x in grid]  # spawn a new reality
                    auxGrid[i][j] = k
                    sudoku(auxGrid)

                return  # already solved it recursively or no solution in *this* reality

    if checkSudoku(grid):
        print(grid)

grid2 = [[1, 6, 8, 0, 0, 0, 9, 0, 2],
         [0, 0, 0, 3, 0, 1, 0, 0, 0],
         [0, 3, 0, 6, 2, 0, 0, 0, 0],
         [0, 0, 9, 0, 0, 0, 1, 0, 6],
         [0, 0, 1, 0, 0, 0, 3, 7, 0],
         [0, 4, 3, 5, 0, 0, 0, 0, 9],
         [0, 0, 0, 8, 0, 2, 6, 0, 0],
         [0, 0, 0, 9, 0, 5, 0, 2, 3],
         [2, 0, 6, 0, 3, 0, 7, 0, 0]]

sudoku(grid2)

OUTPUT

> python3 test.py
[[1, 6, 8, 4, 5, 7, 9, 3, 2],
 [5, 7, 2, 3, 9, 1, 4, 6, 8],
 [9, 3, 4, 6, 2, 8, 5, 1, 7],
 [8, 2, 9, 7, 4, 3, 1, 5, 6],
 [6, 5, 1, 2, 8, 9, 3, 7, 4],
 [7, 4, 3, 5, 1, 6, 2, 8, 9],
 [3, 9, 5, 8, 7, 2, 6, 4, 1],
 [4, 1, 7, 9, 6, 5, 8, 2, 3],
 [2, 8, 6, 1, 3, 4, 7, 9, 5]]
>

Your solver is a brute-force solver which uses few smarts about the game itself. So, I can't promise there won't be a puzzle that again takes too long to finish. A more efficient solver might try all the tricks a human would to place digits before resorting to brute-force.

The modification I made may prevent your code from finding multiple solutions if they exist.

As I commented, this is a hard sudoku, so you have to guess several cells to be able to solve it. You can check my hard sudoku solver I programmed some time ago, if that helps:

def sudoku(grid):
    sudoku_dict = {}
    r = 'ABCDEFGHI'
    c = '123456789'
    for i in range(9):
        for j in range(9):
            sudoku_dict[r[i]+c[j]] = str(grid[i][j]) if grid[i][j] != 0 else c
    square = [[x+y for x in i for y in j] for i in ('ABC','DEF','GHI') for j in ('123','456','789')]
    peers = {}
    for key in sudoku_dict.keys():
        value = [i for i in square if key in i][0]
        row = [[x+y for x in i for y in j][0] for i in key[0] for j in c]
        col = [[x+y for x in i for y in j][0] for i in r for j in key[1]]
        peers[key] = set(x for x in value+row+col if x != key)
    for i in range(9):
        sudoku_dict = Check(sudoku_dict,peers)
    sudoku_dict = search(sudoku_dict, peers)
    solution = []
    for i in r:
        solution.append([])
        for j in c:
            solution[r.find(i)].append(int(sudoku_dict[i+j]))
    return solution

def Check(sudoku_dict, peers):
    for k,v in sudoku_dict.items():
        if len(v) == 1:
            for s in peers[k]:
                sudoku_dict[s] = sudoku_dict[s].replace(v,'')
                if len(sudoku_dict[s])==0:
                    return False
    return sudoku_dict

def search(sudoku_dict,peers):
    if Check(sudoku_dict,peers)==False:
        return False
    if all(len(sudoku_dict[s]) == 1 for s in sudoku_dict.keys()): 
        return sudoku_dict
    n,s = min((len(sudoku_dict[s]), s) for s in sudoku_dict.keys() if len(sudoku_dict[s]) > 1)
    res = []
    for value in sudoku_dict[s]:
        new_sudoku_dict = sudoku_dict.copy()
        new_sudoku_dict[s] = value
        ans = search(new_sudoku_dict, peers)
        if ans:
            res.append(ans)
    if len(res) > 1:
        raise Exception("Error")
    elif len(res) == 1:
        return res[0]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM