blob: 831946b774c384644b08215225080dda1d5f019a [file] [log] [blame]
// Copyright (c) 2018, 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 'package:graphs/graphs.dart';
import 'package:test/test.dart';
import 'utils/utils.dart';
void main() {
group('without secondarySort', () {
group('topologically sorts a graph', () {
test('with no nodes', () {
expect(_topologicalSort({}), isEmpty);
});
test('with only one node', () {
expect(_topologicalSort({1: []}), equals([1]));
});
test('with no edges', () {
expect(
_topologicalSort({1: [], 2: [], 3: [], 4: []}),
unorderedEquals([1, 2, 3, 4]),
);
});
test('with single edges', () {
expect(
_topologicalSort({
1: [2],
2: [3],
3: [4],
4: [],
}),
equals([1, 2, 3, 4]),
);
});
test('with many edges from one node', () {
final result = _topologicalSort({
1: [2, 3, 4],
2: [],
3: [],
4: [],
});
expect(result.indexOf(1), lessThan(result.indexOf(2)));
expect(result.indexOf(1), lessThan(result.indexOf(3)));
expect(result.indexOf(1), lessThan(result.indexOf(4)));
});
test('with transitive edges', () {
final result = _topologicalSort({
1: [2, 4],
2: [],
3: [],
4: [3],
});
expect(result.indexOf(1), lessThan(result.indexOf(2)));
expect(result.indexOf(1), lessThan(result.indexOf(3)));
expect(result.indexOf(1), lessThan(result.indexOf(4)));
expect(result.indexOf(4), lessThan(result.indexOf(3)));
});
test('with diamond edges', () {
final result = _topologicalSort({
1: [2, 3],
2: [4],
3: [4],
4: [],
});
expect(result.indexOf(1), lessThan(result.indexOf(2)));
expect(result.indexOf(1), lessThan(result.indexOf(3)));
expect(result.indexOf(1), lessThan(result.indexOf(4)));
expect(result.indexOf(2), lessThan(result.indexOf(4)));
expect(result.indexOf(3), lessThan(result.indexOf(4)));
});
});
test('respects custom equality and hash functions', () {
expect(
_topologicalSort<int>(
{
0: [2],
3: [4],
5: [6],
7: [],
},
equals: (i, j) => (i ~/ 2) == (j ~/ 2),
hashCode: (i) => (i ~/ 2).hashCode,
),
equals([
0,
anyOf([2, 3]),
anyOf([4, 5]),
anyOf([6, 7]),
]),
);
});
group('throws a CycleException for a graph with', () {
test('a one-node cycle', () {
expect(
() => _topologicalSort({
1: [1],
}),
throwsCycleException([1]),
);
});
test('a multi-node cycle', () {
expect(
() => _topologicalSort({
1: [2],
2: [3],
3: [4],
4: [1],
}),
throwsCycleException([4, 1, 2, 3]),
);
});
});
});
group('with secondarySort', () {
group('topologically sorts a graph', () {
test('with no nodes', () {
expect(_topologicalSort({}, secondarySort: true), isEmpty);
});
test('with only one node', () {
expect(_topologicalSort({1: []}, secondarySort: true), equals([1]));
});
test('with no edges', () {
expect(
_topologicalSort({1: [], 2: [], 3: [], 4: []}, secondarySort: true),
unorderedEquals([1, 2, 3, 4]),
);
});
test('with single edges', () {
expect(
_topologicalSort(
{
1: [2],
2: [3],
3: [4],
4: [],
},
secondarySort: true,
),
equals([1, 2, 3, 4]),
);
});
test('with many edges from one node', () {
final result = _topologicalSort(
{
1: [2, 3, 4],
2: [],
3: [],
4: [],
},
secondarySort: true,
);
expect(result.indexOf(1), lessThan(result.indexOf(2)));
expect(result.indexOf(1), lessThan(result.indexOf(3)));
expect(result.indexOf(1), lessThan(result.indexOf(4)));
});
test('with transitive edges', () {
final result = _topologicalSort(
{
1: [2, 4],
2: [],
3: [],
4: [3],
},
secondarySort: true,
);
expect(result.indexOf(1), lessThan(result.indexOf(2)));
expect(result.indexOf(1), lessThan(result.indexOf(3)));
expect(result.indexOf(1), lessThan(result.indexOf(4)));
expect(result.indexOf(4), lessThan(result.indexOf(3)));
});
test('with diamond edges', () {
final result = _topologicalSort(
{
1: [2, 3],
2: [4],
3: [4],
4: [],
},
secondarySort: true,
);
expect(result.indexOf(1), lessThan(result.indexOf(2)));
expect(result.indexOf(1), lessThan(result.indexOf(3)));
expect(result.indexOf(1), lessThan(result.indexOf(4)));
expect(result.indexOf(2), lessThan(result.indexOf(4)));
expect(result.indexOf(3), lessThan(result.indexOf(4)));
});
});
group('lexically sorts a graph where possible', () {
test('with no edges', () {
final result =
_topologicalSort({4: [], 3: [], 1: [], 2: []}, secondarySort: true);
expect(result, equals([1, 2, 3, 4]));
});
test('with one non-lexical edge', () {
final result = _topologicalSort(
{
4: [],
3: [1],
1: [],
2: [],
},
secondarySort: true,
);
expect(
result,
equals(
anyOf([
[2, 3, 1, 4],
[3, 1, 2, 4],
]),
),
);
});
test('with a non-lexical topolgical order', () {
final result = _topologicalSort(
{
4: [3],
3: [2],
2: [1],
1: [],
},
secondarySort: true,
);
expect(result, equals([4, 3, 2, 1]));
});
group('with multiple layers', () {
test('in lexical order', () {
final result = _topologicalSort(
{
1: [2],
2: [3],
3: [],
4: [5],
5: [6],
6: [],
},
secondarySort: true,
);
expect(result, equals([1, 2, 3, 4, 5, 6]));
});
test('in non-lexical order', () {
final result = _topologicalSort(
{
1: [3],
3: [5],
4: [2],
2: [6],
5: [],
6: [],
},
secondarySort: true,
);
expect(
result,
anyOf([
equals([1, 3, 4, 2, 5, 6]),
equals([1, 4, 2, 3, 5, 6]),
]),
);
});
});
});
test('respects custom equality and hash functions', () {
expect(
_topologicalSort<int>(
{
0: [2],
3: [4],
5: [6],
7: [],
},
equals: (i, j) => (i ~/ 2) == (j ~/ 2),
hashCode: (i) => (i ~/ 2).hashCode,
secondarySort: true,
),
equals([
0,
anyOf([2, 3]),
anyOf([4, 5]),
anyOf([6, 7]),
]),
);
});
group('throws a CycleException for a graph with', () {
test('a one-node cycle', () {
expect(
() => _topologicalSort(
{
1: [1],
},
secondarySort: true,
),
throwsCycleException([1]),
);
});
test('a multi-node cycle', () {
expect(
() => _topologicalSort(
{
1: [2],
2: [3],
3: [4],
4: [1],
},
secondarySort: true,
),
throwsCycleException([4, 1, 2, 3]),
);
});
});
});
}
/// Runs a topological sort on a graph represented a map from keys to edges.
List<T> _topologicalSort<T>(
Map<T, List<T>> graph, {
bool Function(T, T)? equals,
int Function(T)? hashCode,
bool secondarySort = false,
}) {
if (equals != null) {
graph = LinkedHashMap(equals: equals, hashCode: hashCode)..addAll(graph);
}
return topologicalSort(
graph.keys,
(node) {
expect(graph, contains(node));
return graph[node]!;
},
equals: equals,
hashCode: hashCode,
secondarySort:
secondarySort ? (a, b) => (a as Comparable<T>).compareTo(b) : null,
);
}