UNPKG

@rimbu/graph

Version:

Immutable Graph data structures for TypeScript

107 lines 4.43 kB
import { OptLazy } from '@rimbu/common'; import { HashSet } from '@rimbu/hashed'; import { SortedSet } from '@rimbu/sorted'; import { Stream } from '@rimbu/stream'; import { FastIteratorBase, StreamBase } from '@rimbu/stream/custom'; class GraphBreadthFirstStream extends StreamBase { constructor(node, graph, addVisitedNode) { super(); this.node = node; this.graph = graph; this.addVisitedNode = addVisitedNode; } [Symbol.iterator]() { return new DirectedGraphBreadthFirstIterable(this.node, this.graph, this.addVisitedNode); } } class DirectedGraphBreadthFirstIterable extends FastIteratorBase { constructor(node, graph, addVisitedNode) { super(); this.node = node; this.graph = graph; this.addVisitedNode = addVisitedNode; this.nextIterators = []; addVisitedNode(node); const startConnectionStream = this.graph.getConnectionStreamFrom(this.node); this.currentIterator = startConnectionStream[Symbol.iterator](); } fastNext(otherwise) { let nextConnection; while (undefined !== (nextConnection = this.currentIterator.fastNext())) { const result = nextConnection; const targetNode = result[1]; if (this.addVisitedNode(targetNode)) { const targetConnectionStream = this.graph.getConnectionStreamFrom(targetNode); this.nextIterators.push(targetConnectionStream[Symbol.iterator]()); return result; } } const nextIterator = this.nextIterators.shift(); if (undefined === nextIterator) return OptLazy(otherwise); this.currentIterator = nextIterator; return this.fastNext(otherwise); } } /** * Returns a stream of connections that can be reached in the given `graph` * starting at the given `startNode`, and using breadth-first traversal. It can * avoid loops if needed in a custom way by supplying the `addVisitedNode` function. * @param graph - the graph to traverse * @param startNode - the start node within the graph * @param addVisitedNode - a function taking the currenty traversed node, * and returning true if the node has been traversed before, or false otherwise * @example * ```ts * const g = EdgeGraphHashed.of([1, 2], [2, 3], [1, 3], [3, 4]) * const stream = traverseBreadthFirstCustom(g, 1) * console.log(stream.toArray()) * // => [[1, 2], [1, 3], [2, 3], [3, 4]] * ``` */ export function traverseBreadthFirstCustom(graph, startNode, addVisitedNode = () => true) { if (!graph.nonEmpty() || !graph.hasNode(startNode)) return Stream.empty(); return new GraphBreadthFirstStream(startNode, graph, addVisitedNode); } /** * Returns a stream of connections that can be reached in the given `graph` * starting at the given `startNode`, and using breadth-first traversal. It avoids * loops by internally placing the visited nodes in a HashSet builder. * @param graph - the graph to traverse * @param startNode - the start node within the graph * @example * ```ts * const g = EdgeGraphHashed.of([1, 2], [2, 3], [1, 3], [3, 4]) * const stream = traverseBreadthFirstHashed(g, 1) * console.log(stream.toArray()) * // => [[1, 2], [1, 3], [2, 3], [3, 4]] * ``` */ export function traverseBreadthFirstHashed(graph, startNode) { if (!graph.nonEmpty() || !graph.hasNode(startNode)) return Stream.empty(); const visitSet = HashSet.builder(); return new GraphBreadthFirstStream(startNode, graph, visitSet.add); } /** * Returns a stream of connections that can be reached in the given `graph` * starting at the given `startNode`, and using breadth-first traversal. It avoids * loops by internally placing the visited nodes in a SortedSet builder. * @param graph - the graph to traverse * @param startNode - the start node within the graph * @example * ```ts * const g = EdgeGraphHashed.of([1, 2], [2, 3], [1, 3], [3, 4]) * const stream = traverseBreadthFirstSorted(g, 1) * console.log(stream.toArray()) * // => [[1, 2], [1, 3], [2, 3], [3, 4]] * ``` */ export function traverseBreadthFirstSorted(graph, startNode) { if (!graph.nonEmpty() || !graph.hasNode(startNode)) return Stream.empty(); const visitSet = SortedSet.builder(); return new GraphBreadthFirstStream(startNode, graph, visitSet.add); } //# sourceMappingURL=traverse-breadth-first.mjs.map