Skip to content

Commit

Permalink
Graphs refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
br80 committed Nov 27, 2018
1 parent 1c5eb3c commit e86add8
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 125 deletions.
154 changes: 29 additions & 125 deletions projects/graph/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Drawing Connected Components
# Searching and Generating Graphs

This is a multi-stage project to draw a graph and show the connected
components of that graph in different colors.


# Day 1-2

## Part 1: Graph, Vertex, Edge Classes

In the file `graph.py`, implement a `Graph` class that supports the API expected
Expand Down Expand Up @@ -45,150 +47,52 @@ to your graph to ensure that edges to nonexistent vertices are rejected.
graph.add_edge('0', '4') # No '4' vertex, should raise an Exception!
```

## Part 2: Implement Breadth-First Traversal

## Part 2: Drawing with Bokeh

In `draw.py`, implement the `BokehGraph` class. The constructor should accept a
`Graph` object (as you implemented in part 1), and optionally other parameters
configuring e.g. graphical settings. The `show` method should use Bokeh to
generate and display HTML that draws the graph - the included `Pipfile` will
install Bokeh and necessarily dependencies.

This is purposefully open-ended, so feel free to get creative. But also, ask
questions to avoid being blocked, and generally discuss and work from lecture
examples.

Helpful resources:
- https://bokeh.pydata.org/en/latest/
- https://bokeh.pydata.org/en/latest/docs/user_guide/graph.html

To test your implementation, try drawing your graph from part 1, and share your
result with the class!


## Part 3: Draw a Random Graph

In `graph_demo.py` write a function that demonstrates `Graph` and `BokehGraph`
by instantiating a random graph and drawing it. The main parameters should be
the number of vertices and the number of edges - for instance, you could have 4
vertices and 6 edges. Edges should by default by bidirectional, be added
randomly and should be unique. You should pick reasonable default parameters,
but also allow them to be passed in as arguments.

*Challenge question* - what's the largest number of edges you should allow?
Think about this, and if you think you know the answer add it as a check to
reject input when the number of edges is too large.

`graph_demo.py` is set to be executable from the command line, e.g.
`python graph_demo.py`. You can use `argv` (already imported) to parse command
line arguments, e.g. `python graph_demo.py arg1 arg2`, and pass them along to
the graph and drawing functionality to further configure.

Suggested functionality that could be set via arguments:
- Directional edges: allow edges to not be bidirectional (this will change how
many you would allow maximum)
- Layout: should it be random, or set to follow a pattern?
- Jitter/bounding boxes: how should layout be tuned to avoid overlap?
- Colors: again should they be random, specified, or somewhere in betwen?
- Output: configure where the file is output, or support other Bokeh outputs
- Edge probability: this would replace the number of edges, and instead be a
probability between 0 and 1 of the likelihood of an edge between any two
vertices (supporting this *and* edge number is a bit tricky, but good to try)

As with number of vertices and edges, pick reasonable defaults so the user
doesn't have to specify anything to get results (i.e. `python graph_demo.py`
alone should do something). You don't need to do all of these arguments, and you
can also come up with your own - any feature that you think would be cool to
expose for the user to configure is worth considering.
Write a function within your Graph class that takes takes a starting node as an argument, then performs BFT. Your function should print the resulting nodes in the order they were visited.

Modify the `GraphView` code to draw the graph itself.

## Part 3: Implement Depth-First Traversal with a Stack

## Part 4: Implement Breadth-First Search (or Depth-First Search)
Write a function within your Graph class that takes takes a starting node as an argument, then performs DFT. Your function should print the resulting nodes in the order they were visited.

This is a necessary step in figuring out the connected components. Add methods
to the `Graph` class to support search - you can add a `bfs` method, `dfs`,
both, or a general `search` method that takes an argument to specify type.

For implementing depth-first search, try to use a stack instead of recursion.
Hint: Python lists already have the methods needed to function as a stack (`pop`
and `append`).
## Part 3.5: Implement Depth-First Traversal using Recursion

Write a function within your Graph class that takes takes a starting node as an argument, then performs DFT using recursion. Your function should print the resulting nodes in the order they were visited.

## Part 5: Color the Connected Components Differently
## Part 4: Implement Breadth-First Search

It's likely the random graphs have multiple connected components. Add a function
to `graph_demo.py` that uses search to identify them, and sets each vertex in a
component to be the same color so when drawn they look something like this:
Write a function within your Graph class that takes takes a starting node and a destination node as an argument, then performs BFS. Your function should return the shortest path from the start node to the destination node.

![Connected Components Mockup](https://raw.githubusercontent.com/LambdaSchool/Graphs/master/projects/graph/UI_UX%20Mockup.png)
## Part 5: Implement Depth-First Search

It is also suggested to add another command line argument to `graph_demo.py` to
allow you to select whether or not connected components should run (i.e. to
choose between just drawing a random graph, and drawing a random graph with the
connected components indicated by common color).
Write a function within your Graph class that takes takes a starting node and a destination node as an argument, then performs DFS. Your function should return a valid path (not necessarily the shortest) from the start node to the destination node.


## Part 6: Refactor
# Day 3-4

You've come a long way - time to hit the linter and clean up your code. You
should ensure your code conforms to
[pep8](https://www.python.org/dev/peps/pep-0008/), the standard Python style.

There are many tools to help, your editor may have built in support already
(look for squiggly lines below text and colored dots on the left). If you don't
already have something set up, check out
[Flake8](http://flake8.pycqa.org/en/latest/).
## Part 6: Write an algorithm that will let you create any number of random adventure rooms

In addition to style, think if there's more substantial refactoring you should
do - do the methods you have in each class make sense, or would they be more
appropriate somewhere else? Do names of methods you intend for your use (and not
the use of people who would reuse your code) start with `_`? In general, are you
picking good variable names, writing informative comments, and all that other
good stuff?
Part 1 may have felt familiar as you were filling out your graph class. Creating vertices and adding edges is very similar to creating Rooms and adding connections to your adventure game. Your adventure room map can be thought of as a graph where each node has four possible edges: n_to, s_to, e_to and w_to.

Picture two audiences for your code - coworkers, and future you. Both will care
and benefit from your code being as readable as possible.
Write an algorithm, `createRandomRooms` that takes in a number and generates that number of rooms. Each room should be connected and reachable from the player's starting room.

* It may help to assign a unique room_id to each room.
```
[_]-[_]
| |
[_]-[_]
```
* None of the rooms should overlap and they should all exist on a valid grid. For example, starting in the lower left and traveling E,W,N,S or N,E,S,W should always return you to the original room.
* How can you verify that all of the rooms are connected? (Hint: use a traversal)
* Some information about the room, like possible exits, should be present in the room description.
* Try to keep your runtime complexity to O(n).

### Stretch 1: Interactivity

Add interactivity to the generated graph - a widget that enables drawing a new
random graph would be a great feature:
https://bokeh.pydata.org/en/latest/docs/user_guide/interaction/widgets.html#userguide-interaction-widgets

### Stretch 2: Random edge weights

Add random edge weights, integers from 1-10. Draw the weight near the center of the edge.

> Hint: the center of a line is at point _x_,_y_, where _x_ is the average of
> the x coordinates of the endpoints of the line, and _y_ is the average of the
> y coordinates of the endpoints of the line.
Even though there are two edges between each connected vertex on the graph,
there should only be one label (since the edges are effectively a single,
non-directed edge.)

### Stretch 3: Clicks

Figure out which vertex a user is clicking on when they click with a mouse.
Log the vertex value when they do to test (you can just use `print`, or see the
Python library for logging: https://docs.python.org/3.5/library/logging.html).

Use this input processing to allow the user to select two vertices. Again Bokeh
interactivity documentation will be key to implementing this functionality.

### Stretch 4: Dijkstra

Implement [Dijkstra's
Algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) to find the
shortest route between starting and ending points from Stretch 3.
## Part 7: Randomly drop a Treasure, then write a `search` method to find it.

Highlight the route.
Create a large number of rooms (say, 100) then drop a treasure in a random room before the game starts. Then, write a function for your player that uses BFS to find the shortest path to the treasure.

### Stretch 5: Travelling saleperson

Read about the [travelling salesperson problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem).
This is a *hard* problem, literally, so even as a stretch goal a real
implementation is not expected - but it's an interesting and famous problem
in the space, and worth learning about if you want to go deep in graphs.
194 changes: 194 additions & 0 deletions projects/graph/README.md.backup
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Drawing Connected Components

This is a multi-stage project to draw a graph and show the connected
components of that graph in different colors.


## Part 1: Graph, Vertex, Edge Classes

In the file `graph.py`, implement a `Graph` class that supports the API expected
by `draw.py`. In particular, this means there should be a field `vertices` that
contains a dictionary mapping vertex labels to edges. For example:

```python
{
'0': {'1', '3'},
'1': {'0'},
'2': set(),
'3': {'0'}
}
```

This represents a graph with four vertices and two total (bidirectional) edges.
The vertex `'2'` has no edges, while `'0'` is connected to both `'1'` and `'3'`.

You should also create `add_vertex` and `add_edge` methods that add the
specified entities to the graph. To test your implementation, instantiate an
empty graph and then try to run the following:

```python
graph = Graph() # Instantiate your graph
graph.add_vertex('0')
graph.add_vertex('1')
graph.add_vertex('2')
graph.add_vertex('3')
graph.add_edge('0', '1')
graph.add_edge('0', '3')
print(graph.vertices)
```

You should see something like the first example. As a stretch goal, add checks
to your graph to ensure that edges to nonexistent vertices are rejected.

```python
# Continuing from previous example
graph.add_edge('0', '4') # No '4' vertex, should raise an Exception!
```


## Part 2: Drawing with Bokeh

In `draw.py`, implement the `BokehGraph` class. The constructor should accept a
`Graph` object (as you implemented in part 1), and optionally other parameters
configuring e.g. graphical settings. The `show` method should use Bokeh to
generate and display HTML that draws the graph - the included `Pipfile` will
install Bokeh and necessarily dependencies.

This is purposefully open-ended, so feel free to get creative. But also, ask
questions to avoid being blocked, and generally discuss and work from lecture
examples.

Helpful resources:
- https://bokeh.pydata.org/en/latest/
- https://bokeh.pydata.org/en/latest/docs/user_guide/graph.html

To test your implementation, try drawing your graph from part 1, and share your
result with the class!


## Part 3: Draw a Random Graph

In `graph_demo.py` write a function that demonstrates `Graph` and `BokehGraph`
by instantiating a random graph and drawing it. The main parameters should be
the number of vertices and the number of edges - for instance, you could have 4
vertices and 6 edges. Edges should by default by bidirectional, be added
randomly and should be unique. You should pick reasonable default parameters,
but also allow them to be passed in as arguments.

*Challenge question* - what's the largest number of edges you should allow?
Think about this, and if you think you know the answer add it as a check to
reject input when the number of edges is too large.

`graph_demo.py` is set to be executable from the command line, e.g.
`python graph_demo.py`. You can use `argv` (already imported) to parse command
line arguments, e.g. `python graph_demo.py arg1 arg2`, and pass them along to
the graph and drawing functionality to further configure.

Suggested functionality that could be set via arguments:
- Directional edges: allow edges to not be bidirectional (this will change how
many you would allow maximum)
- Layout: should it be random, or set to follow a pattern?
- Jitter/bounding boxes: how should layout be tuned to avoid overlap?
- Colors: again should they be random, specified, or somewhere in betwen?
- Output: configure where the file is output, or support other Bokeh outputs
- Edge probability: this would replace the number of edges, and instead be a
probability between 0 and 1 of the likelihood of an edge between any two
vertices (supporting this *and* edge number is a bit tricky, but good to try)

As with number of vertices and edges, pick reasonable defaults so the user
doesn't have to specify anything to get results (i.e. `python graph_demo.py`
alone should do something). You don't need to do all of these arguments, and you
can also come up with your own - any feature that you think would be cool to
expose for the user to configure is worth considering.

Modify the `GraphView` code to draw the graph itself.


## Part 4: Implement Breadth-First Search (or Depth-First Search)

This is a necessary step in figuring out the connected components. Add methods
to the `Graph` class to support search - you can add a `bfs` method, `dfs`,
both, or a general `search` method that takes an argument to specify type.

For implementing depth-first search, try to use a stack instead of recursion.
Hint: Python lists already have the methods needed to function as a stack (`pop`
and `append`).


## Part 5: Color the Connected Components Differently

It's likely the random graphs have multiple connected components. Add a function
to `graph_demo.py` that uses search to identify them, and sets each vertex in a
component to be the same color so when drawn they look something like this:

![Connected Components Mockup](https://raw.githubusercontent.com/LambdaSchool/Graphs/master/projects/graph/UI_UX%20Mockup.png)

It is also suggested to add another command line argument to `graph_demo.py` to
allow you to select whether or not connected components should run (i.e. to
choose between just drawing a random graph, and drawing a random graph with the
connected components indicated by common color).


## Part 6: Refactor

You've come a long way - time to hit the linter and clean up your code. You
should ensure your code conforms to
[pep8](https://www.python.org/dev/peps/pep-0008/), the standard Python style.

There are many tools to help, your editor may have built in support already
(look for squiggly lines below text and colored dots on the left). If you don't
already have something set up, check out
[Flake8](http://flake8.pycqa.org/en/latest/).

In addition to style, think if there's more substantial refactoring you should
do - do the methods you have in each class make sense, or would they be more
appropriate somewhere else? Do names of methods you intend for your use (and not
the use of people who would reuse your code) start with `_`? In general, are you
picking good variable names, writing informative comments, and all that other
good stuff?

Picture two audiences for your code - coworkers, and future you. Both will care
and benefit from your code being as readable as possible.


### Stretch 1: Interactivity

Add interactivity to the generated graph - a widget that enables drawing a new
random graph would be a great feature:
https://bokeh.pydata.org/en/latest/docs/user_guide/interaction/widgets.html#userguide-interaction-widgets

### Stretch 2: Random edge weights

Add random edge weights, integers from 1-10. Draw the weight near the center of the edge.

> Hint: the center of a line is at point _x_,_y_, where _x_ is the average of
> the x coordinates of the endpoints of the line, and _y_ is the average of the
> y coordinates of the endpoints of the line.

Even though there are two edges between each connected vertex on the graph,
there should only be one label (since the edges are effectively a single,
non-directed edge.)

### Stretch 3: Clicks

Figure out which vertex a user is clicking on when they click with a mouse.
Log the vertex value when they do to test (you can just use `print`, or see the
Python library for logging: https://docs.python.org/3.5/library/logging.html).

Use this input processing to allow the user to select two vertices. Again Bokeh
interactivity documentation will be key to implementing this functionality.

### Stretch 4: Dijkstra

Implement [Dijkstra's
Algorithm](https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm) to find the
shortest route between starting and ending points from Stretch 3.

Highlight the route.

### Stretch 5: Travelling saleperson

Read about the [travelling salesperson problem](https://en.wikipedia.org/wiki/Travelling_salesman_problem).
This is a *hard* problem, literally, so even as a stretch goal a real
implementation is not expected - but it's an interesting and famous problem
in the space, and worth learning about if you want to go deep in graphs.

0 comments on commit e86add8

Please sign in to comment.