Graphs!
This commit is contained in:
3 files changed
+220
-7
No files matched your search
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user