Guest User

Untitled

a guest
Mar 24th, 2018
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 3.04 KB | None | 0 0
  1. #!/usr/bin/env python
  2.  
  3.  
  4. class Node(object):
  5. """A node in a directed graph."""
  6. def __init__(self, name, outs):
  7. """Initializes a Node.
  8.  
  9. Args:
  10. name: The name of this node.
  11. outs: Nodes with an edge leading out from this node.
  12. """
  13. self.name = name
  14. self.outs = outs
  15.  
  16. def __repr__(self):
  17. return self.name
  18.  
  19.  
  20. class Graph(object):
  21. """A directed graph."""
  22. def __init__(self, nodes):
  23. """Initializes a Graph.
  24.  
  25. Args:
  26. nodes: A list of nodes in this graph.
  27. """
  28. self.nodes = nodes
  29.  
  30. def find_cycle(self):
  31. known_acyclic = set()
  32. for node in self.nodes:
  33. cycle = self.find_cycle_from_node(node, [], known_acyclic)
  34. if cycle:
  35. return cycle
  36. return None
  37.  
  38. def find_cycle_from_node(self, node, path, known_acyclic):
  39. """Finds a cycle from a given node if there is one.
  40.  
  41. Recursively searches every path through the graph, looking for a cycle.
  42.  
  43. Args:
  44. node: Current node to check for cycles.
  45. path: A list containing the acyclic path to the current node from the
  46. first node.
  47. known_acyclic: Once we know that no path from a particular node has
  48. a cycle, we can ignore any paths going through that node.
  49.  
  50. Returns:
  51. A list of nodes that make up a cycle or None if no cycle exists.
  52. The list will begin and end with the same node, i.e. [A, B, A].
  53. """
  54.  
  55. if node in known_acyclic:
  56. return None
  57.  
  58. path.append(node)
  59. if node in path[:-1]:
  60. # If path is [A, B, Y, Z, Y], return [Y, Z, Y] (ignore the A, B steps
  61. # leading to the cycle).
  62. return path[path.index(node):]
  63.  
  64. for out_node in node.outs:
  65. cycle = self.find_cycle_from_node(out_node, path, known_acyclic)
  66. if cycle:
  67. return cycle
  68.  
  69. path.pop()
  70. known_acyclic.add(node)
  71. return None
  72.  
  73.  
  74. ### UNIT TEST
  75.  
  76. import unittest
  77.  
  78. def _cycle_test(*paths):
  79. """Test find_cycle on a test graph, indicated by the list of paths.
  80.  
  81. Args:
  82. paths: A list of paths. Each path is a list of node names. For each
  83. successive two nodes in a path (X, Y), an edge exists from X to Y.
  84. """
  85. node_names = sorted({n for p in paths for n in p})
  86. nodes = {n: Node(n, []) for n in node_names}
  87. for p in paths:
  88. for i in range(len(p) - 1):
  89. nodes[p[i]].outs.append(nodes[p[i + 1]])
  90. graph = Graph([nodes[n] for n in node_names])
  91. cycle = graph.find_cycle()
  92. if cycle is None:
  93. return None
  94. return [n.name for n in cycle]
  95.  
  96. class GraphCycleTest(unittest.TestCase):
  97.  
  98. def test_cycle_detection(self):
  99. (A, B, C, D) = "ABCD"
  100. self.assertEqual(_cycle_test([A, B, A]), [A, B, A])
  101. self.assertEqual(_cycle_test([A, B, C, A]), [A, B, C, A])
  102. self.assertEqual(_cycle_test([A, C], [B, C], [D]), None)
  103. self.assertEqual(_cycle_test([A, B], [B, C, B], [C, D]), [B, C, B])
Add Comment
Please, Sign In to add comment