blob: 28f7e51aad2fd33f304ecd29d99583aedf0dd503 [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';
/// Returns the shortest path from [start] to [target] given the directed
/// edges of a graph provided by [edges].
///
/// If [start] `==` [target], an empty [List] is returned and [edges] is never
/// called.
///
/// 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.
Iterable<T>? shortestPath<T extends Object>(
T start,
T target,
Iterable<T> Function(T) edges, {
bool Function(T, T)? equals,
int Function(T)? hashCode,
}) =>
_shortestPaths<T>(
start,
edges,
target: target,
equals: equals,
hashCode: hashCode,
)[target];
/// Returns a [Map] of the shortest paths from [start] to all of the nodes in
/// the directed graph defined by [edges].
///
/// All return values will contain the key [start] with an empty [List] value.
///
/// [start] and all values returned by [edges] must not be `null`.
/// If asserts are enabled, an [AssertionError] is raised if these conditions
/// are not met. If asserts are not enabled, violations result in undefined
/// behavior.
///
/// 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.
Map<T, Iterable<T>> shortestPaths<T extends Object>(
T start,
Iterable<T> Function(T) edges, {
bool Function(T, T)? equals,
int Function(T)? hashCode,
}) =>
_shortestPaths<T>(
start,
edges,
equals: equals,
hashCode: hashCode,
);
Map<T, Iterable<T>> _shortestPaths<T extends Object>(
T start,
Iterable<T> Function(T) edges, {
T? target,
bool Function(T, T)? equals,
int Function(T)? hashCode,
}) {
final distances = HashMap<T, _Tail<T>>(equals: equals, hashCode: hashCode);
distances[start] = _Tail<T>();
final nonNullEquals = equals ??= _defaultEquals;
final isTarget =
target == null ? _neverTarget : (T node) => nonNullEquals(node, target);
if (isTarget(start)) {
return distances;
}
final toVisit = ListQueue<T>()..add(start);
while (toVisit.isNotEmpty) {
final current = toVisit.removeFirst();
final currentPath = distances[current]!;
for (var edge in edges(current)) {
final existingPath = distances[edge];
if (existingPath == null) {
distances[edge] = currentPath.append(edge);
if (isTarget(edge)) {
return distances;
}
toVisit.add(edge);
}
}
}
return distances;
}
bool _defaultEquals(Object a, Object b) => a == b;
bool _neverTarget(Object _) => false;
/// An immutable iterable that can efficiently return a copy with a value
/// appended.
///
/// This implementation has an efficient [length] property.
///
/// Note that grabbing an [iterator] for the first time is O(n) in time and
/// space because it copies all the values to a new list and uses that
/// iterator in order to avoid stack overflows for large paths. This copy is
/// cached for subsequent calls.
class _Tail<T extends Object> extends Iterable<T> {
final T? tail;
final _Tail<T>? head;
@override
final int length;
_Tail()
: tail = null,
head = null,
length = 0;
_Tail._(this.tail, this.head, this.length);
_Tail<T> append(T value) => _Tail._(value, this, length + 1);
@override
Iterator<T> get iterator => _asIterable.iterator;
late final _asIterable = () {
_Tail<T>? next = this;
final reversed = List.generate(length, (_) {
final val = next!.tail;
next = next!.head;
return val as T;
});
return reversed.reversed;
}();
}