UNPKG

@amarillion/helixgraph

Version:

A collection of graph algorithms for game development

113 lines (112 loc) 3.97 kB
import { assert } from "../assert.js"; import { Stream } from "../iterableUtils.js"; /** helper function to allow eigher single or multiple destination nodes in path finding functions */ export function toSet(value) { if (Array.isArray(value)) { return new Set(value); } else { return new Set([value]); } } /** * Utility that takes the 'prev' data from any of astar, dijkstra, or breadthFirstSearch, and turns it in a sequence of edges forming a path. * * @param {*} source start node * @param {*} dest destination node * @param {*} prev Map (node, [dir, srcNode]) - result from astar, dijkstra, or breadthFirstSearch */ export function trackbackEdges(source, dest, prev, maxIterations = 1e6) { const path = []; const isValid = trackback(source, dest, prev, (from, edge /* , to */) => { path.unshift(edge); }, maxIterations); return isValid ? path : null; } export function trackbackNodes(source, dest, prev, maxIterations = 1e6) { const path = []; const isValid = trackback(source, dest, prev, (from, edge, to) => { path.unshift(to); }, maxIterations); path.unshift(source); return isValid ? path : null; } /** * * @callback trackbackFun * @param {*} fromNode * @param {*} edge * @param {*} toNode * * Creates a path from the results of dijsktra, bfs or astar, by tracking back using a prev map. * @param {*} source starting node * @param {*} dest target node * @param {Map} prev is a Map(destNode, { edge, from }) as returned bij `dijkstra`, `astar` or `breadthFirstSearch`) * @param {trackbackFun} callback called for each step of the path from source to dest, but in reverse order * * @returns: an array of [ edge ], or `null` if there is no path possible, i.e. dest is unreachable from source. * * TODO: for some applications, better to return an array of [ 'node' ] or an array of both? */ export function trackback(source, dest, prev, callback, maxIterations = 1e6) { let current = dest; // set a maximum no of iterations to prevent infinite loop for (let i = 0; i < maxIterations; ++i) { const step = prev.get(current); if (!step) { return false; // no valid path } callback(step.from, step.edge, current); current = step.from; if (current === source) { // path finished! return true; // valid path } } assert(false, "Reached iteration limit when constructing path"); } export function shortestPathsFromSource(source, destinations, prev) { // Now backtrack from each destination to the source const result = []; for (const dest of destinations) { const path = trackbackEdges(source, dest, prev); if (path !== null) { result.push(path); } } return result; } /** all shortest paths from sources to sinks . Returns an array of arrays of steps. */ export function allShortestPaths(sources, sinks, algorithm) { const allPaths = new Map(); for (const source of sources) { const prev = algorithm(source, sinks); const paths = shortestPathsFromSource(source, sinks, prev); // note that it's possible that some source->sink paths are NOT possible. // they will be omitted from the result allPaths.set(source, paths); } return allPaths; } /** * Utility function. * given an adjacency func, find the first edge that goes from one node to another. */ export function edgeBetween(getAdjacent, from, to) { var _a; return (_a = Stream.of(getAdjacent(from)) .find(step => step[1] === to)) === null || _a === void 0 ? void 0 : _a[0]; } /** * Utility function. * given an adjacency func, find all edges that go from one node to another. */ export function edgesBetween(getAdjacent, from, to) { return Stream.of(getAdjacent(from)) .filter(step => step[1] === to) .map(step => step[0]) .collect(); }