Detect cycles in a dependency graph using Tarjan's algorithm to find strongly connected components. Args: graph: A dependency graph represented as adjacency lists (node -> set of dependencies) Returns: A list of lists, where each inner list c
(graph: Dict[str, Set[str]])
| 16 | |
| 17 | |
| 18 | def detect_cycles(graph: Dict[str, Set[str]]) -> List[List[str]]: |
| 19 | """ |
| 20 | Detect cycles in a dependency graph using Tarjan's algorithm to find |
| 21 | strongly connected components. |
| 22 | |
| 23 | Args: |
| 24 | graph: A dependency graph represented as adjacency lists |
| 25 | (node -> set of dependencies) |
| 26 | |
| 27 | Returns: |
| 28 | A list of lists, where each inner list contains the nodes in a cycle |
| 29 | """ |
| 30 | # Implementation of Tarjan's algorithm |
| 31 | index_counter = [0] |
| 32 | index = {} # node -> index |
| 33 | lowlink = {} # node -> lowlink value |
| 34 | onstack = set() # nodes currently on the stack |
| 35 | stack = [] # stack of nodes |
| 36 | result = [] # list of cycles (strongly connected components) |
| 37 | |
| 38 | def strongconnect(node): |
| 39 | # Set the depth index for node |
| 40 | index[node] = index_counter[0] |
| 41 | lowlink[node] = index_counter[0] |
| 42 | index_counter[0] += 1 |
| 43 | stack.append(node) |
| 44 | onstack.add(node) |
| 45 | |
| 46 | # Consider successors |
| 47 | for successor in graph.get(node, set()): |
| 48 | if successor not in index: |
| 49 | # Successor has not yet been visited; recurse on it |
| 50 | strongconnect(successor) |
| 51 | lowlink[node] = min(lowlink[node], lowlink[successor]) |
| 52 | elif successor in onstack: |
| 53 | # Successor is on the stack and hence in the current SCC |
| 54 | lowlink[node] = min(lowlink[node], index[successor]) |
| 55 | |
| 56 | # If node is a root node, pop the stack and generate an SCC |
| 57 | if lowlink[node] == index[node]: |
| 58 | # Start a new strongly connected component |
| 59 | scc = [] |
| 60 | while True: |
| 61 | successor = stack.pop() |
| 62 | onstack.remove(successor) |
| 63 | scc.append(successor) |
| 64 | if successor == node: |
| 65 | break |
| 66 | |
| 67 | # Only include SCCs with more than one node (actual cycles) |
| 68 | if len(scc) > 1: |
| 69 | result.append(scc) |
| 70 | |
| 71 | # Visit each node |
| 72 | for node in graph: |
| 73 | if node not in index: |
| 74 | strongconnect(node) |
| 75 |
no test coverage detected