[cfe] Reimplement topological sort

This replaces the previous implementation (and the topological sort of
classes, on which it was based) which was O(|vertices|*|edges|). The new
implementation is O(|vertices|+|edges|).

Change-Id: I4f5f1e63b4c7dd67d6f17c087724c20d4d33bf83
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/221820
Reviewed-by: Chloe Stefantsova <cstefantsova@google.com>
Commit-Queue: Johnni Winther <johnniwinther@google.com>
This commit is contained in:
Johnni Winther 2021-12-02 15:31:37 +00:00 committed by Commit Bot
parent bc9f2bdc88
commit 6b4b1a0fef
8 changed files with 687 additions and 320 deletions

View file

@ -1258,8 +1258,8 @@ severity: $severity
Graph<List<Uri>> strongGraph =
new StrongComponentGraph(libraryGraph, stronglyConnectedComponents);
List<List<List<Uri>>> componentLayers = [];
topologicalSort(strongGraph, layers: componentLayers);
List<List<List<Uri>>> componentLayers =
topologicalSort(strongGraph).layers;
List<List<Uri>> layeredComponents = [];
List<Uri> currentLayer = [];
for (List<List<Uri>> layer in componentLayers) {
@ -1493,20 +1493,6 @@ severity: $severity
/// pipeline (including backends) can assume that there are no hierarchy
/// cycles.
List<SourceClassBuilder> handleHierarchyCycles(ClassBuilder objectClass) {
// Compute the initial work list of all classes declared in this loader.
List<SourceClassBuilder> workList = <SourceClassBuilder>[];
for (LibraryBuilder library in libraryBuilders) {
if (library.loader == this) {
Iterator<Builder> members = library.iterator;
while (members.moveNext()) {
Builder member = members.current;
if (member is SourceClassBuilder) {
workList.add(member);
}
}
}
}
Set<ClassBuilder> denyListedClasses = new Set<ClassBuilder>();
for (int i = 0; i < denylistedCoreClasses.length; i++) {
denyListedClasses.add(coreLibrary.lookupLocalMember(
@ -1523,42 +1509,16 @@ severity: $severity
}
// Sort the classes topologically.
Set<SourceClassBuilder> topologicallySortedClasses =
new Set<SourceClassBuilder>();
List<SourceClassBuilder> previousWorkList;
do {
previousWorkList = workList;
workList = <SourceClassBuilder>[];
for (int i = 0; i < previousWorkList.length; i++) {
SourceClassBuilder cls = previousWorkList[i];
Map<TypeDeclarationBuilder?, TypeAliasBuilder?> directSupertypeMap =
cls.computeDirectSupertypes(objectClass);
List<TypeDeclarationBuilder?> directSupertypes =
directSupertypeMap.keys.toList();
bool allSupertypesProcessed = true;
for (int i = 0; i < directSupertypes.length; i++) {
Builder? supertype = directSupertypes[i];
if (supertype is SourceClassBuilder &&
supertype.library.loader == this &&
!topologicallySortedClasses.contains(supertype)) {
allSupertypesProcessed = false;
break;
}
}
if (allSupertypesProcessed && cls.isPatch) {
allSupertypesProcessed =
topologicallySortedClasses.contains(cls.origin);
}
if (allSupertypesProcessed) {
topologicallySortedClasses.add(cls);
checkClassSupertypes(cls, directSupertypeMap, denyListedClasses);
} else {
workList.add(cls);
}
}
} while (previousWorkList.length != workList.length);
List<SourceClassBuilder> classes = topologicallySortedClasses.toList();
List<SourceClassBuilder> classesWithCycles = previousWorkList;
_SourceClassGraph classGraph = new _SourceClassGraph(this, objectClass);
TopologicalSortResult<SourceClassBuilder> result =
topologicalSort(classGraph);
List<SourceClassBuilder> classes = result.sortedVertices;
for (SourceClassBuilder cls in classes) {
checkClassSupertypes(
cls, classGraph.directSupertypeMap[cls]!, denyListedClasses);
}
List<SourceClassBuilder> classesWithCycles = result.cyclicVertices;
// Once the work list doesn't change in size, it's either empty, or
// contains all classes with cycles.
@ -2478,3 +2438,44 @@ class SourceLoaderDataForTesting {
final MacroApplicationData macroApplicationData = new MacroApplicationData();
}
class _SourceClassGraph implements Graph<SourceClassBuilder> {
@override
final List<SourceClassBuilder> vertices = [];
final ClassBuilder _objectClass;
final Map<SourceClassBuilder, Map<TypeDeclarationBuilder?, TypeAliasBuilder?>>
directSupertypeMap = {};
final Map<SourceClassBuilder, List<SourceClassBuilder>> _supertypeMap = {};
_SourceClassGraph(SourceLoader loader, this._objectClass) {
// Compute the vertices as all classes declared in this loader.
for (LibraryBuilder library in loader.libraryBuilders) {
if (library.loader == loader) {
Iterator<Builder> members = library.iterator;
while (members.moveNext()) {
Builder member = members.current;
if (member is SourceClassBuilder && !member.isPatch) {
vertices.add(member);
}
}
}
}
}
List<SourceClassBuilder> computeSuperClasses(SourceClassBuilder cls) {
Map<TypeDeclarationBuilder?, TypeAliasBuilder?> directSupertypes =
directSupertypeMap[cls] = cls.computeDirectSupertypes(_objectClass);
List<SourceClassBuilder> superClasses = [];
for (TypeDeclarationBuilder? directSupertype in directSupertypes.keys) {
if (directSupertype is SourceClassBuilder) {
superClasses.add(directSupertype);
}
}
return superClasses;
}
@override
Iterable<SourceClassBuilder> neighborsOf(SourceClassBuilder vertex) {
return _supertypeMap[vertex] ??= computeSuperClasses(vertex);
}
}

View file

@ -37,13 +37,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// class D4 = A with B implements TAlias<C>;
// ^
@ -58,13 +51,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// mixin N1 on TAlias<A> {}
// ^
@ -100,6 +86,20 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
// - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
// class C1 extends AAlias {}

View file

@ -37,13 +37,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// class D4 = A with B implements TAlias<C>;
// ^
@ -58,13 +51,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// mixin N1 on TAlias<A> {}
// ^
@ -100,6 +86,20 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
// - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
// class C1 extends AAlias {}

View file

@ -37,13 +37,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// class D4 = A with B implements TAlias<C>;
// ^
@ -58,13 +51,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// mixin N1 on TAlias<A> {}
// ^
@ -100,6 +86,20 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
// - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
// class C1 extends AAlias {}

View file

@ -37,13 +37,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// class D4 = A with B implements TAlias<C>;
// ^
@ -58,13 +51,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// mixin N1 on TAlias<A> {}
// ^
@ -100,6 +86,20 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
// - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
// class C1 extends AAlias {}

View file

@ -37,13 +37,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:42:32: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// class D4 = A with B implements TAlias<C>;
// ^
@ -58,13 +51,6 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:46:13: Error: Can't use a typedef denoting a type variable as a constructor, nor for a static member access.
// mixin N1 on TAlias<A> {}
// ^
@ -100,6 +86,20 @@ library /*isNonNullableByDefault*/;
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:40:7: Error: The type 'TAlias' can't be mixed in.
// class D3 = A with TAlias<B>;
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:44:7: Error: The type 'TAlias' can't be mixed in.
// class D5 extends A with TAlias<B> {}
// ^
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:14:9: Context: The issue arises via this type alias.
// typedef TAlias<T> = T?;
// ^
//
// pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart:16:7: Error: The type 'AAlias' which is an alias of 'A?' can't be used as supertype because it is nullable.
// - 'A' is from 'pkg/front_end/testcases/nonfunction_type_aliases/nullable_supertypes.dart'.
// class C1 extends AAlias {}

View file

@ -148,52 +148,63 @@ class StrongComponentGraph<T> implements Graph<List<T>> {
Iterable<List<T>> get vertices => components;
}
/// Returns the non-cyclic vertices of [graph] sorted in topological order.
///
/// If [indexMap] is provided, it is filled with "index" of each vertex.
/// If [layers] is provided, it is filled with a list of the vertices for each
/// "index".
///
/// Here, the "index" of a vertex is the length of the longest path through
/// neighbors. For vertices with no neighbors, the index is 0. For any other
/// vertex, it is 1 plus max of the index of its neighbors.
List<T> topologicalSort<T>(Graph<T> graph,
{Map<T, int>? indexMap, List<List<T>>? layers}) {
List<T> workList = graph.vertices.toList();
indexMap ??= {};
List<T> topologicallySortedVertices = [];
List<T> previousWorkList;
do {
previousWorkList = workList;
workList = [];
for (int i = 0; i < previousWorkList.length; i++) {
T vertex = previousWorkList[i];
int index = 0;
bool allSupertypesProcessed = true;
for (T neighbor in graph.neighborsOf(vertex)) {
int? neighborIndex = indexMap[neighbor];
if (neighborIndex == null) {
allSupertypesProcessed = false;
break;
} else {
index = max(index, neighborIndex + 1);
}
}
if (allSupertypesProcessed) {
indexMap[vertex] = index;
topologicallySortedVertices.add(vertex);
if (layers != null) {
if (index >= layers.length) {
assert(index == layers.length);
layers.add([vertex]);
} else {
layers[index].add(vertex);
}
}
const int cyclicMarker = -1;
int _topologicalSortInternal<T>(
Graph<T> graph, TopologicalSortResult<T> result, T vertex) {
int? index = result.indexMap[vertex];
if (index == null) {
result.indexMap[vertex] = cyclicMarker;
int index = 0;
for (T neighbor in graph.neighborsOf(vertex)) {
int neighborIndex = _topologicalSortInternal(graph, result, neighbor);
if (neighborIndex == cyclicMarker) {
result.cyclicVertices.add(vertex);
return cyclicMarker;
} else {
workList.add(vertex);
index = max(index, neighborIndex + 1);
}
}
} while (previousWorkList.length != workList.length);
return topologicallySortedVertices;
result.sortedVertices.add(vertex);
if (index >= result.layers.length) {
assert(index == result.layers.length);
result.layers.add([vertex]);
} else {
result.layers[index].add(vertex);
}
return result.indexMap[vertex] = index;
}
return index;
}
/// Perform a topological sorting of the vertices in [graph], returning a
/// [TopologicalSortResult] object with the result.
TopologicalSortResult<T> topologicalSort<T>(Graph<T> graph) {
TopologicalSortResult<T> result = new TopologicalSortResult();
for (T vertex in graph.vertices) {
_topologicalSortInternal(graph, result, vertex);
}
return result;
}
/// The result of computing the [topologicalSort] on a [Graph].
class TopologicalSortResult<T> {
/// The non-cyclic vertices of the graph sorted in topological order.
final List<T> sortedVertices = [];
/// The cyclic vertices of graph, including vertices that have a path to
/// a vertex.
final List<T> cyclicVertices = [];
/// The topological index of all non-cyclic vertices of the graph.
///
/// The "topological index" of a vertex is the length of the longest path
/// through neighbors. For vertices with no neighbors, the index is 0.
/// For any other vertex, it is 1 plus max of the index of its neighbors.
final Map<T, int> indexMap = {};
/// The non-cyclic vertices in layers according to their topological index.
/// That is, `layers[i]` contain the list of vertices with index `i`.
final List<List<T>> layers = [];
}

View file

@ -8,6 +8,13 @@ import 'package:expect/expect.dart' show Expect;
import 'package:kernel/util/graph.dart';
const String A = 'A';
const String B = 'B';
const String C = 'C';
const String D = 'D';
const String E = 'E';
const String F = 'F';
class TestGraph implements Graph<String> {
final Map<String, List<String>> graph;
@ -20,185 +27,533 @@ class TestGraph implements Graph<String> {
Iterable<String> neighborsOf(String vertex) => graph[vertex]!;
}
void test(String expected, Map<String, List<String>> graph) {
List<List<String>> result = computeStrongComponents(new TestGraph(graph));
Expect.stringEquals(expected, "$result");
}
void test(
{required List<List<String>> expectedStrongComponents,
List<String> expectedSortedVertices = const [],
List<String> expectedCyclicVertices = const [],
List<List<String>> expectedLayers = const [],
List<List<List<String>>> expectedStrongLayers = const [],
required Map<String, List<String>> graphData}) {
Graph<String> graph = new TestGraph(graphData);
void checkGraph(Map<String, List<String>> graph, String startingNodeName,
List<List<String>> expectedEvaluations, List<bool> expectedSccFlags) {
List<List<String>> result = computeStrongComponents(new TestGraph(graph));
List<List<String>> expectedReversed = <List<String>>[];
for (List<String> list in expectedEvaluations) {
expectedReversed.add(list.reversed.toList());
List<List<String>> strongComponentResult = computeStrongComponents(graph);
Expect.equals(
expectedStrongComponents.length,
strongComponentResult.length,
"Unexpected strongly connected components count. "
"Expected ${expectedStrongComponents}, "
"actual ${strongComponentResult}");
for (int index = 0; index < expectedStrongComponents.length; index++) {
Expect.listEquals(
expectedStrongComponents[index],
strongComponentResult[index],
"Unexpected strongly connected components. "
"Expected $expectedStrongComponents, actual $strongComponentResult.");
}
TopologicalSortResult<String> topologicalResult = topologicalSort(graph);
Set<String> sortedAndCyclicVertices = topologicalResult.sortedVertices
.toSet()
.intersection(topologicalResult.cyclicVertices.toSet());
Expect.isTrue(sortedAndCyclicVertices.isEmpty,
"Found vertices both sorted and cyclic: $sortedAndCyclicVertices");
List<String> sortedOrCyclicVertices = [
...topologicalResult.sortedVertices,
...topologicalResult.cyclicVertices
];
Expect.equals(
graphData.length,
sortedOrCyclicVertices.length,
"Unexpected vertex count. Expected ${graphData.length}, "
"found ${sortedOrCyclicVertices.length}.");
Expect.listEquals(
expectedSortedVertices,
topologicalResult.sortedVertices,
"Unexpected sorted vertices. "
"Expected $expectedSortedVertices, "
"actual ${topologicalResult.sortedVertices}.");
Expect.listEquals(
expectedCyclicVertices,
topologicalResult.cyclicVertices,
"Unexpected cyclic vertices. "
"Expected $expectedCyclicVertices, "
"actual ${topologicalResult.cyclicVertices}.");
Expect.equals(
expectedLayers.length,
topologicalResult.layers.length,
"Unexpected topological layer count. "
"Expected ${expectedLayers}, "
"actual ${topologicalResult.layers}");
for (int index = 0; index < expectedLayers.length; index++) {
Expect.listEquals(
expectedLayers[index],
topologicalResult.layers[index],
"Unexpected topological layers. "
"Expected $expectedLayers, "
"actual ${topologicalResult.layers}.");
for (String vertex in topologicalResult.layers[index]) {
int actualIndex = topologicalResult.indexMap[vertex]!;
Expect.equals(
index,
actualIndex,
"Unexpected topological index for $vertex. "
"Expected $index, found $actualIndex.");
}
}
StrongComponentGraph<String> strongComponentGraph =
new StrongComponentGraph(graph, strongComponentResult);
TopologicalSortResult<List<String>> strongTopologicalResult =
topologicalSort(strongComponentGraph);
Expect.equals(
expectedStrongLayers.length,
strongTopologicalResult.layers.length,
"Unexpected strong topological layer count. "
"Expected ${expectedStrongLayers}, "
"actual ${strongTopologicalResult.layers}");
for (int index = 0; index < expectedStrongLayers.length; index++) {
List<List<String>> expectedStrongLayer = expectedStrongLayers[index];
List<List<String>> strongLayer = strongTopologicalResult.layers[index];
Expect.equals(
expectedStrongLayer.length,
strongLayer.length,
"Unexpected strong topological layer $index count. "
"Expected ${expectedStrongLayers}, "
"actual ${strongTopologicalResult.layers}");
for (int subIndex = 0; subIndex < expectedStrongLayer.length; subIndex++) {
Expect.listEquals(
expectedStrongLayer[subIndex],
strongLayer[subIndex],
"Unexpected strong topological layer $index. "
"Expected $expectedStrongLayer, "
"actual $strongLayer.");
}
for (List<String> vertex in strongTopologicalResult.layers[index]) {
int actualIndex = strongTopologicalResult.indexMap[vertex]!;
Expect.equals(
index,
actualIndex,
"Unexpected strong topological index for $vertex. "
"Expected $index, found $actualIndex.");
}
}
Expect.stringEquals(expectedReversed.join(", "), result.join(", "));
}
void main() {
test("[[B, A], [C], [D]]", {
"A": ["B"],
"B": ["A"],
"C": ["A"],
"D": ["C"],
});
test(graphData: {
A: [B],
B: [A],
C: [A],
D: [C],
}, expectedStrongComponents: [
[B, A],
[C],
[D]
], expectedCyclicVertices: [
B,
A,
C,
D
], expectedStrongLayers: [
[
[B, A]
],
[
[C]
],
[
[D]
]
]);
test("[]", {});
test(graphData: {}, expectedStrongComponents: []);
test("[[A], [B], [C], [D]]", {
"A": [],
"B": [],
"C": [],
"D": [],
});
test(graphData: {
A: [],
B: [],
C: [],
D: [],
}, expectedStrongComponents: [
[A],
[B],
[C],
[D]
], expectedSortedVertices: [
A,
B,
C,
D
], expectedLayers: [
[A, B, C, D]
], expectedStrongLayers: [
[
[A],
[B],
[C],
[D]
]
]);
test("[[B, A], [C], [D]]", {
"D": ["C"],
"C": ["A"],
"B": ["A"],
"A": ["B"],
});
test(graphData: {
D: [C],
C: [A],
B: [A],
A: [B],
}, expectedStrongComponents: [
[B, A],
[C],
[D]
], expectedCyclicVertices: [
B,
A,
C,
D
], expectedStrongLayers: [
[
[B, A]
],
[
[C]
],
[
[D]
]
]);
test("[[D], [C], [B], [A]]", {
"A": ["B"],
"B": ["C"],
"C": ["D"],
"D": [],
});
test(graphData: {
A: [B],
B: [C],
C: [D],
D: [],
}, expectedStrongComponents: [
[D],
[C],
[B],
[A]
], expectedSortedVertices: [
D,
C,
B,
A
], expectedLayers: [
[D],
[C],
[B],
[A]
], expectedStrongLayers: [
[
[D]
],
[
[C]
],
[
[B]
],
[
[A]
]
]);
test("[[D], [C], [B], [A]]", {
"D": [],
"C": ["D"],
"B": ["C"],
"A": ["B"],
});
test(graphData: {
D: [],
C: [D],
B: [C],
A: [B],
}, expectedStrongComponents: [
[D],
[C],
[B],
[A]
], expectedSortedVertices: [
D,
C,
B,
A
], expectedLayers: [
[D],
[C],
[B],
[A]
], expectedStrongLayers: [
[
[D]
],
[
[C]
],
[
[B]
],
[
[A]
]
]);
test("[[A], [B], [C], [D]]", {
"A": [],
"B": ["A"],
"C": ["A"],
"D": ["B", "C"],
});
test(graphData: {
A: [],
B: [A],
C: [A],
D: [B, C],
}, expectedStrongComponents: [
[A],
[B],
[C],
[D]
], expectedSortedVertices: [
A,
B,
C,
D
], expectedLayers: [
[A],
[B, C],
[D]
], expectedStrongLayers: [
[
[A]
],
[
[B],
[C]
],
[
[D]
]
]);
// Test a complex graph.
checkGraph(
{
'a': ['b', 'c'],
'b': ['c', 'd'],
'c': [],
'd': ['c', 'e'],
'e': ['b', 'f'],
'f': ['c', 'd']
},
'a',
[
['c'],
['b', 'd', 'e', 'f'],
['a']
],
[false, true, false]);
test(graphData: {
A: [B, C],
B: [C, D],
C: [],
D: [C, E],
E: [B, F],
F: [C, D]
}, expectedStrongComponents: [
[C],
[F, E, D, B],
[A],
], expectedSortedVertices: [
C
], expectedCyclicVertices: [
E,
D,
B,
A,
F
], expectedLayers: [
[C]
], expectedStrongLayers: [
[
[C]
],
[
[F, E, D, B]
],
[
[A]
]
]);
// Test a diamond-shaped graph.
checkGraph(
{
'a': ['b', 'c'],
'b': ['d'],
'c': ['d'],
'd': []
},
'a',
[
['d'],
['b'],
['c'],
['a']
],
[false, false, false, false]);
test(graphData: {
A: [B, C],
B: [D],
C: [D],
D: []
}, expectedStrongComponents: [
[D],
[B],
[C],
[A],
], expectedSortedVertices: [
D,
B,
C,
A
], expectedLayers: [
[D],
[B, C],
[A]
], expectedStrongLayers: [
[
[D]
],
[
[B],
[C]
],
[
[A]
]
]);
// Test a graph with a single node.
checkGraph(
{'a': []},
'a',
[
['a']
],
[false]);
test(graphData: {
A: []
}, expectedStrongComponents: [
[A]
], expectedSortedVertices: [
A
], expectedLayers: [
[A]
], expectedStrongLayers: [
[
[A]
]
]);
// Test a graph with a single node and a trivial cycle.
checkGraph(
{
'a': ['a']
},
'a',
[
['a']
],
[true]);
test(graphData: {
A: [A]
}, expectedStrongComponents: [
[A]
], expectedCyclicVertices: [
A
], expectedStrongLayers: [
[
[A]
]
]);
// Test a graph with three nodes with circular dependencies.
checkGraph(
{
'a': ['b'],
'b': ['c'],
'c': ['a'],
},
'a',
[
['a', 'b', 'c']
],
[true]);
test(graphData: {
A: [B],
B: [C],
C: [A],
}, expectedStrongComponents: [
[C, B, A]
], expectedCyclicVertices: [
C,
B,
A
], expectedStrongLayers: [
[
[C, B, A]
]
]);
// Test a graph A->B->C->D, where D points back to B and then C.
checkGraph(
{
'a': ['b'],
'b': ['c'],
'c': ['d'],
'd': ['b', 'c']
},
'a',
[
['b', 'c', 'd'],
['a']
],
[true, false]);
test(graphData: {
A: [B],
B: [C],
C: [D],
D: [B, C]
}, expectedStrongComponents: [
[D, C, B],
[A]
], expectedCyclicVertices: [
D,
C,
B,
A
], expectedStrongLayers: [
[
[D, C, B]
],
[
[A]
]
]);
// Test a graph A->B->C->D, where D points back to C and then B.
checkGraph(
{
'a': ['b'],
'b': ['c'],
'c': ['d'],
'd': ['c', 'b']
},
'a',
[
['b', 'c', 'd'],
['a']
],
[true, false]);
test(graphData: {
A: [B],
B: [C],
C: [D],
D: [C, B]
}, expectedStrongComponents: [
[D, C, B],
[A]
], expectedCyclicVertices: [
D,
C,
B,
A
], expectedStrongLayers: [
[
[D, C, B]
],
[
[A]
]
]);
// Test a graph with two nodes with circular dependencies.
checkGraph(
{
'a': ['b'],
'b': ['a']
},
'a',
[
['a', 'b']
],
[true]);
test(graphData: {
A: [B],
B: [A]
}, expectedStrongComponents: [
[B, A]
], expectedCyclicVertices: [
B,
A
], expectedStrongLayers: [
[
[B, A]
]
]);
// Test a graph with two nodes and a single dependency.
checkGraph(
{
'a': ['b'],
'b': []
},
'a',
[
['b'],
['a']
],
[false, false]);
test(graphData: {
A: [B],
B: []
}, expectedStrongComponents: [
[B],
[A]
], expectedSortedVertices: [
B,
A
], expectedLayers: [
[B],
[A]
], expectedStrongLayers: [
[
[B]
],
[
[A]
]
]);
test(graphData: {
A: [],
B: [A],
C: [B, D],
D: [C],
E: [A],
F: [B],
}, expectedStrongComponents: [
[A],
[B],
[D, C],
[E],
[F],
], expectedSortedVertices: [
A,
B,
E,
F,
], expectedCyclicVertices: [
D,
C,
], expectedLayers: [
[A],
[B, E],
[F],
], expectedStrongLayers: [
[
[A]
],
[
[B],
[E]
],
[
[D, C],
[F]
]
]);
}