Solve Sudoku

Jun 6, 2019 by Chris Luedtke

Description

Write a program to solve any Sudoku puzzle by filling the empty cells. In a sudoku solution, each digit 1-9 must occur exactly once in each row, each column, and in each of the 9 3x3 sub-boxes of the grid.

Sample input (0 represents a “blank” slot):

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

Solution

# check out https://en.wikipedia.org/wiki/Sudoku_solving_algorithms
from typing import List


def solve_sudoku(board: List[List[str]]) -> List[List[str]]:
    # create a set of all values that may be placed
    val_set = set(range(1, 10))

    # create a list of slot locations to fill
    d = []
    for r_i in range(9):
        for c_i in range(9):
            if board[r_i][c_i] == 0:
                d.append((r_i, c_i))

    # Iterate over slots, inserting numbers that are valid.
    # If no valid number possible, backtrack to previous slot
    #   and increment by 1
    i = 0
    while True:
        r_i, c_i = d[i]  # row and column index location of the current empty slot

        # set of numbers already used in row
        row_vals = set(board[r_i])

        # set of numbers already used in col
        col_vals = set(board[i][c_i] for i in range(9))

        # set of numbers already used in sub-grid
        b_r_i = r_i // 3 * 3
        b_c_i = c_i // 3 * 3
        box_vals = set(
            board[b_r_i    ][b_c_i:b_c_i + 3] +
            board[b_r_i + 1][b_c_i:b_c_i + 3] +
            board[b_r_i + 2][b_c_i:b_c_i + 3]
        )

        # subtract already-used numbers from the set of possible values
        candidates = val_set - row_vals - col_vals - box_vals

        # filter candidates to those greater than current placement
        current_value = board[r_i][c_i]
        candidates = sorted(c for c in candidates if c > current_value)

        # backtrack if no valid candidates exist
        if not candidates:
            board[r_i][c_i] = 0
            i -= 1
            continue

        # otherwise place the next greater candidate in the slot
        else:
            board[r_i][c_i] = candidates[0]
            # board is solved if last empty slot is filled
            if i == len(d) - 1:
                return board
            i += 1