silphius/app/util/digraph.js
2024-06-25 10:43:12 -05:00

88 lines
2.1 KiB
JavaScript

export default class Digraph {
arcs = new Map();
addDependency(head, tail) {
this.ensureTail(head);
this.ensureTail(tail).add(head);
}
detectCycles() {
const cycles = [];
const visited = new Set();
const walking = new Set();
const walk = (vertex) => {
if (!visited.has(vertex)) {
visited.add(vertex);
walking.add(vertex);
const it = this.neighbors(vertex);
for (let current = it.next(); true !== current.done; current = it.next()) {
const {value: neighbor} = current;
if (!visited.has(neighbor)) {
walk(neighbor);
}
else if (walking.has(neighbor)) {
cycles.push([vertex, neighbor]);
}
}
}
walking.delete(vertex);
};
const {tails} = this;
for (let current = tails.next(); true !== current.done; current = tails.next()) {
walk(current.value);
}
return cycles;
}
ensureTail(tail) {
if (!this.arcs.has(tail)) {
this.arcs.set(tail, new Set());
}
return this.arcs.get(tail);
}
neighbors(vertex) {
return this.arcs.get(vertex).values();
}
sort() {
const visited = new Set();
const scores = new Map();
const walk = (vertex, score) => {
visited.add(vertex);
const neighbors = this.neighbors(vertex);
for (let current = neighbors.next(); true !== current.done; current = neighbors.next()) {
const {value: neighbor} = current;
if (!visited.has(neighbor)) {
score = walk(neighbor, score);
}
}
scores.set(vertex, score);
return score - 1;
};
let score = this.arcs.size - 1;
const {tails} = this;
for (let current = tails.next(); true !== current.done; current = tails.next()) {
const {value: vertex} = current;
if (!visited.has(vertex)) {
score = walk(vertex, score);
}
}
return Array.from(scores.entries())
.sort(([, l], [, r]) => l - r)
.map(([vertex]) => vertex);
}
removeDependency(head, tail) {
if (this.arcs.has(tail)) {
this.arcs.get(tail).delete(head);
}
}
get tails() {
return this.arcs.keys();
}
}