blob: 88281d4f6b2b0e715c411d37d647035d101e9956 [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 'dart:math' show Random;
import 'package:graphs/graphs.dart';
import 'package:test/test.dart';
import 'utils/utils.dart';
void main() {
const graph = <String, List<String>>{
'1': ['2', '5'],
'2': ['3'],
'3': ['4', '5'],
'4': ['1'],
'5': ['8'],
'6': ['7'],
};
List<String> readEdges(String key) => graph[key] ?? [];
List<X> getXValues(X key) => graph[key.value]?.map(X.new).toList() ?? [];
void singlePathTest(String from, String to, List<String>? expected) {
test('$from -> $to should be $expected (mapped)', () {
expect(
shortestPath<X>(
X(from),
X(to),
getXValues,
equals: xEquals,
hashCode: xHashCode,
)?.map((x) => x.value),
expected,
);
});
test('$from -> $to should be $expected', () {
expect(shortestPath(from, to, readEdges), expected);
});
}
void pathsTest(
String from,
Map<String, List<String>> expected,
List<String> nullPaths,
) {
test('paths from $from (mapped)', () {
final result = shortestPaths<X>(
X(from),
getXValues,
equals: xEquals,
hashCode: xHashCode,
).map((k, v) => MapEntry(k.value, v.map((x) => x.value).toList()));
expect(result, expected);
});
test('paths from $from', () {
final result = shortestPaths(from, readEdges);
expect(result, expected);
});
for (var entry in expected.entries) {
singlePathTest(from, entry.key, entry.value);
}
for (var entry in nullPaths) {
singlePathTest(from, entry, null);
}
}
pathsTest('1', {
'5': ['5'],
'3': ['2', '3'],
'8': ['5', '8'],
'1': [],
'2': ['2'],
'4': ['2', '3', '4'],
}, [
'6',
'7',
]);
pathsTest('6', {
'7': ['7'],
'6': [],
}, [
'1',
]);
pathsTest('7', {'7': []}, ['1', '6']);
pathsTest('42', {'42': []}, ['1', '6']);
test('integration test', () {
// Be deterministic in the generated graph. This test may have to be updated
// if the behavior of `Random` changes for the provided seed.
final rnd = Random(1);
const size = 1000;
final graph = HashMap<int, List<int>>();
Iterable<int>? resultForGraph() =>
shortestPath(0, size - 1, (e) => graph[e] ?? const []);
void addRandomEdge() {
final toList = graph.putIfAbsent(rnd.nextInt(size), () => <int>[]);
final toValue = rnd.nextInt(size);
if (!toList.contains(toValue)) {
toList.add(toValue);
}
}
Iterable<int>? result;
// Add edges until there is a shortest path between `0` and `size - 1`
do {
addRandomEdge();
result = resultForGraph();
} while (result == null);
expect(result, [313, 547, 91, 481, 74, 64, 439, 388, 660, 275, 999]);
var count = 0;
// Add edges until the shortest path between `0` and `size - 1` is 2 items
// Adding edges should never increase the length of the shortest path.
// Adding enough edges should reduce the length of the shortest path.
do {
expect(++count, lessThan(size * 5), reason: 'This loop should finish.');
addRandomEdge();
final previousResultLength = result!.length;
result = resultForGraph();
expect(result, hasLength(lessThanOrEqualTo(previousResultLength)));
} while (result!.length > 2);
expect(result, [275, 999]);
count = 0;
// Remove edges until there is no shortest path.
// Removing edges should never reduce the length of the shortest path.
// Removing enough edges should increase the length of the shortest path and
// eventually eliminate any path.
do {
expect(++count, lessThan(size * 5), reason: 'This loop should finish.');
final randomKey = graph.keys.elementAt(rnd.nextInt(graph.length));
final list = graph[randomKey]!;
expect(list, isNotEmpty);
list.removeAt(rnd.nextInt(list.length));
if (list.isEmpty) {
graph.remove(randomKey);
}
final previousResultLength = result!.length;
result = resultForGraph();
if (result != null) {
expect(result, hasLength(greaterThanOrEqualTo(previousResultLength)));
}
} while (result != null);
});
}