import { boolean } from 'ts-matches' import { ExtendedVersion } from '../exver' export type Vertex = { metadata: VMetadata edges: Array> } export type Edge = { metadata: EMetadata from: Vertex to: Vertex } export class Graph { private readonly vertices: Array> = [] constructor() {} dump( metadataRepr: (metadata: VMetadata | EMetadata) => any = (a) => a, ): string { 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, ) } addVertex( metadata: VMetadata, fromEdges: Array, 'to'>>, toEdges: Array, 'from'>>, ): Vertex { const vertex: 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 } findVertex( predicate: (vertex: Vertex) => boolean, ): Generator, null> { const veritces = this.vertices function* gen() { for (let vertex of veritces) { if (predicate(vertex)) { yield vertex } } return null } return gen() } addEdge( metadata: EMetadata, from: Vertex, to: Vertex, ): Edge { const edge = { metadata, from, to, } edge.from.edges.push(edge) edge.to.edges.push(edge) return edge } breadthFirstSearch( from: | Vertex | ((vertex: Vertex) => boolean), ): Generator, null> { const visited: Array> = [] function* rec( vertex: Vertex, ): Generator, null> { 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) } } reverseBreadthFirstSearch( to: | Vertex | ((vertex: Vertex) => boolean), ): Generator, null> { const visited: Array> = [] function* rec( vertex: Vertex, ): Generator, null> { 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) } } shortestPath( from: | Vertex | ((vertex: Vertex) => boolean), to: | Vertex | ((vertex: Vertex) => boolean), ): Array> | null { const isDone = to instanceof Function ? to : (v: Vertex) => v === to const path: Array> = [] const visited: Array> = [] function* check( vertex: Vertex, path: Array>, ): Generator> | null> { 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 } }