(
tuples: Iterable[Tuple[_T, _T]], allitems: Iterable[_T]
)
| 75 | |
| 76 | |
| 77 | def find_cycles( |
| 78 | tuples: Iterable[Tuple[_T, _T]], allitems: Iterable[_T] |
| 79 | ) -> Set[_T]: |
| 80 | # adapted from: |
| 81 | # https://neopythonic.blogspot.com/2009/01/detecting-cycles-in-directed-graph.html |
| 82 | |
| 83 | edges: DefaultDict[_T, Set[_T]] = util.defaultdict(set) |
| 84 | for parent, child in tuples: |
| 85 | edges[parent].add(child) |
| 86 | nodes_to_test = set(edges) |
| 87 | |
| 88 | output = set() |
| 89 | |
| 90 | # we'd like to find all nodes that are |
| 91 | # involved in cycles, so we do the full |
| 92 | # pass through the whole thing for each |
| 93 | # node in the original list. |
| 94 | |
| 95 | # we can go just through parent edge nodes. |
| 96 | # if a node is only a child and never a parent, |
| 97 | # by definition it can't be part of a cycle. same |
| 98 | # if it's not in the edges at all. |
| 99 | for node in nodes_to_test: |
| 100 | stack = [node] |
| 101 | todo = nodes_to_test.difference(stack) |
| 102 | while stack: |
| 103 | top = stack[-1] |
| 104 | for node in edges[top]: |
| 105 | if node in stack: |
| 106 | cyc = stack[stack.index(node) :] |
| 107 | todo.difference_update(cyc) |
| 108 | output.update(cyc) |
| 109 | |
| 110 | if node in todo: |
| 111 | stack.append(node) |
| 112 | todo.remove(node) |
| 113 | break |
| 114 | else: |
| 115 | stack.pop() |
| 116 | return output |
| 117 | |
| 118 | |
| 119 | def _gen_edges(edges: DefaultDict[_T, Set[_T]]) -> Set[Tuple[_T, _T]]: |
no test coverage detected