def my_astar_search_graph(problem, h=None):
    """A* search is best-first graph search with f(n) = g(n)+h(n).
    You need to specify the h function when you call astar_search, or
    else in your Problem subclass."""
    h = search.memoize(h or problem.h, 'h')
    iterations, node = my_best_first_graph_search_for_vis(
        problem, lambda n: n.path_cost + h(n))

    return (iterations, node)
def my_best_first_graph_search(problem, f):
    """
    Taken from Lab 3
    Search the nodes with the lowest f scores first.
    You specify the function f(node) that you want to minimize; for example,
    if f is a heuristic estimate to the goal, then we have greedy best
    first search; if f is node.depth then we have breadth-first search.
    There is a subtlety: the line "f = memoize(f, 'f')" means that the f
    values will be cached on the nodes as they are computed. So after doing
    a best first search you can examine the f values of the path returned.
    """
    # keep a track of the number of iterations for use in evaluation
    iterations = 0
    f = memoize(f, 'f')
    node = Node(problem.initial)
    iterations += 1
    # This is the goal state
    if problem.goal_test(node.state):
        iterations += 1
        return (iterations, node)
    # Create a priority queue that is ordered by its distance
    # from the distance travelled so far (g) + the straight line distance
    # from the new node to the goal state (h)
    frontier = PriorityQueue('min', f)
    frontier.append(node)
    iterations += 1
    explored = set()
    # Loop until there is no more nodes to visit
    while frontier:
        # Get the node with minimum f(n) = g(n) + h(n)
        node = frontier.pop()
        iterations += 1
        # We have reached the goal, return the solution
        if problem.goal_test(node.state):
            iterations += 1
            return iterations
        # Mark the node as visited
        explored.add(node.state)
        # Loop over the nodes neighbours and find the next node
        # with minimum f(n)
        for child in node.expand(problem):
            # Only consider new nodes which havent been explored yet
            # and the ones which we are about to explore in the
            # loop
            if child.state not in explored and child not in frontier:
                frontier.append(child)
                iterations += 1
            # Update the new distance (f(n)) for this node
            # if it is smaller than the previous known one
            elif child in frontier:
                incumbent = frontier[child]
                if f(child) < f(incumbent):
                    del frontier[incumbent]
                    frontier.append(child)
                    iterations += 1
        iterations += 1
    return iterations
def my_astar_search(agent_program, heur=None):
    """
    Taken from Lab 3
    A* search is best-first graph search with f(n) = g(n)+h(n).
    You need to specify the h function when you call astar_search, or
    else in your Problem subclass.
    """
    # define the heuristic function
    heur = memoize(heur or agent_program.problem.h, 'h')
    return my_best_first_graph_search(agent_program.problem,
                                      lambda n: n.path_cost + heur(n))
def my_best_first_graph_search_for_vis(problem, f):
    """Search the nodes with the lowest f scores first.
    You specify the function f(node) that you want to minimize; for example,
    if f is a heuristic estimate to the goal, then we have greedy best
    first search; if f is node.depth then we have breadth-first search.
    There is a subtlety: the line "f = memoize(f, 'f')" means that the f
    values will be cached on the nodes as they are computed. So after doing
    a best first search you can examine the f values of the path returned."""

    # we use these two variables at the time of visualisations
    iterations = 0

    f = search.memoize(f, 'f')
    node = search.Node(problem.initial)

    iterations += 1

    if problem.goal_test(node.state):
        iterations += 1
        return (iterations, node)

    frontier = search.PriorityQueue('min', f)
    frontier.append(node)

    iterations += 1

    explored = set()
    while frontier:
        node = frontier.pop()

        iterations += 1
        if problem.goal_test(node.state):
            iterations += 1
            return (iterations, node)

        explored.add(node.state)
        for child in node.expand(problem):
            if child.state not in explored and child not in frontier:
                frontier.append(child)
                iterations += 1
            elif child in frontier:
                incumbent = frontier[child]
                if f(child) < f(incumbent):
                    del frontier[incumbent]
                    frontier.append(child)
                    iterations += 1

        iterations += 1
    return None
def iterative_deepening_astar(problem, h, main_limit=100):
    h = search.memoize(h or problem.h)

    # Base A* heuristic - adding path cost to passed-in heuristic.
    def heuristic(n):
        return n.path_cost + h(n)

    # Find the initial state Node, and create the initial bound heuristic.
    initial = search.Node(problem.initial)
    bound = heuristic(initial)

    # Algorithm based off psuedocode from
    # https://en.wikipedia.org/wiki/Iterative_deepening_A*#Pseudocode
    def recursive_search(node, current_cost, limit):
        # Ensure the current heuristic hasn't gone over the limit.
        node_heuristic = heuristic(node)
        if current_cost + node_heuristic > limit:
            return current_cost + node_heuristic
        # Check if the search has found a goal state.
        if problem.goal_test(node.state):
            return node
        value = main_limit  # Large value at the start.
        # Recursively search over all child nodes.
        for child in node.expand(problem):
            inner_result = recursive_search(
                child, current_cost + problem.value(node.state), limit)
            if type(inner_result) is search.Node:
                return inner_result
            elif type(inner_result) is int and inner_result < value:
                value = inner_result
        return value

    while True:
        result = recursive_search(initial, 0, bound)
        if type(result) is search.Node:
            return result
        elif type(result) is int:
            if result == main_limit:
                return None
            bound = result
        else:
            print(str(type(result)) + " is an unhandled type")
def best_first_search_for_vis(problem, f):
    f = memoize(f, 'f')
    node = Node(problem.initial)
    frontier = PriorityQueue('min', f)
    frontier.append(node)
    explored = set()
    reached = []
    while frontier:
        node = frontier.pop()
        reached.append(node.state)
        if problem.goal_test(node.state):
            return (node, reached)
        explored.add(node.state)
        for child in node.expand(problem):
            if child.state not in explored and child not in frontier:
                frontier.append(child)
            elif child in frontier:
                if f(child) < frontier[child]:
                    del frontier[child]
                    frontier.append(child)
    return (failure, reached)
def astar_search_for_vis(problem, h=None):
    h = memoize(h or problem.h, 'h')
    return best_first_search_for_vis(problem, lambda n: n.path_cost + h(n))