def setUp(self): self.g1 = Graph() self.g1.add_nodes('a', 'b', 'c', 'd', 'e') self.g1.add_edge('a', 'b', weight=10) self.g1.add_edge('a', 'c', weight=3) self.g1.add_edge('b', 'c', weight=1) self.g1.add_edge('b', 'd', weight=2) self.g1.add_edge('c', 'b', weight=4) self.g1.add_edge('c', 'd', weight=8) self.g1.add_edge('c', 'e', weight=2) self.g1.add_edge('d', 'e', weight=7) self.g1.add_edge('e', 'd', weight=9)
def setUp(self): self.g1 = Undirected(Graph()) self.g1.add_nodes('a', 'b', 'c') self.g1.add_edge('a', 'b') self.g1.add_edge('b', 'c') self.g_directed = Graph() self.g_directed.add_nodes(1, 2, 3, 4, 5) self.g_directed.add_edge(1, 2) self.g_directed.add_edge(1, 3) self.g_directed.add_edge(3, 2) self.g_directed.add_edge(2, 3) self.g_directed.add_edge(3, 4) self.g_directed.add_edge(4, 3) self.g2 = Undirected(self.g_directed)
def topological_sort(graph: Graph, key: Callable[[Node], int] = None)\ -> List[Node]: """ Topologically sort this graph by repeatedly removing a node with no incoming edges and all of its outgoing edges and adding it to the order. Total runtime: O(|E|) + O(|V|) + O(|V|) = O(|V| + |E|). TODO: Check total runtime https://courses.cs.washington.edu/courses/cse326/03wi/lectures/RaoLect20.pdf :param graph: the graph to operate on :param key: a function of one argument used to extract a comparison key to determine which node to visit first (the "smallest" element) :return: a topological ordering of the given graph """ # TODO: Raise exception if not DAG # TODO: Implement using DFS # TODO: Implement using priority queue # the number of incoming edges for each node: O(|E|) in_degrees = {v: len(graph.parents(v)) for v in graph.nodes()} # the nodes ready to be removed: O(|V|) ready = [v for v in graph.nodes() if in_degrees[v] == 0] # the topological ordering order = [] def pop_min(l): if key: _, idx = min([(ready[i], i) for i in range(len(ready))], key=key) return l.pop(idx) else: _, idx = min((ready[i], i) for i in range(len(ready))) return l.pop(idx) # dequeue and output: O(|V|)? while ready: u = pop_min(ready) order.append(u) for v in graph.neighbors(u): in_degrees[v] -= 1 if in_degrees[v] == 0: ready.append(v) return order
def setUp(self): self.g1 = Graph() self.g1.add_nodes('u', 'a', 'b', 'c') self.g1.add_nodes('x', 'y') self.g1.add_edge('u', 'a') self.g1.add_edge('a', 'u') self.g1.add_edge('u', 'c') self.g1.add_edge('c', 'a') self.g1.add_edge('c', 'b') self.g1.add_edge('b', 'u') self.g1.add_edge('x', 'y') self.g2 = Undirected(Graph()) self.g2.add_nodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 's') self.g2.add_edge('a', 'b') self.g2.add_edge('a', 's') self.g2.add_edge('s', 'c') self.g2.add_edge('s', 'g') self.g2.add_edge('c', 'd') self.g2.add_edge('c', 'e') self.g2.add_edge('c', 'f') self.g2.add_edge('f', 'g') self.g2.add_edge('g', 'h') self.g2.add_edge('h', 'e')
def post_order(graph: Graph, v: Node) -> List[Node]: """ Compute the post-order of the given graph using a depth-first search from v. Total runtime: O(|V| + |E|) time due to DFS. :param graph: the graph to operate on :param v: the node to search from :return: a list of nodes in the order they were done being processed :raises ValueError: if v is not a defined node in graph """ if v not in graph.nodes(): raise ValueError(f'node {v} is not defined') order = [node for node in DepthFirstIterator(graph, v)] order.reverse() return order
def setUp(self): self.g_empty = Graph() self.g1 = Graph() self.g1.add_nodes('u', 'a', 'b', 'c', 'x', 'y') self.g1.add_edge('u', 'a') self.g1.add_edge('a', 'u') self.g1.add_edge('u', 'c') self.g1.add_edge('c', 'a') self.g1.add_edge('c', 'b') self.g1.add_edge('b', 'u') self.g1.add_edge('x', 'y') self.g2 = Graph() self.g2.add_nodes('a', 'b', 'c', 'd') self.g2.add_edge('a', 'b') self.g2.add_edge('a', 'd') self.g2.add_edge('b', 'd') self.g2.add_edge('c', 'd') self.g3 = Undirected(Graph()) self.g3.add_nodes('a', 'b', 'c') self.g3.add_nodes('x', 'y', 'z') self.g3.add_edge('a', 'b') self.g3.add_edge('a', 'c') self.g3.add_edge('c', 'b') self.g3.add_edge('x', 'y') self.g3.add_edge('y', 'z') self.g4 = Graph() self.g4.add_nodes('a', 'b', 'c', 'd', 'e') self.g4.add_edge('a', 'b', weight=10) self.g4.add_edge('a', 'c', weight=3) self.g4.add_edge('b', 'c', weight=1) self.g4.add_edge('b', 'd', weight=2) self.g4.add_edge('c', 'b', weight=4) self.g4.add_edge('c', 'd', weight=8) self.g4.add_edge('c', 'e', weight=2) self.g4.add_edge('d', 'e', weight=7) self.g4.add_edge('e', 'd', weight=9)
def test_copy_constructor_mutable_node(self): class A: def __init__(self, v): self.v = v obj_node = A(['mutable', 'list']) self.g1.add_node(obj_node) self.g1.add_edge('y', obj_node) g1_copy = Graph(self.g1) self.assertEqual(self.g1, g1_copy) self.assertFalse(self.g1 is g1_copy) # ensure that obj_node is updated everywhere (deep copy will not work) obj_node.v.append('new') self.assertEqual(self.g1, g1_copy) self.assertFalse(self.g1 is g1_copy)
def setUp(self): self.g_empty = Graph() self.g1 = Graph() self.g1.add_nodes('u', 'a', 'b', 'c', 'x', 'y') self.g1.add_edge('u', 'a') self.g1.add_edge('a', 'u', weight=10) self.g1.add_edge('u', 'c') self.g1.add_edge('c', 'a') self.g1.add_edge('c', 'b') self.g1.add_edge('b', 'u') self.g1.add_edge('x', 'y') self.g2 = Graph() self.g2.add_nodes('a', 'b', 'c', 'd') self.g2.add_edge('a', 'b', weight=5) self.g2.add_edge('a', 'd', weight=-2) self.g2.add_edge('b', 'd') self.g2.add_edge('c', 'd')
class TestGraphAlgorithms(unittest.TestCase): """ Tests for graph algorithms. """ def setUp(self): self.g_empty = Graph() self.g1 = Graph() self.g1.add_nodes('u', 'a', 'b', 'c', 'x', 'y') self.g1.add_edge('u', 'a') self.g1.add_edge('a', 'u') self.g1.add_edge('u', 'c') self.g1.add_edge('c', 'a') self.g1.add_edge('c', 'b') self.g1.add_edge('b', 'u') self.g1.add_edge('x', 'y') self.g2 = Graph() self.g2.add_nodes('a', 'b', 'c', 'd') self.g2.add_edge('a', 'b') self.g2.add_edge('a', 'd') self.g2.add_edge('b', 'd') self.g2.add_edge('c', 'd') self.g3 = Undirected(Graph()) self.g3.add_nodes('a', 'b', 'c') self.g3.add_nodes('x', 'y', 'z') self.g3.add_edge('a', 'b') self.g3.add_edge('a', 'c') self.g3.add_edge('c', 'b') self.g3.add_edge('x', 'y') self.g3.add_edge('y', 'z') self.g4 = Graph() self.g4.add_nodes('a', 'b', 'c', 'd', 'e') self.g4.add_edge('a', 'b', weight=10) self.g4.add_edge('a', 'c', weight=3) self.g4.add_edge('b', 'c', weight=1) self.g4.add_edge('b', 'd', weight=2) self.g4.add_edge('c', 'b', weight=4) self.g4.add_edge('c', 'd', weight=8) self.g4.add_edge('c', 'e', weight=2) self.g4.add_edge('d', 'e', weight=7) self.g4.add_edge('e', 'd', weight=9) def test_post_order(self): self.assertRaises(ValueError, post_order, self.g1, 'fake') acceptable_orders = [['a', 'b', 'c', 'u'], ['a', 'c', 'b', 'u'], ['b', 'c', 'a', 'u'], ['b', 'a', 'c', 'u'], ['c', 'a', 'b', 'u'], ['c', 'b', 'a', 'u']] actual = post_order(self.g1, 'u') self.assertTrue(actual in acceptable_orders) self.assertEqual(['y', 'x'], post_order(self.g1, 'x')) def test_topological_sort(self): self.assertEqual([], topological_sort(self.g_empty)) g2_order = topological_sort(self.g2) for u, v in self.g2.edges(): self.assertTrue(g2_order.index(u) < g2_order.index(v)) # TODO: Test key def test_count_components(self): self.assertTrue( tuple(components(self.g3)) in itertools.permutations( [{'a', 'b', 'c'}, {'x', 'y', 'z'}])) # TODO: More tests def test_shortest_path(self): self.assertEqual(['a', 'c', 'b', 'd'], shortest_path(self.g4, 'a', 'd')) def test_distance(self): self.assertEqual(9, distance(self.g4, 'a', 'd'))
class TestBreadthFirstIterator(unittest.TestCase): """ Tests for BreadthFirstIterator. """ def setUp(self): self.g1 = Graph() self.g1.add_nodes('u', 'a', 'b', 'c') self.g1.add_nodes('x', 'y') self.g1.add_edge('u', 'a') self.g1.add_edge('a', 'u') self.g1.add_edge('u', 'c') self.g1.add_edge('c', 'a') self.g1.add_edge('c', 'b') self.g1.add_edge('b', 'u') self.g1.add_edge('x', 'y') self.g2 = Undirected(Graph()) self.g2.add_nodes('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 's') self.g2.add_edge('a', 'b') self.g2.add_edge('a', 's') self.g2.add_edge('s', 'c') self.g2.add_edge('s', 'g') self.g2.add_edge('c', 'd') self.g2.add_edge('c', 'e') self.g2.add_edge('c', 'f') self.g2.add_edge('f', 'g') self.g2.add_edge('g', 'h') self.g2.add_edge('h', 'e') def test_iterator(self): g1_u = list(BreadthFirstIterator(self.g1, 'u')) g1_x = list(BreadthFirstIterator(self.g1, 'x')) self.assertEqual(['u', 'a', 'c', 'b'], g1_u) self.assertEqual(['x', 'y'], g1_x) g2_a = list(BreadthFirstIterator(self.g2, 'a')) self.assertEqual(['a', 'b', 's', 'c', 'g', 'd', 'e', 'f', 'h'], g2_a) def test_key(self): # reverse alphabetical order g1_u = list( BreadthFirstIterator(self.g1, 'u', key=lambda x: -1 * ord(x))) g2_a = list( BreadthFirstIterator(self.g2, 'a', key=lambda x: -1 * ord(x))) self.assertEqual(['u', 'c', 'a', 'b'], g1_u) self.assertEqual(['a', 's', 'b', 'g', 'c', 'h', 'f', 'e', 'd'], g2_a)
class TestDijkstraIterator(unittest.TestCase): def setUp(self): self.g1 = Graph() self.g1.add_nodes('a', 'b', 'c', 'd', 'e') self.g1.add_edge('a', 'b', weight=10) self.g1.add_edge('a', 'c', weight=3) self.g1.add_edge('b', 'c', weight=1) self.g1.add_edge('b', 'd', weight=2) self.g1.add_edge('c', 'b', weight=4) self.g1.add_edge('c', 'd', weight=8) self.g1.add_edge('c', 'e', weight=2) self.g1.add_edge('d', 'e', weight=7) self.g1.add_edge('e', 'd', weight=9) def test_iterator(self): g1_a = list(DijkstraIterator(self.g1, 'a')) self.assertEqual([('a', 0), ('c', 3), ('e', 5), ('b', 7), ('d', 9)], g1_a)
class TestGraph(unittest.TestCase): """ Tests for Graph (directed). """ def setUp(self): self.g_empty = Graph() self.g1 = Graph() self.g1.add_nodes('u', 'a', 'b', 'c', 'x', 'y') self.g1.add_edge('u', 'a') self.g1.add_edge('a', 'u', weight=10) self.g1.add_edge('u', 'c') self.g1.add_edge('c', 'a') self.g1.add_edge('c', 'b') self.g1.add_edge('b', 'u') self.g1.add_edge('x', 'y') self.g2 = Graph() self.g2.add_nodes('a', 'b', 'c', 'd') self.g2.add_edge('a', 'b', weight=5) self.g2.add_edge('a', 'd', weight=-2) self.g2.add_edge('b', 'd') self.g2.add_edge('c', 'd') def test_copy_constructor(self): g1_copy = Graph(self.g1) self.assertEqual(self.g1.nodes(), g1_copy.nodes()) self.assertEqual(self.g1.edges(), g1_copy.edges()) # changes made to g1_copy should not affect self.g1 g1_copy.add_node('new1') g1_copy.add_edge('a', 'new1') self.assertTrue('new1' in g1_copy.nodes()) self.assertTrue('new1' in g1_copy.neighbors('a')) self.assertTrue('new1' not in self.g1.nodes()) self.assertTrue('new1' not in self.g1.neighbors('a')) # changes made to self.g1 should not affect g1_copy self.g1.add_node('new2') self.g1.add_edge('b', 'new2') self.assertTrue('new2' in self.g1.nodes()) self.assertTrue('new2' in self.g1.neighbors('b')) self.assertTrue('new2' not in g1_copy.nodes()) self.assertTrue('new2' not in g1_copy.neighbors('b')) def test_copy_constructor_mutable_node(self): class A: def __init__(self, v): self.v = v obj_node = A(['mutable', 'list']) self.g1.add_node(obj_node) self.g1.add_edge('y', obj_node) g1_copy = Graph(self.g1) self.assertEqual(self.g1, g1_copy) self.assertFalse(self.g1 is g1_copy) # ensure that obj_node is updated everywhere (deep copy will not work) obj_node.v.append('new') self.assertEqual(self.g1, g1_copy) self.assertFalse(self.g1 is g1_copy) def test_weight_undefined_edge(self): self.assertRaises(ValueError, self.g2.weight, 'd', 'b') def test_weight_undefined_node(self): self.assertRaises(ValueError, self.g2.weight, 'd', 'x') self.assertRaises(ValueError, self.g2.weight, 'x', 'a') self.assertRaises(ValueError, self.g2.weight, 'x', 'y') def test_weight(self): self.assertEqual(1, self.g1.weight('u', 'a')) self.assertEqual(10, self.g1.weight('a', 'u')) self.assertEqual(5, self.g2.weight('a', 'b')) self.assertEqual(-2, self.g2.weight('a', 'd')) self.assertEqual(1, self.g2.weight('b', 'd')) self.assertEqual(1, self.g2.weight('c', 'd')) def test_parents_undefined_node(self): self.assertRaises(ValueError, self.g1.parents, 'z') def test_parents(self): self.assertEqual({'a', 'b'}, self.g1.parents('u')) self.assertEqual({'u', 'c'}, self.g1.parents('a')) self.assertEqual({'c'}, self.g1.parents('b')) self.assertEqual({'u'}, self.g1.parents('c')) self.assertEqual(set(), self.g1.parents('x')) self.assertEqual({'x'}, self.g1.parents('y')) def test_neighbors_undefined_node(self): self.assertRaises(ValueError, self.g1.neighbors, 'z') def test_neighbors(self): self.assertEqual({'a', 'c'}, self.g1.neighbors('u')) self.assertEqual({'u'}, self.g1.neighbors('a')) self.assertEqual({'u'}, self.g1.neighbors('b')) self.assertEqual({'a', 'b'}, self.g1.neighbors('c')) self.assertEqual({'y'}, self.g1.neighbors('x')) self.assertEqual(set(), self.g1.neighbors('y')) def tst_add_node_defined_node(self): self.assertRaises(ValueError, self.g1.add_node, 'u') self.assertRaises(ValueError, self.g1.add_node, 'x') self.g_empty.add_node(True) self.assertRaises(ValueError, self.g_empty.add_node, 1) # True == 1 def test_add_node(self): self.g_empty.add_node(5) self.g_empty.add_node('hello') self.g_empty.add_node(True) self.assertEqual({5, 'hello', True}, self.g_empty.nodes()) def test_add_nodes_defined_nodes(self): self.assertRaises(ValueError, self.g2.add_nodes, 'f', 'c', 'g') # no nodes should be added if add_nodes raises an exception self.assertEqual({'a', 'b', 'c', 'd'}, self.g2.nodes()) def test_add_nodes(self): onebyone = Graph() onebyone.add_node('a') onebyone.add_node('b') onebyone.add_node('c') allatonce = Graph() allatonce.add_nodes('a', 'b', 'c') self.assertEqual(onebyone.nodes(), allatonce.nodes()) # def test_add_edge_ def test_add_edge(self): self.assertRaises(ValueError, self.g_empty.add_edge, 'fake1', 'fake2') self.g_empty.add_node(1) self.g_empty.add_node(2) self.g_empty.add_edge(1, 2) self.assertEqual(self.g_empty._a_in, {1: set(), 2: {1}}) self.assertEqual(self.g_empty._a_out, {1: {2}, 2: set()}) self.assertRaises(ValueError, self.g_empty.add_edge, 1, 2) def test_add_edge_undefined_node(self): self.assertRaises(ValueError, self.g_empty.add_edge, 'hello', 'world') def test_remove_node(self): self.g1.remove_node('x') self.assertEqual({'u', 'a', 'b', 'c', 'y'}, self.g1.nodes()) self.assertRaises(ValueError, self.g1.remove_node, 'z') def test_remove_edge(self): before = self.g1.edges() self.g1.remove_edge('a', 'u') after = self.g1.edges() # reverse edge ('u', 'a') still remains self.assertEqual(after, before.difference({('a', 'u')})) self.assertRaises(ValueError, self.g1.remove_edge, 'u', 'x')
def test_copy_constructor(self): g1_copy = Graph(self.g1) self.assertEqual(self.g1.nodes(), g1_copy.nodes()) self.assertEqual(self.g1.edges(), g1_copy.edges()) # changes made to g1_copy should not affect self.g1 g1_copy.add_node('new1') g1_copy.add_edge('a', 'new1') self.assertTrue('new1' in g1_copy.nodes()) self.assertTrue('new1' in g1_copy.neighbors('a')) self.assertTrue('new1' not in self.g1.nodes()) self.assertTrue('new1' not in self.g1.neighbors('a')) # changes made to self.g1 should not affect g1_copy self.g1.add_node('new2') self.g1.add_edge('b', 'new2') self.assertTrue('new2' in self.g1.nodes()) self.assertTrue('new2' in self.g1.neighbors('b')) self.assertTrue('new2' not in g1_copy.nodes()) self.assertTrue('new2' not in g1_copy.neighbors('b'))
def test_constructor(self): self.assertEqual(Unweighted(Graph()), Unweighted()) self.assertEqual(Unweighted(Undirected()), Undirected(Unweighted()))
class TestUndirectedGraph(unittest.TestCase): """ Tests for Undirected. """ def setUp(self): self.g1 = Undirected(Graph()) self.g1.add_nodes('a', 'b', 'c') self.g1.add_edge('a', 'b') self.g1.add_edge('b', 'c') self.g_directed = Graph() self.g_directed.add_nodes(1, 2, 3, 4, 5) self.g_directed.add_edge(1, 2) self.g_directed.add_edge(1, 3) self.g_directed.add_edge(3, 2) self.g_directed.add_edge(2, 3) self.g_directed.add_edge(3, 4) self.g_directed.add_edge(4, 3) self.g2 = Undirected(self.g_directed) def test_constructor(self): self.assertEqual(Undirected(Graph()), Undirected()) def test_double_decorator(self): double_g_directed = Undirected(Undirected(self.g_directed)) double_g2 = Undirected(self.g2) self.assertEqual(self.g2, double_g_directed) self.assertEqual(self.g2, double_g2) def test_from_weighted_directed(self): # adding another reverse edge should be fine and result in same graph self.g_directed.add_edge(2, 1) g3 = Undirected(self.g_directed) self.assertEqual(self.g2, g3) # adding different weight reverse edge should create ambiguous case self.g_directed.add_edge(3, 1, weight=12) self.assertRaises(ValueError, Undirected, self.g_directed) def test_parents(self): self.assertEqual({'b'}, self.g1.parents('a')) self.assertEqual({'a', 'c'}, self.g1.parents('b')) self.assertEqual({'b'}, self.g1.parents('c')) def test_neighbors(self): self.assertEqual({'b'}, self.g1.neighbors('a')) self.assertEqual({'a', 'c'}, self.g1.neighbors('b')) self.assertEqual({'b'}, self.g1.neighbors('c')) def test_add_edge_defined_edge(self): self.assertRaises(ValueError, self.g1.add_edge, 'b', 'a') def test_remove_edge(self): before = self.g2.edges() self.g2.remove_edge(2, 3) after = self.g2.edges() # both edges existed in directed graph, both are removed self.assertEqual(after, before.difference({(2, 3), (3, 2)})) self.assertRaises(ValueError, self.g2.remove_edge, 1, 5) def test_eq_directed_edges(self): # it is implied that these directed edges exists already in self.g2 self.g_directed.add_edge(2, 1) self.g_directed.add_edge(3, 1) g2_test1 = Undirected(self.g_directed) self.assertEqual(self.g2, g2_test1) def test_eq_new_edge(self): # connecting previously disconnected nodes should make them different self.g_directed.add_edge(4, 5) g2_test2 = Undirected(self.g_directed) self.assertNotEqual(self.g2, g2_test2) def test_eq_new_node(self): # a new node should make them different self.g_directed.add_node(6) g2_test3 = Undirected(self.g_directed) self.assertNotEqual(self.g2, g2_test3)
def test_add_nodes(self): onebyone = Graph() onebyone.add_node('a') onebyone.add_node('b') onebyone.add_node('c') allatonce = Graph() allatonce.add_nodes('a', 'b', 'c') self.assertEqual(onebyone.nodes(), allatonce.nodes())