def components(graph: Undirected) -> List[Set[Node]]: """ Compute a tuple of sets of nodes that make up connected components in the given graph. Implemented as follows: 1. Create a set containing the nodes of the graph: O(|V|). 2. Create an empty list: O(1). 3. Perform a breadth-first search from any node in the set: O(|V| + |E|). 4. Remove the discovered nodes from the set and add them to the list as a new set: O(|V|) + O(|V|). 5. Repeat until the set is empty: O(|V|). 6. Return the list TODO: Calculate total runtime (3. and 5. are not necessarily multiplied) :param graph: the undirected graph to operate on :return: a tuple of connected components """ nodes = graph.nodes() # 1. comps = [] # 2. while nodes: # 5. # 3. discovered: Set[Node] = set( DepthFirstIterator(graph, next(iter(nodes)))) # 4. nodes.difference_update(discovered) comps.append(discovered) return comps # 6.
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 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_eq(self): g2 = Unweighted(Undirected()) g3 = Undirected(Unweighted()) g2.add_nodes(1, 2, 3) g2.add_edge(1, 3, weight=5) g2.add_edge(3, 2) g3.add_nodes(3, 2, 1) g3.add_edge(3, 2, weight=7) g3.add_edge(1, 3) self.assertEqual(g2, g3)
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_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 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')
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)
def test_constructor(self): self.assertEqual(Unweighted(Graph()), Unweighted()) self.assertEqual(Unweighted(Undirected()), Undirected(Unweighted()))
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_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_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)
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)