Type your name here
Breadth-first and depth-first are two algorithms for performing uninformed search---a search that does not use knowledge about the goal of the search. You will implement both search algorithms in python and test them on a simple graph. Then you will apply your search algorithms to a puzzle problem of your choice as explained below.
In this jupyter notebook, you must implement at least the following functions. The first function, search
, implements the algorithm shown at the end of Lecture Notes 03 Problem-Solving Agents
.
solution_path = search(start_state, goal_state, successors_f, breadth_first)
start_state
: single state where search startsgoal_state
: signle state that represents the goalsuccessors_f
: function that accepts a single argument that is a state and returns a list of states that can be reached in one step from the argument statebreadth_first
: True
or False
. If True
, search
performs a breadth-first search. Otherwise it performs a depth-first search.solution_path
: returned value that is either'Goal not found'
if the search has searched everywhere without finding the goal state.The next two functions are very short, and just call search
. These are really just convenience functions so the user of your search algorithm does not need to know what the boolean-valued argument breadth_first
means.
solution_path = breadth_first_search(start_state, goal_state, successors_f)
start_state
: single state where search startsgoal_state
: signle state that represents the goalsuccessors_f
: function that accepts a single argument that is a state and returns a list of states that can be reached in one step from the argument statesolution_path
: returned value that is either'Goal not found'
if the search has searched everywhere without finding the goal state.solution_path = depth_first_search(start_state, goal_state, successors_f)
start_state
: single state where search startsgoal_state
: signle state that represents the goalsuccessors_f
: function that accepts a single argument that is a state and returns a list of states that can be reached in one step from the argument statesolution_path
: returned value that is either'Goal not found'
if the search has searched everywhere without finding the goal state.Each receives as arguments the starting state, the goal state, and a successors function. If they succeed in finding the goal state, breadth_first_search
returns the breadth-first solution path as a list of states starting with the start_state
and ending with the goal_state
. depth_first_search
returns the depth-first solution path. If they do not success, they return the string 'Goal not found'
.
Test your code by running them with a simple graph as shown in the following example, and with the grid example.
Test your code on other graphs, too. The final grading script will include graphs not shown here.
def search(start_state, goal_state, successors_f, breadth_first):
.
.
.
def breadth_first_search(start_state, goal_state, successors_f):
.
.
.
def depth_first_search(start_state, goal_state, successors_f):
.
.
.
Here is a simple example. States are defined by lower case letters. A dictionary stores a list of successor states for each state in the graph that has successors.
successors = {'a': ['b', 'c', 'd'],
'b': ['e', 'f', 'g'],
'c': ['a', 'h', 'i'],
'd': ['j', 'z'],
'e': ['a', 'k', 'l'], # Watch out. This creates the cycle a -> b -> e-> a
'g': ['m'],
'k': ['z']}
successors
{'a': ['b', 'c', 'd'], 'b': ['e', 'f', 'g'], 'c': ['a', 'h', 'i'], 'd': ['j', 'z'], 'e': ['a', 'k', 'l'], 'g': ['m'], 'k': ['z']}
Here is an example of a successors function that works for any search problem whose graph is explicitly represented with a successors dictionary as used in this example.
def successors_f(state):
successors = {'a': ['b', 'c', 'd'],
'b': ['e', 'f', 'g'],
'c': ['a', 'h', 'i'],
'd': ['j', 'z'],
'e': ['a', 'k', 'l'], # Watch out. This creates the cycle a -> b -> e-> a
'g': ['m'],
'k': ['z']}
return successors.get(state, [])
successors_f('a')
['b', 'c', 'd']
successors_f('e')
['a', 'k', 'l']
successors_f('q')
[]
breadth_first_search('a', 'a', successors_f)
['a']
breadth_first_search('a', 'b', successors_f)
['a', 'b']
breadth_first_search('a', 'c', successors_f)
['a', 'c']
breadth_first_search('a', 'd', successors_f)
['a', 'd']
breadth_first_search('a', 'e', successors_f)
['a', 'b', 'e']
breadth_first_search('a', 'm', successors_f)
['a', 'b', 'g', 'm']
for goal in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'z']:
path = breadth_first_search('a', goal, successors_f)
print(f'Path from a to {goal}: {path}')
Path from a to a: ['a'] Path from a to b: ['a', 'b'] Path from a to c: ['a', 'c'] Path from a to d: ['a', 'd'] Path from a to e: ['a', 'b', 'e'] Path from a to f: ['a', 'b', 'f'] Path from a to g: ['a', 'b', 'g'] Path from a to h: ['a', 'c', 'h'] Path from a to i: ['a', 'c', 'i'] Path from a to j: ['a', 'd', 'j'] Path from a to k: ['a', 'b', 'e', 'k'] Path from a to l: ['a', 'b', 'e', 'l'] Path from a to m: ['a', 'b', 'g', 'm'] Path from a to z: ['a', 'd', 'z']
for goal in ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'z']:
path = depth_first_search('a', goal, successors_f)
print(f'Path from a to {goal}: {path}')
Path from a to a: ['a'] Path from a to b: ['a', 'b'] Path from a to c: ['a', 'c'] Path from a to d: ['a', 'd'] Path from a to e: ['a', 'b', 'e'] Path from a to f: ['a', 'b', 'f'] Path from a to g: ['a', 'b', 'g'] Path from a to h: ['a', 'c', 'h'] Path from a to i: ['a', 'c', 'i'] Path from a to j: ['a', 'd', 'j'] Path from a to k: ['a', 'b', 'e', 'k'] Path from a to l: ['a', 'b', 'e', 'l'] Path from a to m: ['a', 'b', 'g', 'm'] Path from a to z: ['a', 'b', 'e', 'k', 'z']
Let's try a navigation problem around a grid of size 10 x 10. Rows and columns will be indexed from 0 to 9.
The following function takes the input state and returns all possible states.
def grid_successors(state):
row, col = state
# succs will be list of tuples () rather than list of lists [] because state must
# be an immutable type to serve as a key in dictionary of expanded nodes
succs = []
for r in [-1, 0, 1]: #check each row
for c in [-1, 0, 1]: # check in each col
newr = row + r
newc = col + c
if 0 <= newr <= 9 and 0 <= newc <= 9:
succs.append( (newr, newc) )
return succs
grid_successors([3,4])
[(2, 3), (2, 4), (2, 5), (3, 3), (3, 4), (3, 5), (4, 3), (4, 4), (4, 5)]
grid_successors([3,9])
[(2, 8), (2, 9), (3, 8), (3, 9), (4, 8), (4, 9)]
grid_successors([0,0])
[(0, 0), (0, 1), (1, 0), (1, 1)]
print('Breadth first')
print('path from (0, 0) to (9, 9) is', breadth_first_search((0, 0), (9, 9), grid_successors))
Breadth first path from (0, 0) to (9, 9) is [(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]
print('Depth-first')
print('path from (0, 0) to (9, 9) is', depth_first_search((0, 0), (9, 9), grid_successors))
Depth-first path from (0, 0) to (9, 9) is [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (1, 9), (2, 8), (2, 7), (2, 6), (2, 5), (2, 4), (2, 3), (2, 2), (2, 1), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (8, 0), (9, 1), (8, 2), (7, 2), (6, 2), (5, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (5, 9), (6, 8), (6, 7), (6, 6), (6, 5), (7, 4), (8, 4), (9, 5), (8, 6), (8, 7), (8, 8), (9, 9)]
Let's plot the paths.
path_dfs = depth_first_search((0, 0), (9, 9), grid_successors)
path_dfs
[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (0, 6), (0, 7), (0, 8), (1, 9), (2, 8), (2, 7), (2, 6), (2, 5), (2, 4), (2, 3), (2, 2), (2, 1), (3, 0), (4, 0), (5, 0), (6, 0), (7, 0), (8, 0), (9, 1), (8, 2), (7, 2), (6, 2), (5, 2), (4, 3), (4, 4), (4, 5), (4, 6), (4, 7), (4, 8), (5, 9), (6, 8), (6, 7), (6, 6), (6, 5), (7, 4), (8, 4), (9, 5), (8, 6), (8, 7), (8, 8), (9, 9)]
Now, we have the path to goal state. To plot this path we must extract the first value in each tuple and put them in a list called rows
to use as the $y$ coordinate of each point, and build a second list called cols
of second values.
import matplotlib.pyplot as plt
rows = [location[0] for location in path_dfs]
cols = [location[1] for location in path_dfs]
plt.plot(rows, cols, 'o-');
path_bfs = breadth_first_search((0, 0), (9, 9), grid_successors)
print(path_bfs)
rows = [location[0] for location in path_bfs]
cols = [location[1] for location in path_bfs]
plt.plot(rows, cols, 'o-');
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6), (7, 7), (8, 8), (9, 9)]
depth_first_search((0, 0), (9, 20), grid_successors)
'Goal not found'
Define a new function named grid_successors_center_block
by copying the above grid_successors
function and then modify it to add a square obstacle from coordinates (4, 4) to (6, 6). Plot the path that results from doing breadth-first and depth-first searches with the start and goal states shown above. Insert code cells and markdown cells here to do these steps.
Define grid_successors_center_block here for 5 points.
Replace this text with at least two sentences of text describing changes you made to grid_successors to implement grid_successors_center_block for 5 points.
Make plots of paths resulting from breadth-first and depth-first searches of the grid for 5 points.
Replace this text with at least five sentences of text describing your resulting plots. How are they different with the center block? for 5 points.
For extra credit, use your functions to solve the Camels Puzzle, described at Logic Puzzles.
The following code illustrates one possible state representation and shows results of a breadth-first and a dept-first search. You must define a new successors function, called camel_successors_f
.
If you do not do this extra credit step, please remove this and the following cells that pertain to this extra credit.
camel_start_state
['R', 'R', 'R', 'R', ' ', 'L', 'L', 'L', 'L']
camel_goal_state
['L', 'L', 'L', 'L', ' ', 'R', 'R', 'R', 'R']
camel_successors_f(camel_start_state)
[['R', 'R', 'R', ' ', 'R', 'L', 'L', 'L', 'L'], ['R', 'R', 'R', 'R', 'L', ' ', 'L', 'L', 'L']]
A handy function for implementing camel_successors_f
is index
that returns the index where an element is found in a list or a tuple.
camel_start_state.index(' ')
4
children = camel_successors_f(camel_start_state)
print(children[0])
camel_successors_f(children[0])
['R', 'R', 'R', ' ', 'R', 'L', 'L', 'L', 'L']
[['R', 'R', ' ', 'R', 'R', 'L', 'L', 'L', 'L'], ['R', 'R', 'R', 'L', 'R', ' ', 'L', 'L', 'L']]
def print_camel_state(state):
return ''.join(state)
print_camel_state(camel_start_state)
'RRRR LLLL'
bfs = breadth_first_search(camel_start_state, camel_goal_state, camel_successors_f)
print(f'Breadth-first solution: ({len(bfs)} steps)')
for s in bfs:
print(print_camel_state(s))
dfs = depth_first_search(camel_start_state, camel_goal_state, camel_successors_f)
print(f'Depth-first solution: ({len(dfs)} steps)')
for s in dfs:
print(print_camel_state(s))
Breadth-first solution: (25 steps) RRRR LLLL RRR RLLLL RRRLR LLL RRRLRL LL RRRL LRLL RR LRLRLL R RLRLRLL RLR RLRLL RLRLR RLL RLRLRLR L RLRLRLRL RLRLRL LR RLRL LRLR RL LRLRLR LRLRLRLR L RLRLRLR LLR RLRLR LLRLR RLR LLRLRLR R LLRLRL RR LLRL LRRR LL LRLRRR LLL RLRRR LLLLR RRR LLLL RRRR Depth-first solution: (25 steps) RRRR LLLL RRR RLLLL RRRLR LLL RRRLRL LL RRRL LRLL RR LRLRLL R RLRLRLL RLR RLRLL RLRLR RLL RLRLRLR L RLRLRLRL RLRLRL LR RLRL LRLR RL LRLRLR LRLRLRLR L RLRLRLR LLR RLRLR LLRLR RLR LLRLRLR R LLRLRL RR LLRL LRRR LL LRLRRR LLL RLRRR LLLLR RRR LLLL RRRR
Your notebook will be run and graded automatically. Download A1grader.tar and extract A1grader.py from it. Run the code in the following cell to demonstrate an example grading session. You should see a perfect score of 80/80 if your functions are defined correctly.
The remaining 20% will be based on your writing. In markdown cells, explain what your functions are doing and make observations about your results. Also mention problems you encountered in trying to solve this assignment.
Do not include this section in your notebook.
Name your notebook Lastname-A1.ipynb
. So, for me it would be Anderson-A1.ipynb
. Submit the file using the Assignment 1
link on Canvas.
%run -i A1grader.py
======================= Code Execution ======================= Extracting python code from notebook named 'Anderson-A1.ipynb' and storing in notebookcode.py Removing all statements that are not function or class defs or import statements. Searching this graph: {'a': ['b'], 'b': ['c', 'd'], 'c': ['e'], 'd': ['f', 'i'], 'e': ['g', 'h', 'i']} Looking for path from a to b. Calling breadth_first_search(a, b, successorsf) and depth_first_search(a, b, successorsf) 10/10 points. Your breadth_first_search found correct solution path of ['a', 'b'] 10/10 points. Your depth_first_search found correct solution path of ['a', 'b'] Looking for path from a to i. Calling breadth_first_search(a, i, successorsf) and depth_first_search(a, i, successorsf) 20/20 points. Your breadth_first_search found correct solution path of ['a', 'b', 'd', 'i'] 20/20 points. Your depth_first_search found correct solution path of ['a', 'b', 'c', 'e', 'i'] Looking for nonexistent path from a to denver. Calling breadth_first_search(a, denver, successorsf) and depth_first_search(a, denver, successorsf) 10/10 points. Your breadth_first_search found correct solution path of Goal not found 10/10 points. Your depth_first_search found correct solution path of Goal not found ====================================================================== notebooks Execution Grade is 80 / 80 ====================================================================== __ / 5 points. Correct implementation of the grid_successors_center_block function __ / 5 points. At least two sentences of Text describing changes you made to grid_successors to implement grid_successors_center_block __ / 5 points. Plots of paths resulting from breadth-first and depth-first searches of the grid __ / 5 points. At least five sentences of text describing your resulting plots. How are they different with the center block? ====================================================================== notebooks Discussion Grade is __ / 20 ====================================================================== ====================================================================== notebooks FINAL GRADE is _ / 100 ====================================================================== Extra Credit: Earn one point of extra credit for using your search functions to solve the camel puzzle. notebooks EXTRA CREDIT is 0 / 1