From c0122316d16f0d4ba7c45d5a8089e4658dd36beb Mon Sep 17 00:00:00 2001 From: henrykh Date: Thu, 19 Feb 2015 14:57:38 -0800 Subject: [PATCH 01/20] 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 02/20] 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 03/20] 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 04/20] 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 05/20] 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 06/20] 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] From a9b6ea63d4c6781ae3e66f3dc1eb269ca674dbb9 Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Mon, 2 Mar 2015 13:41:08 -0800 Subject: [PATCH 07/20] changed add_node to dictionary. add_edge now has weight --- graph/simple_graph.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 2cc2f47..6aeede8 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -2,6 +2,8 @@ from queue import Queue +W_DEFAULT = 1 + class Graph(object): """Implements a graph data structure""" @@ -20,14 +22,14 @@ 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): + 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.graph_dict[node_1][node_2] = weight except KeyError: self.add_node(node_1) self.graph_dict[node_1].append(node_2) From b1f0eddc9df6e3f8f9ef5e9f8ab2b6141c4e7d9d Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Mon, 2 Mar 2015 13:54:26 -0800 Subject: [PATCH 08/20] changed the del_node method to delete a dict, same with del_edge --- graph/simple_graph.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 6aeede8..af95985 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -40,20 +40,19 @@ 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""" From 5e0604c4d21cab5c9eb903585b43399fb7cf4a70 Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Mon, 2 Mar 2015 13:55:47 -0800 Subject: [PATCH 09/20] linting --- graph/simple_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index af95985..7d12204 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -4,6 +4,7 @@ W_DEFAULT = 1 + class Graph(object): """Implements a graph data structure""" @@ -52,7 +53,6 @@ def del_edge(self, node_1, node_2): del self.graph_dict[node_1][node_2] except KeyError: raise KeyError("Edge not found") - def has_node(self, node): """True if node 'n' is contained in the graph, False if not""" From bc25ce7731f6ae1c82a3462ed31ba84234b12a62 Mon Sep 17 00:00:00 2001 From: poolbath1 Date: Mon, 2 Mar 2015 14:15:07 -0800 Subject: [PATCH 10/20] changing tests to use dicts --- graph/simple_graph.py | 4 ++-- graph/test_simple_graph.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 7d12204..ca7ad43 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -33,7 +33,7 @@ def add_edge(self, node_1, node_2, weight=W_DEFAULT): self.graph_dict[node_1][node_2] = weight except KeyError: self.add_node(node_1) - self.graph_dict[node_1].append(node_2) + self.graph_dict[node_1][node_2] = weight if node_2 not in self.nodes(): self.add_node(node_2) @@ -59,7 +59,7 @@ def has_node(self, node): return node in self.graph_dict def neighbors(self, node): - """return the list of all nodes connected to 'n' by edges""" + """return the dict of all nodes connected to 'n' by edges""" try: return self.graph_dict[node] except KeyError: diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index 2c7e24a..2c0916d 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -120,7 +120,7 @@ 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) @@ -128,7 +128,7 @@ def test_del_edge_not_found(test_graph): 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): @@ -140,7 +140,7 @@ def test_has_node_false(test_graph): def test_neighbors(test_graph): - assert test_graph.neighbors(5) == [42] + assert test_graph.neighbors(5) == {42: 1} def test_neighbors_not_found(test_graph): From 241ad51c0d2337e61ff332669bf8935ff8daa460 Mon Sep 17 00:00:00 2001 From: henrykh Date: Mon, 2 Mar 2015 14:38:31 -0800 Subject: [PATCH 11/20] converts edge dict to ordered dict to maintain predictable order in traversals --- graph/simple_graph.py | 2 +- graph/test_simple_graph.py | 49 +++----------------------------------- 2 files changed, 4 insertions(+), 47 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index ca7ad43..ccc87e5 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -23,7 +23,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, OrderedDict()) 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 diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index 2c0916d..b4be4f6 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -1,6 +1,6 @@ import pytest from simple_graph import Graph - +from collections import OrderedDict @pytest.fixture(scope="function") def test_graph(): @@ -140,7 +140,7 @@ def test_has_node_false(test_graph): def test_neighbors(test_graph): - assert test_graph.neighbors(5) == {42: 1} + assert test_graph.neighbors(5) == OrderedDict([(42, 1)]) def test_neighbors_not_found(test_graph): @@ -197,52 +197,9 @@ 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)) - - -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) - - -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] + assert test_breadth_traversal_graph.breadth_first_traversal(1) == range(1, 10) From 214c882eafa7a5ddefde92eaea6d391fbadd26f2 Mon Sep 17 00:00:00 2001 From: henrykh Date: Mon, 2 Mar 2015 14:47:37 -0800 Subject: [PATCH 12/20] adds travis and updates readme with weighted graph --- .travis.yml | 4 ++++ README.md | 5 +++++ 2 files changed, 9 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6eebfa7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,4 @@ +language: python +python: + - "2.7" +install: pip install -r requirements.txt \ No newline at end of file diff --git a/README.md b/README.md index 2332352..916c790 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ This repository holds sample code for a number of classic data structures implem - 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) @@ -35,6 +37,9 @@ This repository holds sample code for a number of classic data structures implem Pytest - for testing structure functions +Travis CI +[![Build Status](https://travis-ci.org/henrykh/data-structures.svg)](https://travis-ci.org/henrykh/data-structures) + ##Collaborators Joel Stanner From 58ae7b93efa25d3c8e6f432cdb53398f900f30c9 Mon Sep 17 00:00:00 2001 From: henrykh Date: Mon, 2 Mar 2015 14:48:23 -0800 Subject: [PATCH 13/20] edit travis yml --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6eebfa7..cd82378 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python python: - "2.7" -install: pip install -r requirements.txt \ No newline at end of file +install: pip install -r requirements.txt +script: py.test \ No newline at end of file From 9cf442e27d2b76be80bcf9f65f2f1a1d480088e6 Mon Sep 17 00:00:00 2001 From: henrykh Date: Mon, 2 Mar 2015 14:50:56 -0800 Subject: [PATCH 14/20] removes pip install requirements from travis, edits readme --- .travis.yml | 1 - README.md | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd82378..38e8f2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: python python: - "2.7" -install: pip install -r requirements.txt script: py.test \ No newline at end of file diff --git a/README.md b/README.md index 916c790..8f49e76 100644 --- a/README.md +++ b/README.md @@ -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. @@ -38,7 +40,6 @@ This repository holds sample code for a number of classic data structures implem Pytest - for testing structure functions Travis CI -[![Build Status](https://travis-ci.org/henrykh/data-structures.svg)](https://travis-ci.org/henrykh/data-structures) ##Collaborators From 5a65d5b9053fa9165522ece4d92e879b5f57e35d Mon Sep 17 00:00:00 2001 From: henrykh Date: Mon, 2 Mar 2015 14:59:13 -0800 Subject: [PATCH 15/20] adds tests for adding edges --- graph/test_simple_graph.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index b4be4f6..d1ed51e 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -203,3 +203,15 @@ def test_depth_cyclic(test_graph): def test_breadth_first(test_breadth_traversal_graph): assert test_breadth_traversal_graph.breadth_first_traversal(1) == range(1, 10) + + +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)]) From c10bfe40ad5f2162edde1827219c8be43ea91f3e Mon Sep 17 00:00:00 2001 From: henrykh Date: Mon, 2 Mar 2015 15:27:10 -0800 Subject: [PATCH 16/20] edges method gives edge weights --- graph/simple_graph.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index ccc87e5..86e1735 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -1,5 +1,5 @@ from collections import OrderedDict -from queue import Queue +# from queue import Queue W_DEFAULT = 1 @@ -18,8 +18,8 @@ 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.items()] def add_node(self, node): """add a new node 'n' to the graph""" @@ -95,25 +95,25 @@ 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() - explored.setdefault(start, 1) + # 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) + # 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() - except KeyError: - raise KeyError("Node does not exist") + # return explored.keys() + # except KeyError: + # raise KeyError("Node does not exist") From 0bd6e10605c5aabe068d55c4f36eae97e0f7c535 Mon Sep 17 00:00:00 2001 From: henrykh Date: Mon, 2 Mar 2015 15:29:38 -0800 Subject: [PATCH 17/20] updates test for edges method --- graph/simple_graph.py | 38 +++++++++++++++++++------------------- graph/test_simple_graph.py | 28 ++++++++++++++-------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 86e1735..85a796f 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -1,5 +1,5 @@ from collections import OrderedDict -# from queue import Queue +from queue import Queue W_DEFAULT = 1 @@ -95,25 +95,25 @@ 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() - # explored.setdefault(start, 1) + 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) + 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() - # except KeyError: - # raise KeyError("Node does not exist") + 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 d1ed51e..f661639 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -9,7 +9,7 @@ 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 @@ -91,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): @@ -203,15 +215,3 @@ def test_depth_cyclic(test_graph): def test_breadth_first(test_breadth_traversal_graph): assert test_breadth_traversal_graph.breadth_first_traversal(1) == range(1, 10) - - -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)]) From 47e5f871d7ce9ee5997ba4df4c6602b5c54bab79 Mon Sep 17 00:00:00 2001 From: henrykh Date: Tue, 3 Mar 2015 09:52:21 -0800 Subject: [PATCH 18/20] updates add edge, neighbors now returns a list --- graph/simple_graph.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index 85a796f..b6eb087 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -30,12 +30,11 @@ def add_edge(self, node_1, node_2, weight=W_DEFAULT): or n2 are not already present in the graph, they are added. """ try: + 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][node_2] = weight - if node_2 not in self.nodes(): - self.add_node(node_2) def del_node(self, node): """delete the node 'n' from the graph""" @@ -59,9 +58,9 @@ def has_node(self, node): return node in self.graph_dict def neighbors(self, node): - """return the dict of all nodes connected to 'n' by edges""" + """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") From 31a5339f5ddf5c47582eca724e6f9d303eb3d43d Mon Sep 17 00:00:00 2001 From: henrykh Date: Tue, 3 Mar 2015 16:26:27 -0800 Subject: [PATCH 19/20] fixes neighbors test to check a list --- graph/test_simple_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/test_simple_graph.py b/graph/test_simple_graph.py index f661639..2bb61f0 100644 --- a/graph/test_simple_graph.py +++ b/graph/test_simple_graph.py @@ -152,7 +152,7 @@ def test_has_node_false(test_graph): def test_neighbors(test_graph): - assert test_graph.neighbors(5) == OrderedDict([(42, 1)]) + assert test_graph.neighbors(5) == [42] def test_neighbors_not_found(test_graph): From 00b6a7d14613f4b2c23951c87c8505dc55fb912f Mon Sep 17 00:00:00 2001 From: henrykh Date: Wed, 4 Mar 2015 16:48:34 -0800 Subject: [PATCH 20/20] adds iteritems to edge_dict in edges() --- graph/simple_graph.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/graph/simple_graph.py b/graph/simple_graph.py index b6eb087..1dd5df9 100644 --- a/graph/simple_graph.py +++ b/graph/simple_graph.py @@ -19,7 +19,8 @@ def edges(self): """return a list of all edges in the graph""" return [(key, node, weight) for key, edge_dict in - self.graph_dict.iteritems() for node, weight in edge_dict.items()] + self.graph_dict.iteritems() + for node, weight in edge_dict.iteritems()] def add_node(self, node): """add a new node 'n' to the graph"""