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

Consider adding a shortestPath helper #27

Closed
@kevmoo

Description

@kevmoo

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;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions