I'm trying to make a sudoku solver using recursion by storing possible values in a dictionary with index as key and the set of possible values as value.Parameter b is just the board expressed as a single list, and .get_row, .get_col, .get_box methods return their respective values properly.If an entry in the dictionary has a set with a single element then it gets put into the board b, otherwise if there's no possible set with a single element a value gets picked from the set of possible numbers and the recursion starts. This is the full repeatable code:
class Sudoku: def solve_it(self, board: List[List[str]]) -> None:""" :param board: sudoku board gets modified inplace at .create_board_from_list :return: None""" unit_values = [] for i in range(len(board)): # flattening the board into unit_values for j in range(len(board[i])): unit_values.append(board[i][j]) self.go_through_recursive(board, unit_values) def get_row(self, n: int, b): # retrieve all elements of nth row in the board assert 0 <= n <= 8 return set(b[9 * n: 9 * (n + 1)]) def get_col(self, n: int, b): # retrieve all elements of nth col in the board assert 0 <= n <= 8 return set(b[n::9]) def get_box(self, n: int, b): # retrieve all elements of nth box in the board assert 0 <= n <= 8 return set(b[i] for i in range(81) if (i // 27) == (n // 3) and i % 9 // 3 == n % 3) def go_through_recursive(self, board, b, d=False):""" :param board: sudoku board as matrix where each row is a line :param b: sudoku board as a single long list""" numbers = {'1', '2', '3', '4', '5', '6', '7', '8', '9'} while True: if d: return True missing_dict = {} # populate missing_dict for idx in range(len(b)): if b[idx] == '.': row = int(idx / 9) col = idx % 9 box = int(row / 3) * 3 + int(col / 3) values = self.get_row(row, b).union(self.get_col(col, b)).union(self.get_box(box, b)) values.remove('.') missing_ones = numbers.difference(values) if len(missing_ones) == 0: # impossible to continue return False missing_dict[idx] = missing_ones old_count = b.count('.') for idx, missings in missing_dict.items(): # store assured values if len(missings) == 1: v = missings.pop() b[idx] = v if b.count('.') == 0: # check if complete self.create_board_from_list(board, b) return True if b.count('.') == old_count: # if no progress has been made for idx, s in missing_dict.items(): # iterate through the dictionary for number in s: # create a new board and store indecisive value then recur if d: return True bb = b[:] bb[idx] = number d = self.go_through_recursive(board, bb) def create_board_from_list(self, board, b): temp_board = [] chunk = 9 for idx in range(0, len(b), chunk): temp_board.append(b[idx: idx + chunk]) for idx in range(len(board)): board[idx] = temp_board[idx] print('done')
The issue I have is that once it completes, some numbers are repeated (either in the rows, cols or boxes). Also I've noticed that if I check the possible values inside the 'store assured values' loop it sometimes returns the whole range [1, 9] possibly being the reason of some rewriting
row = int(idx / 9) col = idx % 9 box = int(row / 3) * 3 + int(col / 3) values = self.get_row(row, b).union(self.get_col(col, b)).union(self.get_box(box, b)) values.remove('.') # some of these values are [1, 9]
for example
board =[[".",".","9","7","4","8",".",".","."], ["7",".",".",".",".",".",".",".","."], [".","2",".","1",".","9",".",".","."], [".",".","7",".",".",".","2","4","."], [".","6","4",".","1",".","5","9","."], [".","9","8",".",".",".","3",".","."], [".",".",".","8",".","3",".","2","."], [".",".",".",".",".",".",".",".","6"], [".",".",".","2","7","5","9",".","."]]
transforms the above into the incorrect
board = [["3","1","9","7","4","8","6","5","2"], ["7","8","5","6","3","2","1","1","9"], ["4","2","6","1","5","9","8","7","3"], ["5","3","7","9","8","6","2","4","1"], ["2","6","4","3","1","7","5","9","8"], ["1","9","8","5","2","4","3","6","7"], ["9","7","1","8","6","3","4","2","5"], ["8","5","2","4","9","1","7","3","6"], ["6","4","3","2","7","5","9","8","4"]]
while the correct board should be:
board = [["5","1","9","7","4","8","6","3","2"], ["7","8","3","6","5","2","4","1","9"], ["4","2","6","1","3","9","8","7","5"], ["3","5","7","9","8","6","2","4","1"], ["2","6","4","3","1","7","5","9","8"], ["1","9","8","5","2","4","3","6","7"], ["9","7","5","8","6","3","1","2","4"], ["8","3","2","4","9","1","7","5","6"], ["6","4","1","2","7","5","9","8","3"]]