Skip to content
Snippets Groups Projects
Commit faf09457 authored by JINLANG WANG's avatar JINLANG WANG
Browse files

add lab5

parent 19cd94a8
No related branches found
No related tags found
No related merge requests found
Pipeline #754144 passed
# Inheritance and DFS
## Inheritance
Paste and run the following code in a new python notebook called debug.ipynb in your p3 folder:
```python
class Parent:
def twice(self):
self.message()
self.message()
def message(self):
print("parent says hi")
class Child:
def message(self):
print("child says hi")
c = Child()
```
Modify `Child` so that it inherits from `Parent`.
What do you think will be printed if you call `c.twice()`? Discuss
with your group, then run it to find out.
When `self.some_method(...)` is called, and there are multiple methods
named `some_method` in your program, the type of `self` (the original object that is calling the method from the class) is what
matters for determining which one runs. It doesn't matter where the
`self.some_method(...)` is (could be any method).
## GraphSearcher
Copy and paste the following starter code (which you'll build on in the project):
```python
class GraphSearcher:
def __init__(self):
self.visited = set()
self.order = []
def visit_and_get_children(self, node):
""" Record the node value in self.order, and return its children
param: node
return: children of the given node
"""
raise Exception("must be overridden in sub classes -- don't change me here!")
def dfs_search(self, node):
# 1. clear out visited set and order list
# 2. start recursive search by calling dfs_visit
def dfs_visit(self, node):
# 1. if this node has already been visited, just `return` (no value necessary)
# 2. mark node as visited by adding it to the set
# 3. call self.visit_and_get_children(node) to get the children
# 4. in a loop, call dfs_visit on each of the children
```
The graphs we search on come in many shapes and formats
(e.g. matrices, files or web), but it would be nice if we could use
the same depth-first search (DFS) code when we want to search
different kinds of graphs. Therefore, we would like to implement a
base class `GraphSearcher` and implement the DFS algorithm in it.
For our purposes, we aren't using DFS to find a specific path. We
just want to see what nodes are reachable from a given starting
`node`, so these methods don't need to return any value. Your job is
to replace the comments in `dfs_search` and `dfs_visit` with code
(some comments may require a couple lines of code).
The `dfs_visit` method will call `visit_and_get_children` to record
the node value and determine the children of a given node. Subclasses
of `GraphSearcher` can override `visit_and_get_children` to lookup the
children of a node in different kinds of graphs (e.g. matrices, files
or web).
Try your code:
```python
g = GraphSearcher()
g.dfs_search("A")
```
You should get an exception. The purpose of `GraphSearcher` is not to
directly create objects, it is to let other clases inherit
`dfs_search` (we'll do the inheritance soon).
## Matrix Format
Paste and run the following:
```python
import pandas as pd
df = pd.DataFrame([
[0,1,0,1],
[0,0,1,0],
[0,0,0,1],
[0,0,1,0],
], index=["A", "B", "C", "D"], columns=["A", "B", "C", "D"])
df
```
A grid of ones and zeros like this is a common way to represent
directed graphs. A `1` in the "C" column of the "B" row means that
there is an edge from node B to node C.
Try drawing a directed graph on a piece of paper based on the above
grid.
`df.loc["????"]` looks up a row in a DataFrame. Use it to lookup the
children of node B.
Complete the following to print all the children of "B" (should only be "C"):
```python
for node, has_edge in df.loc["B"].items():
if ????:
print(????)
```
Let's create a class that inherits from `GraphSearcher` and works with
graphs represented as matrices:
```python
class MatrixSearcher(????):
def __init__(self, df):
super().????() # call constructor method of parent class
self.df = df
def visit_and_get_children(self, node):
# TODO: Record the node value in self.order
children = []
# TODO: use `self.df` to determine what children the node has and append them
return children
```
Complete the `????` and `TODO` parts. Test it, checking what nodes
are reachable from each starting point:
```python
m = MatrixSearcher(df)
m.dfs_search(????)
m.order
```
From "A", for example, `m.order` should be `['A','B', 'C', 'D']`. Look
back at the picture you drew of the graph and make sure you're getting
what you expect when starting from other nodes.
## scrape.py
If you've been doing this work in a notebook, you should now move your
code to a new module called `scrape.py` in your `p3` directory.
Labs/Lab5/dfs-vs-bfs/1.png

35.8 KiB

Labs/Lab5/dfs-vs-bfs/2.png

57.9 KiB

Labs/Lab5/dfs-vs-bfs/3.png

39.8 KiB

Labs/Lab5/dfs-vs-bfs/4.png

71.5 KiB

# DFS vs. BFS
In this lab, you'll get practice with depth-first search and
breadth-first search with some interactive exercises.
Start a new notebook on your virtual machine, then paste+run this code
in a cell (you don't need to read it):
```python
from IPython.display import display, HTML
from graphviz import Digraph
class test_graph:
def __init__(self):
self.nodes = {}
self.traverse_order = None # in what order were nodes checked?
self.next_guess = 0
self.colors = {}
def node(self, name):
name = str(name).upper()
self.nodes[name] = Node(self, name)
def edge(self, src, dst):
src, dst = str(src).upper(), str(dst).upper()
for name in [src, dst]:
if not name in self.nodes:
self.node(name)
self.nodes[src].children.append(self.nodes[dst])
def _repr_svg_(self):
g = Digraph(engine='neato')
for n in self.nodes:
g.node(n, fillcolor=self.colors.get(n, "white"), style="filled")
children = self.nodes[n].children
for i, child in enumerate(children):
g.edge(n, child.name, penwidth=str(len(children) - i), len="1.5")
return g._repr_image_svg_xml()
def dfs(self, src, dst):
src, dst = str(src).upper(), str(dst).upper()
self.traverse_order = []
self.next_guess = 0
self.colors = {}
self.visited = set()
self.path = self.nodes[src].dfs(dst)
display(HTML("now call .visit(???) to identify the first node explored"))
display(self)
def bfs(self, src, dst):
src, dst = str(src).upper(), str(dst).upper()
self.traverse_order = []
self.next_guess = 0
self.colors = {}
self.path = self.nodes[src].bfs(dst)
display(HTML("now call .visit(???) to identify the first node explored"))
display(self)
def visit(self, name):
name = str(name).upper()
if self.traverse_order == None:
print("please call dfs or bfs first")
if self.next_guess == len(self.traverse_order):
print("no more nodes to explore")
return
self.colors = {}
for n in self.traverse_order[:self.next_guess]:
self.colors[n] = "yellow"
if name == self.traverse_order[self.next_guess]:
display(HTML("Correct..."))
self.colors[name] = "yellow"
self.next_guess += 1
else:
display(HTML("<b>Oops!</b> Please guess again."))
self.colors[name] = "red"
display(self)
if self.next_guess == len(self.traverse_order):
if self.path == None:
display(HTML("You're done, there is no path!"))
else:
seq = input("What path was found? [enter nodes, comma separated]: ")
seq = tuple(map(str.strip, seq.upper().split(",")))
if seq == tuple(map(str.upper, self.path)):
print("Awesome!!!")
else:
print("actually, expected was: ", ",".join(self.path))
class Node:
def __init__(self, graph, name):
self.graph = graph
self.name = name
self.children = []
def __repr__(self):
return "node %s" % self.name
def dfs(self, dst):
if self.name in self.graph.visited:
return None
self.graph.traverse_order.append(self.name)
self.graph.visited.add(self.name)
if self.name == dst:
return (self.name, )
for child in self.children:
childpath = child.dfs(dst)
if childpath:
return (self.name, ) + childpath
return None
def backtrace(self):
nodes = []
node = self
while node != None:
nodes.append(node.name)
node = node.back
return tuple(reversed(nodes))
def bfs(self, dst):
added = set()
todo = [self]
self.back = None
added.add(self.name)
while len(todo) > 0:
curr = todo.pop(0)
self.graph.traverse_order.append(curr.name)
if curr.name == dst:
return curr.backtrace()
else:
for child in curr.children:
if not child.name in added:
todo.append(child)
child.back = curr
added.add(child.name)
return None
```
## Problem 1 [4-node, DFS]
Paste the following to a cell:
```python
g = test_graph()
g.edge(1, 2)
g.edge(4, 3)
g.edge(1, 3)
g.edge(2, 4)
g
```
It should look something like this:
<img src="1.png" width=300>
Node 1 has two children: nodes 2 and 3. The thicker line to node 2
indicates node 2 is in the `children` list before node 3.
Let's do a DFS from node 1 to 3. Paste the following:
```python
g.dfs(1, 3)
```
You should see something like this:
<img src="2.png" width=500>
Try calling the visit function with
```python
g.visit(1)
```
The visited node should look like this:
<img src="3.png" width=300>
Keep making `g.visit(????)` calls until you complete the depth first search.
Once the target node is reach, you'll be prompted to enter the path
from source to destination. Do so and type enter to check your
answer:
<img src="4.png" width=600>
## Problem 2 [4-node, BFS]
Paste+run the following(same graph structure as last time, but you'll
visit the nodes in a different order by doing a BFS):
```python
g = test_graph()
g.edge(1, 2)
g.edge(4, 3)
g.edge(1, 3)
g.edge(2, 4)
g.bfs(1, 3)
```
## Problem 3 [7-node, DFS+BFS]
Paste+run the following:
```python
g = test_graph()
for i in range(5):
g.edge(i, i+1)
g.edge(i, 6)
g.edge(6, i)
g.dfs(0, 4)
```
Then change `dfs` to `bfs` and try again.
## Problem 4 [6-node, BFS]
```python
g = test_graph()
for i in range(0, 4, 2):
g.edge(i, i+2)
g.edge(i+1, i+3)
g.edge(i, i+1)
g.edge(4, 5)
g.bfs(2, 1)
```
# Lab 5: Graph Search
1. Practice [graph search order](./dfs-vs-bfs)
2. Start the [module](./dfs-class) you'll be building for P3
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment