| // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| import 'dart:collection'; | |
| import 'cycle_exception.dart'; | |
| import 'strongly_connected_components.dart'; | |
| import 'topological_sort.dart'; | |
| /// Returns a transitive closure of a directed graph provided by [nodes] and | |
| /// [edges]. | |
| /// | |
| /// The result is a map from [nodes] to the sets of nodes that are transitively | |
| /// reachable through [edges]. No particular ordering is guaranteed. | |
| /// | |
| /// If [equals] is provided, it is used to compare nodes in the graph. If | |
| /// [equals] is omitted, the node's own [Object.==] is used instead. | |
| /// | |
| /// Similarly, if [hashCode] is provided, it is used to produce a hash value | |
| /// for nodes to efficiently calculate the return value. If it is omitted, the | |
| /// key's own [Object.hashCode] is used. | |
| /// | |
| /// If you supply one of [equals] or [hashCode], you should generally also to | |
| /// supply the other. | |
| /// | |
| /// Note: this requires that [nodes] and each iterable returned by [edges] | |
| /// contain no duplicate entries. | |
| /// | |
| /// By default, this can handle either cyclic or acyclic graphs. If [acyclic] is | |
| /// true, this will run more efficiently but throw a [CycleException] if the | |
| /// graph is cyclical. | |
| Map<T, Set<T>> transitiveClosure<T extends Object>( | |
| Iterable<T> nodes, | |
| Iterable<T> Function(T) edges, { | |
| bool Function(T, T)? equals, | |
| int Function(T)? hashCode, | |
| bool acyclic = false, | |
| }) { | |
| if (!acyclic) { | |
| return _cyclicTransitiveClosure( | |
| nodes, | |
| edges, | |
| equals: equals, | |
| hashCode: hashCode, | |
| ); | |
| } | |
| final topologicalOrder = topologicalSort( | |
| nodes, | |
| edges, | |
| equals: equals, | |
| hashCode: hashCode, | |
| ); | |
| final result = LinkedHashMap<T, Set<T>>(equals: equals, hashCode: hashCode); | |
| for (final node in topologicalOrder.reversed) { | |
| final closure = LinkedHashSet<T>(equals: equals, hashCode: hashCode); | |
| for (var child in edges(node)) { | |
| closure.add(child); | |
| closure.addAll(result[child]!); | |
| } | |
| result[node] = closure; | |
| } | |
| return result; | |
| } | |
| /// Returns the transitive closure of a cyclic graph using [Purdom's algorithm]. | |
| /// | |
| /// [Purdom's algorithm]: https://algowiki-project.org/en/Purdom%27s_algorithm | |
| /// | |
| /// This first computes the strongly connected components of the graph and finds | |
| /// the transitive closure of those before flattening it out into the transitive | |
| /// closure of the entire graph. | |
| Map<T, Set<T>> _cyclicTransitiveClosure<T extends Object>( | |
| Iterable<T> nodes, | |
| Iterable<T> Function(T) edges, { | |
| bool Function(T, T)? equals, | |
| int Function(T)? hashCode, | |
| }) { | |
| final components = stronglyConnectedComponents<T>( | |
| nodes, | |
| edges, | |
| equals: equals, | |
| hashCode: hashCode, | |
| ); | |
| final nodesToComponents = HashMap<T, List<T>>( | |
| equals: equals, | |
| hashCode: hashCode, | |
| ); | |
| for (final component in components) { | |
| for (final node in component) { | |
| nodesToComponents[node] = component; | |
| } | |
| } | |
| // Because [stronglyConnectedComponents] returns the components in reverse | |
| // topological order, we can avoid an additional topological sort here. | |
| // Instead, we directly traverse the component list with the knowledge that | |
| // once we reach a component, everything reachable from it has already been | |
| // registered in [result]. | |
| final result = LinkedHashMap<T, Set<T>>(equals: equals, hashCode: hashCode); | |
| for (final component in components) { | |
| final closure = LinkedHashSet<T>(equals: equals, hashCode: hashCode); | |
| if (_componentIncludesCycle(component, edges, equals)) { | |
| closure.addAll(component); | |
| } | |
| // De-duplicate downstream components to avoid adding the same transitive | |
| // children over and over. | |
| final downstreamComponents = { | |
| for (final node in component) | |
| for (final child in edges(node)) nodesToComponents[child]!, | |
| }; | |
| for (final childComponent in downstreamComponents) { | |
| if (childComponent == component) continue; | |
| // This if check is just for efficiency. If [childComponent] has multiple | |
| // nodes, `result[childComponent.first]` will contain all the nodes in | |
| // `childComponent` anyway since it's cyclical. | |
| if (childComponent.length == 1) closure.addAll(childComponent); | |
| closure.addAll(result[childComponent.first]!); | |
| } | |
| for (final node in component) { | |
| result[node] = closure; | |
| } | |
| } | |
| return result; | |
| } | |
| /// Returns whether the strongly-connected component [component] of a graph | |
| /// defined by [edges] includes a cycle. | |
| bool _componentIncludesCycle<T>( | |
| List<T> component, | |
| Iterable<T> Function(T) edges, | |
| bool Function(T, T)? equals, | |
| ) { | |
| // A strongly-connected component with more than one node always contains a | |
| // cycle, by definition. | |
| if (component.length > 1) return true; | |
| // A component with only a single node only contains a cycle if that node has | |
| // an edge to itself. | |
| final node = component.single; | |
| return edges( | |
| node, | |
| ).any((edge) => equals == null ? edge == node : equals(edge, node)); | |
| } |