272 lines
9.0 KiB
JavaScript
272 lines
9.0 KiB
JavaScript
"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
|