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')
Exemple #7
0
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])
Exemple #12
0
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)