diff --git a/Sudoku Solver/solver.py b/Sudoku Solver/solver.py index 1fc11f0..e0022ce 100644 --- a/Sudoku Solver/solver.py +++ b/Sudoku Solver/solver.py @@ -1,78 +1,131 @@ -b = [ - [7,8,0,4,0,0,1,2,0], - [6,0,0,0,7,5,0,0,9], - [0,0,0,6,0,1,0,7,8], - [0,0,7,0,4,0,2,6,0], - [0,0,1,0,5,0,9,3,0], - [9,0,4,0,6,0,0,0,5], - [0,7,0,3,0,0,0,1,2], - [1,2,0,0,0,7,4,0,0], - [0,4,9,2,0,6,0,0,7] -] - -def solve(b): - find = find_empty(b) # Check empty box - if not find: - return True # Final state of the board when every box is filled - else: - row, col = find +import typing + +def print_board(board: list[list[int]]) -> None: + """ + Prints the Sudoku board in a formatted way. + + Args: + board (list[list[int]]): The 9x9 Sudoku board. + """ + for i in range(len(board)): + # Print horizontal separator after every 3 rows (except the first) + if i % 3 == 0 and i != 0: + print("- - - - - - - - - - - - - ") + + for j in range(len(board[0])): + # Print vertical separator after every 3 columns (except the last) + if j % 3 == 0 and j != 0: + print(" | ", end="") + + # Print the number, followed by a space, unless it's the last column + if j == 8: + print(board[i][j]) + else: + print(str(board[i][j]) + " ", end="") + + +def find_empty(board: list[list[int]]) -> typing.Optional[tuple[int, int]]: + """ + Finds the next empty (0) cell on the Sudoku board. + + Args: + board (list[list[int]]): The 9x9 Sudoku board. - for i in range(1,10): - if valid(b, i, (row, col)): - b[row][col] = i # Place the number in the board if its valid + Returns: + tuple[int, int]: A tuple (row, col) of the empty cell, or None if no empty cell is found. + """ + for r in range(len(board)): + for c in range(len(board[0])): + if board[r][c] == 0: # Check if the current cell contains a 0 (empty) + return (r, c) # Return the row and column of the empty cell + return None # No empty cells found, board is full - if solve(b): - return True # Continue from that board state onwards - - b[row][col] = 0 - return False +def valid(board: list[list[int]], num: int, pos: tuple[int, int]) -> bool: + """ + Checks if placing a number 'num' at 'pos' (row, col) is valid according to Sudoku rules. -def valid(b, num, pos): - - for i in range(len(b[0])): # Check row - if b[pos[0]][i] == num and pos[1] != i: # Check element in row if its equal to the number added and if its the current position being added to then ignore it + Args: + board (list[list[int]]): The 9x9 Sudoku board. + num (int): The number (1-9) to check for validity. + pos (tuple[int, int]): The position (row, col) on the board where 'num' is being considered. + + Returns: + bool: True if the number is valid at the given position, False otherwise. + """ + row, col = pos + + # Check row: Iterate through all columns in the current row + for c in range(len(board[0])): + # If the number 'num' is found in the row and it's not the position we are checking + if board[row][c] == num and col != c: return False - - for i in range(len(b)): # Check column - if b[i][pos[1]] == num and pos[0] != i: # Check element column-wise if it equals the number added and its not the position that the new number was just added into + # Check column: Iterate through all rows in the current column + for r in range(len(board)): + # If the number 'num' is found in the column and it's not the position we are checking + if board[r][col] == num and row != r: return False - box_x = pos[1] // 3 # Check 3x3 box - box_y = pos[0] // 3 + # Check 3x3 box: Determine the top-left corner of the 3x3 box + box_x = col // 3 # Integer division to get the box's column index (0, 1, or 2) + box_y = row // 3 # Integer division to get the box's row index (0, 1, or 2) - for i in range(box_y*3, box_y*3 + 3): # Loop through the 3x3 boxes - for j in range(box_x * 3, box_x*3 + 3): - if b[i][j] == num and (i,j) != pos: # Check if same number exits in the 3x3 box and (i, j) was the position the new number was just added to + # Iterate through cells within the determined 3x3 box + for r_box in range(box_y * 3, box_y * 3 + 3): + for c_box in range(box_x * 3, box_x * 3 + 3): + # If the number 'num' is found in the box and it's not the position we are checking + if board[r_box][c_box] == num and (r_box, c_box) != pos: return False - return True + return True # If no conflicts are found, the number is valid -def print_board(b): - for i in range(len(b)): - if i % 3 == 0 and i != 0: - print("- - - - - - - - - - - - - ") # Separate sections of the board row-wise (Every 3x3 box) - for j in range(len(b[0])): - if j % 3 == 0 and j != 0: - print(" | ", end="") # Separate sections of the board column-wise +def solve(board: list[list[int]]) -> bool: + """ + Solves the Sudoku board using a backtracking algorithm. - if j == 8: - print(b[i][j]) - else: - print(str(b[i][j]) + " ", end="") + This function attempts to fill empty cells (represented by 0) with valid numbers + (1-9). If a number leads to a solution, it returns True. If not, it backtracks + and tries another number. + + Args: + board (list[list[int]]): The 9x9 Sudoku board to be solved. + This board will be modified in-place. + + Returns: + bool: True if the board is successfully solved, False otherwise (no solution exists). + """ + find = find_empty(board) # Find the next empty cell (row, col) + + # Base case: If no empty cell is found, the board is solved + if not find: + return True + else: + row, col = find # Unpack the row and column of the empty cell + + # Try numbers from 1 to 9 in the current empty cell + for num_to_try in range(1, 10): + if valid(board, num_to_try, (row, col)): # Check if the number is valid at this position + board[row][col] = num_to_try # If valid, place the number on the board + + # Recursively call solve for the updated board + if solve(board): + return True # If the recursive call finds a solution, propagate True + # Backtrack: If the current number doesn't lead to a solution, reset the cell to 0 + board[row][col] = 0 -def find_empty(b): - for i in range(len(b)): - for j in range(len(b[0])): - if b[i][j] == 0: # Check if there is a 0 in each position of the box - return (i, j) # Return the row and column + return False # No number from 1-9 worked for the current empty cell, so backtrack further - return None -print_board(b) -print(" ") -solve(b) -print_board(b) +if __name__ == "__main__": + # Example Sudoku board + initial_board = [ + [7,8,0,4,0,0,1,2,0], + [6,0,0,0,7,5,0,0,9], + [0,0,0,6,0,1,0,7,8], + [0,0,7,0,4,0,2,6,0], + [0,0,1,0,5,0,9,3,0], + [9,0,4,0,6,0,0,0,5], + [0,7,0,3,0,0,