Question: Question is in bold. Instrument the astar_search code in search.py so that it prints out 1. the number of expanded nodes (when a node is

Question is in bold.

Instrument the astar_search code in search.py so that it prints out

1. the number of expanded nodes (when a node is removed from the open list and

added to the close list),

2. the number of generated nodes (when a node is created from its parent),

3. the number of duplicate nodes (when the state is already in the open list or the

closed list),

4. the solution path, and

5. the solution path length.

code

import numpy as np

class Node:

"""A node in a search tree. Contains a pointer to the parent (the node

that this is a successor of) and to the actual state for this node. Note

that if a state is arrived at by two paths, then there are two nodes with

the same state. Also includes the action that got us to this state, and

the total path_cost (also known as g) to reach the node. Other functions

may add an f and h value; see best_first_graph_search and astar_search for

an explanation of how the f and h values are handled. You will not need to

subclass this class."""

def __init__(self, state, parent=None, action=None, path_cost=0):

"""Create a search tree Node, derived from a parent by an action."""

self.state = state

self.parent = parent

self.action = action

self.path_cost = path_cost

self.depth = 0

if parent:

self.depth = parent.depth + 1

def __repr__(self):

return "".format(self.state)

def __lt__(self, node):

return self.state < node.state

def expand(self, problem):

"""List the nodes reachable in one step from this node."""

return [self.child_node(problem, action)

for action in problem.actions(self.state)]

def child_node(self, problem, action):

"""[Figure 3.10]"""

next_state = problem.result(self.state, action)

next_node = Node(next_state, self, action, problem.path_cost(self.path_cost, self.state, action, next_state))

return next_node

def solution(self):

"""Return the sequence of actions to go from the root to this node."""

return [node.action for node in self.path()[1:]]

def path(self):

"""Return a list of nodes forming the path from the root to this node."""

node, path_back = self, []

while node:

path_back.append(node)

node = node.parent

return list(reversed(path_back))

# We want for a queue of nodes in breadth_first_graph_search or

# astar_search to have no duplicated states, so we treat nodes

# with the same state as equal. [Problem: this may not be what you

# want in other contexts.]

def __eq__(self, other):

return isinstance(other, Node) and self.state == other.state

def __hash__(self):

# We use the hash value of the state

# stored in the node instead of the node

# object itself to quickly search a node

# with the same state in a Hash Table

return hash(self.state)

def best_first_graph_search(problem, f, display=False):

"""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."""

f = memoize(f, 'f')

node = Node(problem.initial)

frontier = PriorityQueue('min', f)

frontier.append(node)

explored = set()

while frontier:

node = frontier.pop()

if problem.goal_test(node.state):

if display:

print(len(explored), "paths have been expanded and", len(frontier), "paths remain in the frontier")

return 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)

elif child in frontier:

if f(child) < frontier[child]:

del frontier[child]

frontier.append(child)

return None

def astar_search(problem, h=None, display=False):

"""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 = memoize(h or problem.h, 'h')

return best_first_graph_search(problem, lambda n: n.path_cost + h(n), display)

class EightPuzzle(Problem):

""" The problem of sliding tiles numbered from 1 to 8 on a 3x3 board, where one of the

squares is a blank. A state is represented as a tuple of length 9, whereelement at

index i represents the tile numberat index i (0 if it's an empty square) """

def __init__(self, initial, goal=(1, 2, 3, 4, 5, 6, 7, 8, 0)):

""" Define goal state and initialize a problem """

super().__init__(initial, goal)

def find_blank_square(self, state):

"""Return the index of the blank square in a given state"""

return state.index(0)

def actions(self, state):

""" Return the actions that can be executed in the given state.

The result would be a list, since there are only four possible actions

in any given state of the environment """

possible_actions = ['UP', 'DOWN', 'LEFT', 'RIGHT']

index_blank_square = self.find_blank_square(state)

if index_blank_square % 3 == 0:

possible_actions.remove('LEFT')

if index_blank_square < 3:

possible_actions.remove('UP')

if index_blank_square % 3 == 2:

possible_actions.remove('RIGHT')

if index_blank_square > 5:

possible_actions.remove('DOWN')

return possible_actions

def result(self, state, action):

""" Given state and action, return a new state that is the result of the action.

Action is assumed to be a valid action in the state """

# blank is the index of the blank square

blank = self.find_blank_square(state)

new_state = list(state)

delta = {'UP': -3, 'DOWN': 3, 'LEFT': -1, 'RIGHT': 1}

neighbor = blank + delta[action]

new_state[blank], new_state[neighbor] = new_state[neighbor], new_state[blank]

return tuple(new_state)

def goal_test(self, state):

""" Given a state, return True if state is a goal state or False, otherwise """

return state == self.goal

def check_solvability(self, state):

""" Checks if the given state is solvable """

inversion = 0

for i in range(len(state)):

for j in range(i + 1, len(state)):

if (state[i] > state[j]) and state[i] != 0 and state[j] != 0:

inversion += 1

return inversion % 2 == 0

def h(self, node):

""" Return the heuristic value for a given state. Default heuristic function used is

h(n) = number of misplaced tiles """

return sum(s != g for (s, g) in zip(node.state, self.goal))

code call:

astar_search(EightPuzzle((1,2,3,4,0,6,7,5,8)))

Step by Step Solution

There are 3 Steps involved in it

1 Expert Approved Answer
Step: 1 Unlock blur-text-image
Question Has Been Solved by an Expert!

Get step-by-step solutions from verified subject matter experts

Step: 2 Unlock
Step: 3 Unlock

Students Have Also Explored These Related Programming Questions!