*A1.1: First paragraph is changed. It no longer mentions a requirement of applying your search functions to a puzzle of your choice.*

*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 the grid navigation problem that you modify 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 starts`goal_state`

: signle state that represents the goal`successors_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 state`breadth_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- a list of states that shows the path found from the start state to the goal state, or
- the string
`'Goal not found'`

if the search has searched everywhere without finding the goal state.

- a list of states that shows the path found from the start state to the goal state, or

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 starts`goal_state`

: signle state that represents the goal`successors_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 state`solution_path`

: returned value that is either- a list of states that shows the path found from the start state to the goal state, or
- the string
`'Goal not found'`

if the search has searched everywhere without finding the goal state.

- a list of states that shows the path found from the start state to the goal state, or

`solution_path = depth_first_search(start_state, goal_state, successors_f)`

`start_state`

: single state where search starts`goal_state`

: signle state that represents the goal`successors_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 state`solution_path`

: returned value that is either- a list of states that shows the path found from the start state to the goal state, or
- the string
`'Goal not found'`

if the search has searched everywhere without finding the goal state.

- a list of states that shows the path found from the start state to the goal state, or

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.

In [ ]:

```
def search(start_state, goal_state, successors_f, breadth_first):
.
.
.
```

In [ ]:

```
def breadth_first_search(start_state, goal_state, successors_f):
.
.
.
```

In [ ]:

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

In [1]:

```
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
```

Out[1]:

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.

In [3]:

```
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, [])
```

In [4]:

```
successors_f('a')
```

Out[4]:

In [5]:

```
successors_f('e')
```

Out[5]:

In [6]:

```
successors_f('q')
```

Out[6]:

In [7]:

```
breadth_first_search('a', 'a', successors_f)
```

Out[7]:

In [8]:

```
breadth_first_search('a', 'b', successors_f)
```

Out[8]:

In [9]:

```
breadth_first_search('a', 'c', successors_f)
```

Out[9]:

In [10]:

```
breadth_first_search('a', 'd', successors_f)
```

Out[10]:

In [11]:

```
breadth_first_search('a', 'e', successors_f)
```

Out[11]:

In [12]:

```
breadth_first_search('a', 'm', successors_f)
```

Out[12]:

In [13]:

```
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}')
```

In [14]:

```
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}')
```

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.

In [15]:

```
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
```

In [16]:

```
grid_successors([3,4])
```

Out[16]:

In [17]:

```
grid_successors([3,9])
```

Out[17]:

In [18]:

```
grid_successors([0,0])
```

Out[18]:

In [19]:

```
print('Breadth first')
print('path from (0, 0) to (9, 9) is', breadth_first_search((0, 0), (9, 9), grid_successors))
```

In [20]:

```
print('Depth-first')
print('path from (0, 0) to (9, 9) is', depth_first_search((0, 0), (9, 9), grid_successors))
```

Let's plot the paths.

In [21]:

```
path_dfs = depth_first_search((0, 0), (9, 9), grid_successors)
path_dfs
```

Out[21]:

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.

In [22]:

```
import matplotlib.pyplot as plt
```

In [23]:

```
rows = [location[0] for location in path_dfs]
cols = [location[1] for location in path_dfs]
plt.plot(rows, cols, 'o-');
```

In [24]:

```
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-');
```

In [25]:

```
depth_first_search((0, 0), (9, 20), grid_successors)
```

Out[25]:

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.

In [26]:

```
camel_start_state
```

Out[26]:

In [27]:

```
camel_goal_state
```

Out[27]:

In [28]:

```
camel_successors_f(camel_start_state)
```

Out[28]:

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.

In [29]:

```
camel_start_state.index(' ')
```

Out[29]:

In [30]:

```
children = camel_successors_f(camel_start_state)
print(children[0])
camel_successors_f(children[0])
```

Out[30]:

In [31]:

```
def print_camel_state(state):
return ''.join(state)
print_camel_state(camel_start_state)
```

Out[31]:

In [32]:

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

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.

In [41]:

```
%run -i A1grader.py
```