From c6dea0a68d002180bfd71f67a32e93e564aa4b0b Mon Sep 17 00:00:00 2001 From: henrykh Date: Tue, 17 Feb 2015 21:39:02 -0800 Subject: [PATCH 1/8] replaces nested loop in edges() with list comprehension --- graph/simple_graph.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 3895a25..742dca3 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -8,13 +8,8 @@ def nodes(self): return self.graph_dict.keys() def edges(self): - edge_list = [] - for key, value in self.graph_dict.items(): - for item in value: - edge_list.append((key, item)) - return edge_list - - # return [(key, value) for key, value in self.graph_dict.items()] + return [(key, node) for key, value in + self.graph_dict.iteritems() for node in value] def add_node(self, node): self.graph_dict[node] = [] From 27b7e0af847a2728644cfb1ba643f25bcfa41f0e Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Thu, 19 Feb 2015 13:42:38 -0800 Subject: [PATCH 2/8] fixed bug with add_node if node was already present --- graph/simple_graph.py | 2 +- graph/test_simple_graph.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 7aab4c7..7aee42c 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -16,7 +16,7 @@ def edges(self): def add_node(self, node): """add a new node 'n' to the graph""" - self.graph_dict[node] = [] + self.graph_dict.setdefault(node,[]) def add_edge(self, node_1, node_2): """add a new edge to the graph connecting 'n1' and 'n2', if either n1 diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index e611354..01bbb10 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -26,6 +26,11 @@ def test_add_node(): assert 5 in test.graph_dict +def test_add_node_if_already_present(test_graph): + test_graph.add_node(5) + assert 42 in test_graph.graph_dict[5] + + def test_add_edge(): test = Graph() test.add_node(5) From c0122316d16f0d4ba7c45d5a8089e4658dd36beb Mon Sep 17 00:00:00 2001 From: henrykh Date: Thu, 19 Feb 2015 14:57:38 -0800 Subject: [PATCH 3/8] first draft of depth first traversal --- graph/simple_graph.py | 15 +++++++++++++++ graph/test_simple_graph.py | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 7aee42c..058f324 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -1,3 +1,5 @@ +from collections import OrderedDict + class Graph(object): """Implements a graph data structure""" @@ -69,3 +71,16 @@ 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): + explored = OrderedDict() + return self._depth_first_traversal(start, explored) + + def _depth_first_traversal(self, start, explored): + 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() diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index 01bbb10..87447f5 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -15,6 +15,23 @@ def test_graph(): return test_graph +@pytest.fixture(scope="function") +def test_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 + def test_constructor(): test = Graph() assert test.graph_dict == {} @@ -128,3 +145,8 @@ 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(test_traversal_graph): + assert test_traversal_graph.depth_first_traversal(0) == [ + 0, 1, 4, 5, 6, 7, 8, 2, 9, 3] From a0ccc2971940f55bd3ea14548d24b3926836c2ce Mon Sep 17 00:00:00 2001 From: henrykh Date: Thu, 19 Feb 2015 15:11:25 -0800 Subject: [PATCH 4/8] adds test for unconnected graph and cyclic graph --- graph/test_simple_graph.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index 87447f5..ffd501a 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -147,6 +147,37 @@ def test_adjacent_second_node_not_found(test_graph): assert 'Second node not found' in str(e.value) -def test_depth_first(test_traversal_graph): +def test_depth_first_from_node_0(test_traversal_graph): assert test_traversal_graph.depth_first_traversal(0) == [ 0, 1, 4, 5, 6, 7, 8, 2, 9, 3] + + +def test_depth_first_from_node_1(test_traversal_graph): + assert test_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"] From 67a1b26debe453667407e06ef2bb277e24819a78 Mon Sep 17 00:00:00 2001 From: henrykh Date: Thu, 19 Feb 2015 15:48:47 -0800 Subject: [PATCH 5/8] adds first draft of breadth first traversal --- graph/simple_graph.py | 85 ++++++++++++++++++++++++++++++++++++++ graph/test_simple_graph.py | 36 +++++++++++++--- 2 files changed, 116 insertions(+), 5 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 058f324..0d82610 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -1,4 +1,6 @@ from collections import OrderedDict +# import Queue + class Graph(object): """Implements a graph data structure""" @@ -84,3 +86,86 @@ def _depth_first_traversal(self, start, explored): self._depth_first_traversal(child, explored) return explored.keys() + + def breadth_first_traversal(self, start): + 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() + + + + + + + +class Queue(object): + """Implement a queue data structure""" + def __init__(self): + self.front = None + self.back = None + + def __iter__(self): + current = self.front + while current: + yield current + current = current.next_item + + def enqueue(self, val): + """Take item value, add to the back of the queue""" + new_item = Item(val) + try: + self.back.next_item = new_item + except AttributeError: + self.front = new_item + self.back = new_item + + def dequeue(self): + """Remove the front item from the queue and return its value""" + + prevFront = self.front + try: + self.front = self.front.next_item + if self.front is None: + self.back = self.front + except AttributeError: + raise AttributeError("The queue is empty") + return prevFront.val + + def size(self): + count = 0 + for _ in self: + count += 1 + + return count + + +class Item(object): + + def __init__(self, val, next_item=None): + self.val = val + self.next_item = next_item + + # def _breadth_first_traversal(self, start, explored): + # 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() + + + + diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index ffd501a..3dba5b9 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -16,7 +16,7 @@ def test_graph(): @pytest.fixture(scope="function") -def test_traversal_graph(): +def test_depth_traversal_graph(): test_graph = Graph() for i in range(10): test_graph.add_node(i) @@ -32,6 +32,24 @@ def test_traversal_graph(): 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 == {} @@ -147,13 +165,13 @@ def test_adjacent_second_node_not_found(test_graph): assert 'Second node not found' in str(e.value) -def test_depth_first_from_node_0(test_traversal_graph): - assert test_traversal_graph.depth_first_traversal(0) == [ +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_traversal_graph): - assert test_traversal_graph.depth_first_traversal(1) == [ +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] @@ -181,3 +199,11 @@ def test_depth_multiple_edges(): 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) + + + + From fa2602a5e082a57351509b7cacdaacff8f443496 Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Thu, 19 Feb 2015 16:00:11 -0800 Subject: [PATCH 6/8] importing Queue --- __init__.py | 0 graph/__init__.py | 0 graph/simple_graph.py | 71 ++----------------------------------------- 3 files changed, 2 insertions(+), 69 deletions(-) create mode 100644 __init__.py create mode 100644 graph/__init__.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graph/__init__.py b/graph/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 0d82610..ce43d4c 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -1,6 +1,5 @@ from collections import OrderedDict -# import Queue - +from queue import Queue class Graph(object): """Implements a graph data structure""" @@ -20,7 +19,7 @@ def edges(self): def add_node(self, node): """add a new node 'n' to the graph""" - self.graph_dict.setdefault(node,[]) + self.graph_dict.setdefault(node, []) def add_edge(self, node_1, node_2): """add a new edge to the graph connecting 'n1' and 'n2', if either n1 @@ -103,69 +102,3 @@ def breadth_first_traversal(self, start): queue.enqueue(child) return explored.keys() - - - - - - - -class Queue(object): - """Implement a queue data structure""" - def __init__(self): - self.front = None - self.back = None - - def __iter__(self): - current = self.front - while current: - yield current - current = current.next_item - - def enqueue(self, val): - """Take item value, add to the back of the queue""" - new_item = Item(val) - try: - self.back.next_item = new_item - except AttributeError: - self.front = new_item - self.back = new_item - - def dequeue(self): - """Remove the front item from the queue and return its value""" - - prevFront = self.front - try: - self.front = self.front.next_item - if self.front is None: - self.back = self.front - except AttributeError: - raise AttributeError("The queue is empty") - return prevFront.val - - def size(self): - count = 0 - for _ in self: - count += 1 - - return count - - -class Item(object): - - def __init__(self, val, next_item=None): - self.val = val - self.next_item = next_item - - # def _breadth_first_traversal(self, start, explored): - # 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() - - - - From 397dd51ebd2bb822a0f602f9a9c2bde08b08b040 Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Thu, 19 Feb 2015 16:13:49 -0800 Subject: [PATCH 7/8] added exception handling to breadth_first, and tests --- graph/simple_graph.py | 26 +++++++++++++++----------- graph/test_simple_graph.py | 6 ++++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index ce43d4c..985ef31 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -1,6 +1,7 @@ from collections import OrderedDict from queue import Queue + class Graph(object): """Implements a graph data structure""" @@ -87,18 +88,21 @@ def _depth_first_traversal(self, start, explored): return explored.keys() def breadth_first_traversal(self, start): - explored = OrderedDict() - queue = Queue() - explored.setdefault(start, 1) + try: + explored = OrderedDict() + queue = Queue() + explored.setdefault(start, 1) - queue.enqueue(start) + queue.enqueue(start) - while queue.size(): - node = queue.dequeue() + 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) + for child in self.graph_dict[node]: + if child not in explored: + explored.setdefault(child, 1) + queue.enqueue(child) - return explored.keys() + return explored.keys() + except KeyError: + raise KeyError("Node does not exist") diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index 3dba5b9..301413e 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -205,5 +205,11 @@ def test_breadth_first(test_breadth_traversal_graph): assert test_breadth_traversal_graph.breadth_first_traversal(1) == range(1, 10) +def test_breadth_first_from_node_3(test_breadth_traversal_graph): + assert test_breadth_traversal_graph.breadth_first_traversal(3) == [3] +def test_breadth_first_from_non_existent_node(test_breadth_traversal_graph): + with pytest.raises(KeyError) as e: + test_breadth_traversal_graph.breadth_first_traversal(11) + assert "Node does not exist" in str(e.value) From 15c45ade1531106f9bda214f0ed621ad1e8f584e Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Thu, 19 Feb 2015 16:38:03 -0800 Subject: [PATCH 8/8] Docstrings added, README.md updated --- README.md | 2 ++ graph/simple_graph.py | 14 ++++++++++++-- graph/test_simple_graph.py | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ccf9f80..2332352 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ 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. ## Resources [Linked Lists Wiki](http://en.wikipedia.org/wiki/Linked_list) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 985ef31..2cc2f47 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -75,10 +75,17 @@ def adjacent(self, node_1, node_2): raise KeyError("First node not found") def depth_first_traversal(self, start): - explored = OrderedDict() - return self._depth_first_traversal(start, explored) + """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]: @@ -88,6 +95,9 @@ def _depth_first_traversal(self, start, 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() diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index 301413e..2c7e24a 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -197,12 +197,19 @@ def test_depth_multiple_edges(): assert test.depth_first_traversal(0) == [0, 1, 2, 3, 4] +def test_depth_first_from_non_existent_node(test_depth_traversal_graph): + with pytest.raises(KeyError) as e: + test_depth_traversal_graph.depth_first_traversal(11) + assert "Node does not exist" in str(e.value) + + 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) + assert ( + test_breadth_traversal_graph.breadth_first_traversal(1) == range(1, 10)) def test_breadth_first_from_node_3(test_breadth_traversal_graph): @@ -213,3 +220,29 @@ def test_breadth_first_from_non_existent_node(test_breadth_traversal_graph): with pytest.raises(KeyError) as e: test_breadth_traversal_graph.breadth_first_traversal(11) assert "Node does not exist" in str(e.value) + + +def test_breadth_cyclic(test_graph): + assert test_graph.breadth_first_traversal(5) == [5, 42, "test"] + + +def test_breadth_first_no_edges(): + test = Graph() + test.add_node(55) + test.add_node("test") + test.add_node(2) + assert test.breadth_first_traversal(55) == [55] + + +def test_breadth_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.breadth_first_traversal(0) == [0, 1, 2, 3, 4]