More graphs and primes!

This commit is contained in:
mindfulminun committed 2022-10-18 23:26:30 -05:00
1 parent e273396609
commit a7dc28d86d
6 files changed
+301 -157

No files matched your search

+72
View File
@@ -0,0 +1,72 @@
import { Graph } from './graph.ts'
/**
* Creates a undirected cyclic graph.
*
* ```plaintext
* A
* / \
* C B
* \ / \
* \ E D
* \|
* F
* ```
*/
export function g1() {
const G = new Graph<null, null>({ directed: false })
const vertices = [
G.createVertex(null, 'A'),
G.createVertex(null, 'B'),
G.createVertex(null, 'C'),
G.createVertex(null, 'D'),
G.createVertex(null, 'E'),
G.createVertex(null, 'F')
]
G.createEdge(vertices[0], vertices[1], null, { id: 'AB' })
G.createEdge(vertices[0], vertices[2], null, { id: 'AC' })
G.createEdge(vertices[1], vertices[3], null, { id: 'BD' })
G.createEdge(vertices[1], vertices[4], null, { id: 'BE' })
G.createEdge(vertices[2], vertices[5], null, { id: 'CF' })
G.createEdge(vertices[3], vertices[5], null, { id: 'DF' })
G.createEdge(vertices[4], vertices[5], null, { id: 'EF' })
return G
}
/**
* Creates an *undirected acyclic graph* for testing. Resembles a tree.
*
*
* ```plaintext
* 0
* / \
* 1 2
* / \ / \
* 3 4 5 6
* ```
*/
export function g2() {
const G = new Graph<null, null>({ directed: false })
const vertices = [
G.createVertex(null, 'A'),
G.createVertex(null, 'B'),
G.createVertex(null, 'C'),
G.createVertex(null, 'D'),
G.createVertex(null, 'E'),
G.createVertex(null, 'F'),
G.createVertex(null, 'G')
]
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)
return G
}
+100
View File
@@ -0,0 +1,100 @@
import { Graph, VertexType, EdgeType, Path, Vertex, PathType } from "./graph.ts";
/**
* Describes a search function for DFS and BFS.
*/
export type SearchFn<Graph> = (v: VertexType<Graph>, e: EdgeType<Graph> | null, pathmap: Map<typeof v, PathType<Graph>>) => boolean | void
/**
* A graph solver, which can perform various algorithms on a graph.
*/
export class GraphSolver<vData, eData> {
G: Graph<vData, eData>
constructor(G: Graph<vData, eData>) {
this.G = G
}
/**
* Performs a breadth-first search on a graph. Returns the path from the root to the found node.
* @author MindfulMinun
* @since 2022-09-28
*/
BFS(root: Vertex<vData, eData>, search: SearchFn<typeof this.G>): Path<vData, eData> | null {
const queue = [root]
const backPaths = new Map<Vertex<vData, eData>, Path<vData, eData>>()
const discovered = new WeakSet<typeof root>()
discovered.add(root)
let v: VertexType<typeof this.G> | null = root
backPaths.set(root, this.G.createPath(root))
while (queue.length !== 0) {
v = queue.shift()!
const found = search(v, backPaths.get(v)?.edges.at(-1) ?? null, backPaths)
if (found) break
for (const E of v.adjacentEdges) {
if (E.directed && v !== E.u) continue
const w = E.not(v)
if (!discovered.has(w)) {
discovered.add(w)
const path = backPaths.get(v)!.copy()
path.addEdge(E)
backPaths.set(w, path)
queue.push(w)
}
}
// If we iterate through all vertices without finding one that fulfills `search`,
// then the found vertex is null and we should return null
v = null
}
if (v === null) return null
return backPaths.get(v) ?? null
}
/**
* Performs a depth-first search on a graph. Returns the path from the root to the found node.
* @author MindfulMinun
* @since 2022-09-28
*/
DFS(root: Vertex<vData, eData>, search: SearchFn<typeof this.G>): Path<vData, eData> | null {
const stack = [root]
const backPaths = new Map<VertexType<typeof this.G>, Path<vData, eData>>()
// const discovered = new WeakSet<typeof root>()
// discovered.add(root)
let v: VertexType<typeof this.G> | null = root
backPaths.set(root, this.G.createPath(root))
while (stack.length !== 0) {
v = stack.pop()!
const found = search(v, backPaths.get(v)?.edges.at(-1) ?? null, backPaths)
if (found) break
for (const E of v.adjacentEdges) {
if (E.directed && v !== E.u) continue
const w = E.not(v)
// discovered.add(w)
stack.unshift(w)
// Update the path!
const path = backPaths.get(v)!.copy()
path.addEdge(E)
backPaths.set(w, path)
}
v = null
}
if (v === null) return null
return backPaths.get(v) ?? null
}
}
+261
View File
@@ -0,0 +1,261 @@
import * as Colors from "https://deno.land/[email protected]/fmt/colors.ts"
type UUID = ReturnType<typeof crypto.randomUUID>
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 PathType<G> = G extends Graph<infer T, infer V> ? Path<T, V> : never
export type WeightFn<Graph> = (this: Graph, e: EdgeType<Graph>) => number
interface GraphOpts<G> {
directed: boolean
weights: WeightFn<G>
}
interface EdgeOpts {
directed: boolean
id: UUID
}
/**
* 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(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()
}
createVertex(data: vData, id: UUID = crypto.randomUUID()): Vertex<vData, eData> {
const V = new Vertex(this, id, data)
this.vertices.set(id, V)
return V
}
deleteVertex(V: Vertex<vData, eData>) {
// Delete all edges connected to V, then delete V
for (const E of V.adjacentEdges) E.delete()
this.vertices.delete(V.id)
}
createEdge(u: Vertex<vData, eData>, v: Vertex<vData, eData>, data: eData, opts: Partial<EdgeOpts> = {}) {
const options: EdgeOpts = {
directed: this.directed,
id: crypto.randomUUID(),
...opts
}
const E = new Edge(this, options.id, u, v, options.directed, data)
this.edges.set(options.id, E)
v.adjacentEdges.add(E)
u.adjacentEdges.add(E)
return E
}
deleteEdge(E: Edge<vData, eData>) {
// Remove me from the caches of my connecting nodes, then delete me
E.u.adjacentEdges.delete(E)
E.v.adjacentEdges.delete(E)
this.edges.delete(E.id)
}
createPath(start: Vertex<vData, eData>) {
return new Path<vData, eData>(this, start)
}
toJSON() {
return {
"@graph": Graph.signature,
v: Array.from(this.vertices.values()).map(val => val.representAsJSON()),
e: Array.from(this.edges.values()).map(val => val.representAsJSON())
}
}
static fromJSON<vData, eData>(json: unknown) {
const G = new Graph<vData, eData>()
if (!json || typeof json !== 'object') throw Error("Failed to parse: Unexpected object!")
const obj = json as Record<string, unknown>
if (obj['@graph'] !== Graph.signature) throw Error("Failed to parse: The object isn't a graph!")
if (!Array.isArray(obj.v)) throw Error("Failed to parse: The vertices property isn't an array!")
if (!Array.isArray(obj.e)) throw Error("Failed to parse: The edges property isn't an array!")
for (const V of obj.v) {
if (!Array.isArray(V)) throw Error("Failed to parse: One of the vertices isn't a tuple!")
const [uuid, data] = V as [UUID, vData]
if (typeof uuid != 'string') throw Error("Failed to parse: Unexpected UUID!")
G.createVertex(data, uuid)
}
for (const E of obj.e) {
if (!Array.isArray(E)) throw Error("Failed to parse: One of the edges isn't a tuple!")
const [uuid, u, v, isDirected, data] = E as [UUID, UUID, UUID, boolean, eData]
if (typeof uuid != 'string') throw Error("Failed to parse: UUID wasn't a string!")
if (typeof u != 'string') throw Error("Failed to parse: U wasn't a string!")
if (typeof v != 'string') throw Error("Failed to parse: V wasn't a string!")
const U = G.vertices.get(u)
const V = G.vertices.get(v)
if (!U) throw Error(`Vertex ${U} doesn't exist!`)
if (!V) throw Error(`Vertex ${V} doesn't exist!`)
G.createEdge(U, V, data, {
id: uuid,
directed: isDirected
})
}
return G
}
static signature = "Made with love by MindfulMinun <https://benjic.xyz>" as const
}
/**
* Represents a vertex in a graph.
* @author MindfulMinun
* @since 2022-07-26
*/
export class Vertex<VertexData, EdgeData> {
graph: Graph<VertexData, EdgeData>
data: VertexData
id: UUID
adjacentEdges: Set<Edge<VertexData, EdgeData>>
constructor(parentGraph: Graph<VertexData, EdgeData>, id: UUID, data: VertexData) {
this.graph = parentGraph
this.data = data
this.id = id
this.adjacentEdges = new Set()
}
delete() { this.graph.deleteVertex(this) }
representAsJSON() {
return [this.id, this.data]
}
toString() {
return Colors.cyan(`<${this.id.slice(0, 8)}>: ${this.data}`)
}
[Symbol.for("Deno.customInspect")]() {
return this.toString()
}
}
/**
* 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
id: UUID
u: Vertex<vData, eData>
v: Vertex<vData, eData>
directed: boolean
constructor(
parentGraph: Graph<vData, eData>,
id: UUID,
u: Vertex<vData, eData>,
v: Vertex<vData, eData>,
directed: boolean,
data: eData
) {
this.graph = parentGraph
this.data = data
this.id = id
this.directed = directed
if (u.graph !== v.graph || u.graph !== parentGraph) throw Error("Both vertices and the new edge must all belong to the same graph.")
this.u = u
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() {
// tuple!
return [
this.id,
this.u.id,
this.v.id,
this.directed,
this.data
]
}
toString() {
return Colors.magenta(`<${this.id.slice(0, 8)}>: ${this.u} ${!this.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
}
copy() {
const path = new Path<vData, eData>(this.graph, this.start ?? undefined)
path.edges = this.edges.slice()
return path
}
}