mirror of
https://github.com/dart-lang/sdk
synced 2024-09-18 20:01:19 +00:00
[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:
parent
bc9f2bdc88
commit
6b4b1a0fef
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 = [];
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue