This commit is contained in:
MindfulMinun committed 2022-08-01 02:44:17 -05:00
1 parent bb4b783537
commit e273396609
3 files changed
+220 -7

No files matched your search

+120
View File
@@ -0,0 +1,120 @@
import { Graph, VertexType, EdgeType } from "./graph.ts";
type WeightFn<Graph> = (v: VertexType<Graph>, e: EdgeType<Graph>) => number
type SearchFn<Graph> = (v: VertexType<Graph>, e: EdgeType<Graph> | null, pathmap: Map<typeof v, typeof e>) => boolean | void
export class GraphSolver<vData, eData> {
G: Graph<vData, eData>
constructor(G: Graph<vData, eData>) {
this.G = G
}
/**
* Performs DFS on a graph.
* @author MindfulMinun
* @since 2022-07-31
*/
#DFS(root: VertexType<typeof this.G>, search: (v: typeof root) => typeof root | void) {
const stack = [root]
const discovered = new WeakSet<typeof root>()
discovered.add(root)
while (stack.length !== 0) {
const v = stack.pop()!
const value = search(v)
if (value) return value
for (const E of v.adjacentEdges) {
const w = E.v === v ? E.u : E.v
if (!discovered.has(w)) {
discovered.add(w)
stack.push(w)
}
}
}
return null
}
/**
* Performs a breadth-first search on a graph. Returns the path from root to the found node.
* @author MindfulMinun
* @since 2022-07-31
*/
BFS(root: VertexType<typeof this.G>, search: SearchFn<typeof this.G>) {
const queue = [root]
const backEdges = new Map<VertexType<typeof this.G>, EdgeType<typeof this.G>>()
const discovered = new WeakSet<typeof root>()
discovered.add(root)
let v: VertexType<typeof this.G> | null = root
while (queue.length !== 0) {
v = queue.shift()!
const value = search(v, backEdges.get(v) ?? null, backEdges)
if (value) break
for (const E of v.adjacentEdges) {
if (this.G.directed && v !== E.u) continue
const w = E.not(v)
if (!discovered.has(w)) {
discovered.add(w)
backEdges.set(w, E)
queue.push(w)
}
}
v = null
}
if (v === null) return null
const reversed: EdgeType<typeof this.G>[] = []
while (v != root) {
const back = backEdges.get(v)
if (!back) break
v = back.not(v)
reversed.push(back)
}
const path = this.G.createPath(root)
reversed.reverse().forEach(e => path.addEdge(e))
return path
}
}
// 0
// / \
// 1 2
// / \ / \
// 3 4 5 6
if (import.meta.main) {
const G = new Graph<number, null>({ directed: true })
//#region Graph definition
const vertices = [
G.createVertex(0),
G.createVertex(1),
G.createVertex(2),
G.createVertex(3),
G.createVertex(4),
G.createVertex(5),
G.createVertex(6)
]
G.createEdge(vertices[0], vertices[1], null)
G.createEdge(vertices[0], vertices[2], null)
G.createEdge(vertices[1], vertices[3], null)
G.createEdge(vertices[1], vertices[4], null)
G.createEdge(vertices[2], vertices[5], null)
G.createEdge(vertices[2], vertices[6], null)
//#endregion
const solver = new GraphSolver(G)
const solution = solver.BFS(vertices[1], v => v === vertices[6])
console.log(
G.directed ? 'directed' : 'undirected',
solution?.edges.map(e => e),
solution?.vertices.map(v => v.data)
)
}
+81 -7
View File
@@ -2,12 +2,34 @@ import * as Colors from 'https://deno.land/std/fmt/colors.ts'
type UUID = ReturnType<typeof crypto.randomUUID>
/** Represents a graph, consisting of vertices and edges. */
export type VertexType<G> = G extends Graph<infer T, infer V> ? Vertex<T, V> : never
export type EdgeType<G> = G extends Graph<infer T, infer V> ? Edge<T, V> : never
export type WeightFn<Graph> = (v: VertexType<Graph>, e: EdgeType<Graph>) => number
interface GraphOpts<G> {
directed: boolean
weights: WeightFn<G>
}
/**
* Represents a graph, consisting of vertices and edges.
* @author MindfulMinun
* @since 2022-07-26
*/
export class Graph<vData, eData> {
vertices: Map<UUID, Vertex<vData, eData>>
edges: Map<UUID, Edge<vData, eData>>
directed: boolean
weights: WeightFn<typeof this>
constructor() {
constructor(opts: Partial<GraphOpts<Graph<vData, eData>>> = {}) {
const options: GraphOpts<Graph<vData, eData>> = {
directed: false,
weights: () => 1,
...opts
}
this.directed = options.directed
this.weights = options.weights
this.vertices = new Map()
this.edges = new Map()
}
@@ -44,6 +66,10 @@ export class Graph<vData, eData> {
return E
}
createPath(start: Vertex<vData, eData>) {
return new Path<vData, eData>(this, start)
}
toJSON() {
return {
"@graph": Graph.signature,
@@ -91,9 +117,11 @@ export class Graph<vData, eData> {
static signature = "Made with love by MindfulMinun <https://benjic.xyz>" as const
}
export class WeightedGraph<vData, eData> extends Graph<vData, eData> {
}
/**
* Represents a vertex in a graph.
* @author MindfulMinun
* @since 2022-07-26
*/
export class Vertex<VertexData, EdgeData> {
graph: Graph<VertexData, EdgeData>
@@ -108,12 +136,14 @@ export class Vertex<VertexData, EdgeData> {
this.adjacentEdges = new Set()
}
delete() { this.graph.deleteVertex(this) }
representAsJSON() {
return [this.id, this.data]
}
toString() {
return Colors.cyan(`<${this.id.slice(0, 8)}>`)
return Colors.cyan(`<${this.id.slice(0, 8)}>: ${this.data}`)
}
[Symbol.for("Deno.customInspect")]() {
@@ -121,6 +151,11 @@ export class Vertex<VertexData, EdgeData> {
}
}
/**
* Represents an edge in a graph which connects two vertices.
* @author MindfulMinun
* @since 2022-07-26
*/
export class Edge<vData, eData> {
graph: Graph<vData, eData>
data: eData
@@ -139,6 +174,11 @@ export class Edge<vData, eData> {
this.v = v
}
/** Given one vertex, get the opposite vertex of this edge */
not(v: typeof this.u | typeof this.v) {
return this.v !== v ? this.v : this.u
}
delete() { this.graph.deleteEdge(this) }
representAsJSON() {
@@ -151,10 +191,44 @@ export class Edge<vData, eData> {
}
toString() {
return Colors.magenta(`<${this.id.slice(0, 8)}>: ${this.u} --> ${this.v}`)
return Colors.magenta(`<${this.id.slice(0, 8)}>: ${this.u} ${!this.graph.directed ? '<' : ''}--> ${this.v}`)
}
[Symbol.for("Deno.customInspect")]() {
return this.toString()
}
}
export class Path<vData, eData> {
graph: Graph<vData, eData>
start: Vertex<vData, eData> | null
edges: Edge<vData, eData>[]
constructor(graph: Graph<vData, eData>, start?: Vertex<vData, eData>) {
this.graph = graph
this.edges = []
this.start = start ?? null
}
addEdge(e: Edge<vData, eData>) {
if (!this.start) this.start = e.u
if (e.graph !== this.graph) throw Error("The edges in a path must all belong to the same graph.")
this.edges.push(e)
}
get vertices() {
const vertices: Vertex<vData, eData>[] = []
if (!this.start) return vertices
let current = this.start
vertices.push(current)
for (const e of this.edges) {
current = e.u !== current ? e.u : e.v
vertices.push(current)
}
return vertices
}
}