Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language: python
python:
- "2.7"
script: py.test
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
[![Build Status](https://travis-ci.org/henrykh/data-structures.svg)](https://travis-ci.org/henrykh/data-structures)

# data-structures

This repository holds sample code for a number of classic data structures implemented in Python.
Expand All @@ -20,6 +22,10 @@ This repository holds sample code for a number of classic data structures implem

* Graph (unweighted, directed)
- As described from [Python Patterns - Implementing Graphs](https://www.python.org/doc/essays/graphs/) "Graphs are networks consisting of nodes connected by edges or arcs. In directed graphs, the connections between nodes have a direction, and are called arcs."
- Graph Traversal
- Graph traversal is explored in a [depth first](http://en.wikipedia.org/wiki/Depth-first_search) and [breadth first](http://en.wikipedia.org/wiki/Breadth-first_search) style.
- Graph weighting
- Edges accept value weights or default to one if none is provided

## Resources
[Linked Lists Wiki](http://en.wikipedia.org/wiki/Linked_list)
Expand All @@ -33,6 +39,8 @@ This repository holds sample code for a number of classic data structures implem


Pytest - for testing structure functions
Travis CI


##Collaborators
Joel Stanner
Expand Down
Empty file added __init__.py
Empty file.
Empty file added graph/__init__.py
Empty file.
80 changes: 64 additions & 16 deletions graph/simple_graph.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
from collections import OrderedDict
from queue import Queue


W_DEFAULT = 1


class Graph(object):
"""Implements a graph data structure"""

Expand All @@ -11,43 +18,41 @@ def nodes(self):
def edges(self):
"""return a list of all edges in the graph"""

return [(key, node) for key, value in
self.graph_dict.iteritems() for node in value]
return [(key, node, weight) for key, edge_dict in
self.graph_dict.iteritems()
for node, weight in edge_dict.iteritems()]

def add_node(self, node):
"""add a new node 'n' to the graph"""
self.graph_dict.setdefault(node,[])
self.graph_dict.setdefault(node, OrderedDict())

def add_edge(self, node_1, node_2):
def add_edge(self, node_1, node_2, weight=W_DEFAULT):
"""add a new edge to the graph connecting 'n1' and 'n2', if either n1
or n2 are not already present in the graph, they are added.
"""
try:
self.graph_dict[node_1].append(node_2)
self.add_node(node_2)
self.graph_dict[node_1][node_2] = weight
except KeyError:
self.add_node(node_1)
self.graph_dict[node_1].append(node_2)
if node_2 not in self.nodes():
self.add_node(node_2)
self.graph_dict[node_1][node_2] = weight

def del_node(self, node):
"""delete the node 'n' from the graph"""
try:
del self.graph_dict[node]
for val_list in self.graph_dict.values():
if node in val_list:
val_list.remove(node)
for val_dict in self.graph_dict.values():
if node in val_dict:
del val_dict[node]
except KeyError:
raise KeyError("Node not found")

def del_edge(self, node_1, node_2):
"""delete the edge connecting 'n1' and 'n2' from the graph"""
try:
self.graph_dict[node_1].remove(node_2)
del self.graph_dict[node_1][node_2]
except KeyError:
raise KeyError("First node not found")
except ValueError:
raise ValueError("Edge not found")
raise KeyError("Edge not found")

def has_node(self, node):
"""True if node 'n' is contained in the graph, False if not"""
Expand All @@ -56,7 +61,7 @@ def has_node(self, node):
def neighbors(self, node):
"""return the list of all nodes connected to 'n' by edges"""
try:
return self.graph_dict[node]
return self.graph_dict[node].keys()
except KeyError:
raise KeyError("Node not found")

Expand All @@ -69,3 +74,46 @@ def adjacent(self, node_1, node_2):
return node_2 in self.graph_dict[node_1]
except KeyError:
raise KeyError("First node not found")

def depth_first_traversal(self, start):
"""Perform a full depth-first traversal of the graph beginning at start.
Return the full visited path when traversal is complete.
"""
try:
explored = OrderedDict()
return self._depth_first_traversal(start, explored)
except KeyError:
raise KeyError("Node does not exist")

def _depth_first_traversal(self, start, explored):
"""Helper function for depth_first_traversal for recursion"""
explored.setdefault(start, 1)

for child in self.graph_dict[start]:
if child not in explored:
self._depth_first_traversal(child, explored)

return explored.keys()

def breadth_first_traversal(self, start):
"""Perform a full breadth-first traversal of the graph, beginning at
start. Return the full visited path when traversal is complete.
"""
try:
explored = OrderedDict()
queue = Queue()
explored.setdefault(start, 1)

queue.enqueue(start)

while queue.size():
node = queue.dequeue()

for child in self.graph_dict[node]:
if child not in explored:
explored.setdefault(child, 1)
queue.enqueue(child)

return explored.keys()
except KeyError:
raise KeyError("Node does not exist")
97 changes: 92 additions & 5 deletions graph/test_simple_graph.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
from simple_graph import Graph

from collections import OrderedDict

@pytest.fixture(scope="function")
def test_graph():
Expand All @@ -9,12 +9,47 @@ def test_graph():
test_graph.add_node(42)
test_graph.add_node("test")
test_graph.add_edge(5, 42)
test_graph.add_edge(42, "test")
test_graph.add_edge(42, "test", 1000)
test_graph.add_edge("test", 5)

return test_graph


@pytest.fixture(scope="function")
def test_depth_traversal_graph():
test_graph = Graph()
for i in range(10):
test_graph.add_node(i)
test_graph.add_edge(0, 1)
test_graph.add_edge(0, 2)
test_graph.add_edge(0, 3)
test_graph.add_edge(1, 4)
test_graph.add_edge(1, 5)
test_graph.add_edge(1, 8)
test_graph.add_edge(5, 6)
test_graph.add_edge(6, 7)
test_graph.add_edge(2, 9)

return test_graph


@pytest.fixture(scope="function")
def test_breadth_traversal_graph():
test_graph = Graph()
for i in range(1, 10):
test_graph.add_node(i)
test_graph.add_edge(1, 2)
test_graph.add_edge(1, 3)
test_graph.add_edge(1, 4)
test_graph.add_edge(2, 5)
test_graph.add_edge(2, 6)
test_graph.add_edge(4, 7)
test_graph.add_edge(4, 8)
test_graph.add_edge(5, 9)

return test_graph


def test_constructor():
test = Graph()
assert test.graph_dict == {}
Expand Down Expand Up @@ -56,12 +91,24 @@ def test_add_edge_second_node_new():
assert "test" in test.graph_dict[55]


def test_add_edge_with_default_weight():
test = Graph()
test.add_edge(1, 2)
assert test.graph_dict[1] == OrderedDict([(2, 1)])


def test_add_edge_with_weight_parameter():
test = Graph()
test.add_edge(1, 2, 10000)
assert test.graph_dict[1] == OrderedDict([(2, 10000)])


def test_nodes(test_graph):
assert test_graph.nodes() == ["test", 42, 5]


def test_edges(test_graph):
assert test_graph.edges() == [("test", 5), (42, "test"), (5, 42)]
assert test_graph.edges() == [("test", 5, 1), (42, "test", 1000), (5, 42, 1)]


def test_del_node(test_graph):
Expand All @@ -85,15 +132,15 @@ def test_del_edge(test_graph):


def test_del_edge_not_found(test_graph):
with pytest.raises(ValueError) as e:
with pytest.raises(KeyError) as e:
test_graph.del_edge(5, "test")
assert "Edge not found" in str(e.value)


def test_del_edge_node_not_found(test_graph):
with pytest.raises(KeyError) as e:
test_graph.del_edge(8, "test")
assert "First node not found" in str(e.value)
assert "Edge not found" in str(e.value)


def test_has_node_true(test_graph):
Expand Down Expand Up @@ -128,3 +175,43 @@ def test_adjacent_second_node_not_found(test_graph):
with pytest.raises(KeyError) as e:
test_graph.adjacent("test", 47)
assert 'Second node not found' in str(e.value)


def test_depth_first_from_node_0(test_depth_traversal_graph):
assert test_depth_traversal_graph.depth_first_traversal(0) == [
0, 1, 4, 5, 6, 7, 8, 2, 9, 3]


def test_depth_first_from_node_1(test_depth_traversal_graph):
assert test_depth_traversal_graph.depth_first_traversal(1) == [
1, 4, 5, 6, 7, 8]


def test_depth_first_no_edges():
test = Graph()
test.add_node(55)
test.add_node("test")
test.add_node(2)
assert test.depth_first_traversal(55) == [55]


def test_depth_multiple_edges():
test = Graph()
for i in range(5):
test.add_node(i)
test.add_edge(0, 1)
test.add_edge(0, 2)
test.add_edge(1, 2)
test.add_edge(1, 3)
test.add_edge(2, 3)
test.add_edge(3, 4)

assert test.depth_first_traversal(0) == [0, 1, 2, 3, 4]


def test_depth_cyclic(test_graph):
assert test_graph.depth_first_traversal(5) == [5, 42, "test"]


def test_breadth_first(test_breadth_traversal_graph):
assert test_breadth_traversal_graph.breadth_first_traversal(1) == range(1, 10)