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 "
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
Get step-by-step solutions from verified subject matter experts
