Skip to content
This repository was archived by the owner on May 24, 2023. It is now read-only.

Consider adding a shortestPath helper #27

Closed
kevmoo opened this issue Nov 5, 2018 · 1 comment
Closed

Consider adding a shortestPath helper #27

kevmoo opened this issue Nov 5, 2018 · 1 comment

Comments

@kevmoo
Copy link
Contributor

kevmoo commented Nov 5, 2018

Here's a tested implementation.

The only downside: I use PriorityQueue from pkg:collection. You could nix that and loose a bit of perf – whatever... CC @natebosch

import 'dart:collection';

import 'package:collection/collection.dart' show PriorityQueue;

/// If [sourceNode] equals [targetNode], `true` is always returned.
///
/// Assumes that [T] implements correct and efficient `==` and `hashCode`.
List<T> shortestPath<T>(
        T sourceNode, T targetNode, Iterable<T> Function(T) edgeWeight) =>
    shortestWeightedPath(sourceNode, targetNode,
        (T node) => edgeWeight(node).map((n) => MapEntry(n, 1)))?.value;

/// The value returned from [edgeWeight] must be greater than zero. This
/// requirement is verified in an assert. If violated When running without
/// asserts, the behavior is undefined.
///
/// If [sourceNode] equals [targetNode], `true` is always returned.
///
/// Assumes that [T] implements correct and efficient `==` and `hashCode`.
MapEntry<num, List<T>> shortestWeightedPath<T>(T sourceNode, T targetNode,
    Iterable<MapEntry<T, num>> Function(T) edgeWeight) {
  if (sourceNode == targetNode) {
    return MapEntry<num, List<T>>(0, []);
  }

  final distances = HashMap<T, MapEntry<num, List<T>>>();
  distances[sourceNode] = MapEntry<num, List<T>>(0, []);

  int compareDistance(T a, T b) {
    final distanceA = distances[a]?.key ?? double.infinity;
    final distanceB = distances[b]?.key ?? double.infinity;
    return distanceA.compareTo(distanceB);
  }

  final toVisit = PriorityQueue<T>(compareDistance)..add(sourceNode);

  MapEntry<num, List<T>> bestOption;

  while (toVisit.isNotEmpty) {
    final current = toVisit.removeFirst();
    final distanceToCurrent = distances[current];

    if (bestOption != null && distanceToCurrent.key >= bestOption.key) {
      // Skip any existing `toVisit` items that have no change of being
      // better than bestOption (if it exists)
      continue;
    }

    for (var edge in edgeWeight(current)) {
      assert(edge.value > 0, 'Edge weight must be greater than zero.');
      final existingDistance = distances[edge.key];

      final newOptionDistance = distanceToCurrent.key + edge.value;

      if (existingDistance == null ||
          existingDistance.key > newOptionDistance) {
        final newOption = MapEntry<num, List<T>>(newOptionDistance,
            distanceToCurrent.value.followedBy(<T>[edge.key]).toList());

        if (edge.key == targetNode) {
          assert(bestOption == null || bestOption.key > newOptionDistance);
          bestOption = newOption;
        }

        distances[edge.key] = newOption;
        if (bestOption == null || bestOption.key > newOption.key) {
          // Only add a node to visit if it might be a better path to the
          // target node
          toVisit.add(edge.key);
        }
      }
    }
  }

  assert(bestOption == distances[targetNode]);
  return bestOption;
}
@natebosch
Copy link
Contributor

haven't looked closely at the code but I like the idea in general. I don't think there is an overly strong need to avoid a dependency on collection so I'm not super concerned about that.

kevmoo added a commit that referenced this issue Nov 6, 2018
@kevmoo kevmoo closed this as completed in 90300ee Nov 7, 2018
dcharkes pushed a commit to dart-lang/tools that referenced this issue May 23, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

2 participants