def test_position_is_valid(): board = util.code_to_board(boards['81'][0]) board[0][0] = -1 assert not util.position_is_valid(board, 0, 0) board = util.code_to_board(boards['81'][0]) board[0][4] = board[0][0] assert not util.position_is_valid(board, 0, 0) assert not util.position_is_valid(board, 0, 4) board = util.code_to_board(boards['81'][0]) board[4][0] = board[0][0] assert not util.position_is_valid(board, 0, 0) assert not util.position_is_valid(board, 4, 0) board = util.code_to_board(boards['81'][0]) board[1][1] = board[0][0] assert not util.position_is_valid(board, 0, 0) assert not util.position_is_valid(board, 1, 1) board = util.code_to_board(boards['81'][0]) for i in range(9 * 9): x = util.to_x(i) y = util.to_y(i) assert util.position_is_valid(board, x, y)
def test_board_is_solved(): board = util.code_to_board(boards['81'][0]) board[0][0] = -1 with pytest.raises(util.InvalidBoardException): util.board_is_solved(board) board = util.code_to_board(boards['24'][0]) assert not util.board_is_solved(board) board = util.code_to_board(boards['81'][0]) assert util.board_is_solved(board)
def test_board_to_code(): board = [] with pytest.raises(util.InvalidBoardException): util.board_to_code(board) board = util.code_to_board(boards['24'][0]) assert util.board_to_code(board) == boards['24'][0]
def test_all_boards(): boards = load('tests/test-boards.json') for guess_count in sorted(list(boards.keys()), reverse=True): if guess_count == '81': continue print(guess_count) for index in range(len(boards[guess_count])): code = boards[guess_count][index] # print(code) board = code_to_board(code) guess_board = init_guesses(board) modified = True i = 0 while modified: i += 1 modified = False for move_type in deductive_methods: result, _, _ = deductive_methods[move_type][0](guess_board) if result: # print(i, move_type) modified = True break if not util.board_is_solved(util.remove_guesses(guess_board)): print(code) exit()
def naiive_backtrack(board_code): """Naiive backtracking algorithm used to find the solution to a board with missing clues. Parameters ---------- board_code : string Board code listed from top left to bottom right. Returns ------- string Board code for solved board. Raises ------ UnsolvableBoardException If the board does not have a solution. InvalidBoardException If the board code is invalid. """ board = util.code_to_board(board_code) if not util.board_is_valid(board): raise util.InvalidBoardException search_board = np.copy(board) if util.board_is_solved(board): return util.board_to_code(board) position = 0 step = 0 while True: step += 1 if position == 81: if not util.board_is_solved(search_board): raise util.InvalidBoardException # if we got here when solving there must have been an issue with the board code print(f'Solved board in {step} steps') return util.board_to_code(search_board) x = util.to_x(position) y = util.to_y(position) if board[x][y] != 0: position += 1 continue while search_board[x][y] <= 9: search_board[x][y] += 1 if util.position_is_valid(search_board, x, y): position += 1 break if search_board[x][y] == 10: search_board[x][y] = 0 position -= 1 if position < 0: raise util.UnsolvableBoardException while board[util.to_x(position)][util.to_y(position)] != 0: position -= 1 if position < 0: raise util.UnsolvableBoardException
def test_dfs(n=50): # testing solved board code = sudokus['81'][0] solution = dfs.dfs(code) assert util.board_is_solved(util.code_to_board(solution)) # testing unsolveable board code = sudokus['81'][0] code = '77' + code[2:] with pytest.raises(util.UnsolvableBoardException): solution = dfs.dfs(code) import random for i in tqdm(range(n)): code = test_list.pop(np.random.randint(0, len(test_list))) solution = dfs.dfs(code) assert util.board_is_solved(util.code_to_board(solution)) print(f'dfs solved {n} boards')
def test_backtrack(n=1): # testing solved board code = sudokus['81'][0] solution = backtracking.naiive_backtrack(code) assert util.board_is_solved(util.code_to_board(solution)) # testing unsolveable board code = sudokus['81'][0] code = '77' + code[2:] with pytest.raises(util.InvalidBoardException): solution = backtracking.naiive_backtrack(code) import random for i in tqdm(range(n)): code = test_list.pop(np.random.randint(0, len(test_list))) solution = backtracking.naiive_backtrack(code) assert util.board_is_solved(util.code_to_board(solution)) print(f'naiive backtrack solved {n} boards')
def naiive_backtrack_count(board_code): """Naiive backtracking algorithm used to count solutions to a board with missing clues. Parameters ---------- board_code : string Board code listed from top left to bottom right. Returns ------- int The number of unique solutions the board has. """ board = util.code_to_board(board_code) search_board = np.copy(board) if util.board_is_solved(board): return 1 position = 0 step = 0 solutions = 0 while True: step += 1 if position == 81: if not util.board_is_solved(search_board): raise util.InvalidBoardException solutions += 1 position -= 1 while board[util.to_x(position)][util.to_y(position)] != 0: position -= 1 if position < 0: print(f'found {solutions} solutions in {step} steps') return solutions x = util.to_x(position) y = util.to_y(position) if board[x][y] != 0: position += 1 continue while search_board[x][y] <= 9: search_board[x][y] += 1 if util.position_is_valid(search_board, x, y): position += 1 break if search_board[x][y] == 10: search_board[x][y] = 0 position -= 1 if position < 0: print(f'found {solutions} solutions in {step} steps') return solutions while board[util.to_x(position)][util.to_y(position)] != 0: position -= 1 if position < 0: print(f'found {solutions} solutions in {step} steps') return solutions
def fill_board(): code = '0' * 81 board = util.code_to_board(code) guesses = util.generate_guess_list(board) np.random.shuffle(guesses) for cell in guesses: np.random.shuffle(cell['guesses']) # print(guesses) pbar = tqdm(total=81) while (len(guesses)): pbar.update(1) # while there are empty cells random_cell = guesses.pop(0) for tentative in random_cell['guesses']: forward_valid = True # for each guess in the cell, try it and see if it leads to a solution board[random_cell['x']][random_cell['y']] = tentative updated_guesses = [] # to update guesses, iterate over all of the old guesses and see if they have to be changed after inserting the new tentative guess for old_guess in guesses: new_guess = { 'x': old_guess['x'], 'y': old_guess['y'], 'guesses': old_guess['guesses'][:] } # creating a deep copy of the guess if tentative in new_guess['guesses']: if new_guess['x'] == random_cell['x'] or new_guess[ 'y'] == random_cell['y'] or ( new_guess['x'] // 3 == random_cell['x'] // 3 and new_guess['y'] // 3 == random_cell['y'] // 3): new_guess['guesses'].remove(tentative) # forward checking if len(new_guess['guesses']) == 0: forward_valid = False break updated_guesses.append(new_guess) if forward_valid: try: solution = dfs.dfs_from_board(np.copy(board)) guesses = updated_guesses break # break out of this tentative testing loop except util.UnsolvableBoardException: board[random_cell['x']][random_cell['y']] = 0 else: board[random_cell['x']][random_cell['y']] = 0 pbar.close() assert util.board_is_solved(board) return board
def test_board_validity(): board = [] assert util.board_is_valid(board) == False board = np.zeros((6, 9)) assert util.board_is_valid(board) == False board = np.zeros((9, 9)) board[0][0] = -1 assert util.board_is_valid(board) == False board[0][0] = 10 assert util.board_is_valid(board) == False board = util.code_to_board(boards['24'][0]) assert util.board_is_valid(board) == True
def test_code_to_board(): code = 3 with pytest.raises(util.InvalidBoardException) as err: util.code_to_board(code) assert f'Board code must be a string, got type {type(3)}' in str(err.value) code = '33' with pytest.raises(util.InvalidBoardException) as err: util.code_to_board(code) assert 'Board code must be 81 characters long' in str(err.value) code = 'a' * 81 with pytest.raises(util.InvalidBoardException) as err: util.code_to_board(code) assert 'Board code must only contain numbers 0 - 9' in str(err.value) code = '000304907830000000000600000050080000006250030000000806009000400000000001000023000' board = util.code_to_board(code) for i in range(9 * 9): x = util.to_x(i) y = util.to_y(i) # remember we transpose the board assert board[y][x] == int(code[i])
def dfs(board_code): """Depth first search of board solutions, selecting branches with fewest possible guesses. Parameters ---------- board_code : string Board code listed from top left to bottom right. Returns ------- string Board code for solved board. Raises ------ UnsolvableBoardException If the board does not have a solution. """ board = util.code_to_board(board_code) if util.board_is_solved(board): return util.board_to_code(board) return dfs_from_board(board)
def test_init_guesses(): board = util.code_to_board(boards['34'][0]) # TODO pass
def test_print(): # TODO implement this util.print_board(util.code_to_board(boards['81'][0]))
for index in range(len(boards[guess_count])): code = boards[guess_count][index] # print(code) board = code_to_board(code) guess_board = init_guesses(board) modified = True i = 0 while modified: i += 1 modified = False for move_type in deductive_methods: result, _, _ = deductive_methods[move_type][0](guess_board) if result: # print(i, move_type) modified = True break if not util.board_is_solved(util.remove_guesses(guess_board)): print(code) exit() if __name__ == "__main__": # test_all_boards() moves = [] # code = load('tests/test-boards.json')['25'][0] code = '000000430100830090602000108001008064020070951000900380080010000703485609040003800' print(code) board = code_to_board(code) # print_board(board) deductive_solve(board, True)