"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Graph = void 0; /** * A directed graph data structure supporting vertex/edge management and graph traversal algorithms * including breadth-first search, reverse BFS, and shortest path computation. * * @typeParam VMetadata - The type of metadata stored on vertices * @typeParam EMetadata - The type of metadata stored on edges */ class Graph { constructor() { this.vertices = []; } /** * Serializes the graph to a JSON string for debugging. * @param metadataRepr - Optional function to transform metadata values before serialization * @returns A pretty-printed JSON string of the graph structure */ dump(metadataRepr = (a) => a) { const seen = new WeakSet(); return JSON.stringify(this.vertices, (k, v) => { if (k === 'metadata') return metadataRepr(v); if (k === 'from') return metadataRepr(v.metadata); if (k === 'to') return metadataRepr(v.metadata); return v; }, 2); } /** * Adds a new vertex to the graph, optionally connecting it to existing vertices via edges. * @param metadata - The metadata to attach to the new vertex * @param fromEdges - Edges pointing from existing vertices to this new vertex * @param toEdges - Edges pointing from this new vertex to existing vertices * @returns The newly created vertex */ addVertex(metadata, fromEdges, toEdges) { const vertex = { metadata, edges: [], }; for (let edge of fromEdges) { const vEdge = { metadata: edge.metadata, from: edge.from, to: vertex, }; edge.from.edges.push(vEdge); vertex.edges.push(vEdge); } for (let edge of toEdges) { const vEdge = { metadata: edge.metadata, from: vertex, to: edge.to, }; edge.to.edges.push(vEdge); vertex.edges.push(vEdge); } this.vertices.push(vertex); return vertex; } /** * Returns a generator that yields all vertices matching the predicate. * @param predicate - A function to test each vertex * @returns A generator of matching vertices */ findVertex(predicate) { const veritces = this.vertices; function* gen() { for (let vertex of veritces) { if (predicate(vertex)) { yield vertex; } } return null; } return gen(); } /** * Adds a directed edge between two existing vertices. * @param metadata - The metadata to attach to the edge * @param from - The source vertex * @param to - The destination vertex * @returns The newly created edge */ addEdge(metadata, from, to) { const edge = { metadata, from, to, }; edge.from.edges.push(edge); edge.to.edges.push(edge); return edge; } /** * Performs a breadth-first traversal following outgoing edges from the starting vertex or vertices. * @param from - A starting vertex, or a predicate to select multiple starting vertices * @returns A generator yielding vertices in BFS order */ breadthFirstSearch(from) { const visited = []; function* rec(vertex) { if (visited.includes(vertex)) { return null; } visited.push(vertex); yield vertex; let generators = vertex.edges .filter((e) => e.from === vertex) .map((e) => rec(e.to)); while (generators.length) { let prev = generators; generators = []; for (let gen of prev) { const next = gen.next(); if (!next.done) { generators.push(gen); yield next.value; } } } return null; } if (from instanceof Function) { let generators = this.vertices.filter(from).map(rec); return (function* () { while (generators.length) { let prev = generators; generators = []; for (let gen of prev) { const next = gen.next(); if (!next.done) { generators.push(gen); yield next.value; } } } return null; })(); } else { return rec(from); } } /** * Performs a reverse breadth-first traversal following incoming edges from the starting vertex or vertices. * @param to - A starting vertex, or a predicate to select multiple starting vertices * @returns A generator yielding vertices in reverse BFS order */ reverseBreadthFirstSearch(to) { const visited = []; function* rec(vertex) { if (visited.includes(vertex)) { return null; } visited.push(vertex); yield vertex; let generators = vertex.edges .filter((e) => e.to === vertex) .map((e) => rec(e.from)); while (generators.length) { let prev = generators; generators = []; for (let gen of prev) { const next = gen.next(); if (!next.done) { generators.push(gen); yield next.value; } } } return null; } if (to instanceof Function) { let generators = this.vertices.filter(to).map(rec); return (function* () { while (generators.length) { let prev = generators; generators = []; for (let gen of prev) { const next = gen.next(); if (!next.done) { generators.push(gen); yield next.value; } } } return null; })(); } else { return rec(to); } } /** * Finds the shortest path (by edge count) between two vertices using BFS. * @param from - The starting vertex, or a predicate to select starting vertices * @param to - The target vertex, or a predicate to identify target vertices * @returns An array of edges forming the shortest path, or `null` if no path exists */ shortestPath(from, to) { const isDone = to instanceof Function ? to : (v) => v === to; const path = []; const visited = []; function* check(vertex, path) { if (isDone(vertex)) { return path; } if (visited.includes(vertex)) { return null; } visited.push(vertex); yield; let generators = vertex.edges .filter((e) => e.from === vertex) .map((e) => check(e.to, [...path, e])); while (generators.length) { let prev = generators; generators = []; for (let gen of prev) { const next = gen.next(); if (next.done === true) { if (next.value) { return next.value; } } else { generators.push(gen); yield; } } } return null; } if (from instanceof Function) { let generators = this.vertices.filter(from).map((v) => check(v, [])); while (generators.length) { let prev = generators; generators = []; for (let gen of prev) { const next = gen.next(); if (next.done === true) { if (next.value) { return next.value; } } else { generators.push(gen); } } } } else { const gen = check(from, []); while (true) { const next = gen.next(); if (next.done) { return next.value; } } } return null; } } exports.Graph = Graph; //# sourceMappingURL=graph.js.map