UNPKG

@sha1n/dagraph

Version:

Directed acyclic graph utility in TypeScript

142 lines (141 loc) 4.27 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createDAG = void 0; class Node { constructor(data, dependencies = new Set()) { this.data = data; this.dependencies = dependencies; } get id() { return this.data.id; } } class DAGraph { constructor() { this.nodesById = new Map(); } /** * Adds the specified identifiable node to the graph. */ addNode(data) { this.ensureNode(data); return this; } /** * @returns the data node identified by the specified id if found, else returns undefined. */ getNode(id) { var _a; return (_a = this.nodesById.get(id)) === null || _a === void 0 ? void 0 : _a.data; } /** * Adds an edge pointing from 'from' to 'to'. */ addEdge(from, to) { const fromNode = this.ensureNode(from); const toNode = this.ensureNode(to); toNode.dependencies.add(fromNode.id); if (!this.isAcyclic()) { throw new Error(`[${from.id}] -> [${to.id}] form a cycle`); } return this; } /** * Returns a generator that returns all the nodes in topological order. * Implements a depth-first-search algorithm. */ *topologicalSort() { const nodesById = this.nodesById; const visited = new Set(); const dependenciesOf = function* (node) { for (const child of node.dependencies || []) { if (!visited.has(child)) { yield* dependenciesOf(nodesById.get(child)); yield nodesById.get(child).data; visited.add(child); } } }; for (const node of nodesById.values()) { if (!visited.has(node.id)) { yield* dependenciesOf(node); yield node.data; visited.add(node.id); } } } /** * A generator that returns the traverse roots of this graph. */ *roots() { for (const node of this.nodesById.values()) { if (node.dependencies.size === 0) { yield node.data; } } } /** * A generator that returns all the nodes in the this graph. */ *nodes() { for (const node of this.nodesById.values()) { yield node.data; } } /** * Returns a graph with the same edges pointing in the opposite direction. * * @returns a DAGraph */ reverse() { const reverseGraph = new DAGraph(); for (const node of this.nodesById.values()) { reverseGraph.addNode(node.data); for (const dependency of node.dependencies) { const depData = this.nodesById.get(dependency).data; reverseGraph.addNode(depData); reverseGraph.addEdge(node.data, depData); } } return reverseGraph; } ensureNode(data) { let node = this.nodesById.get(data.id); if (node) { return node; } node = new Node(data); this.nodesById.set(data.id, node); return node; } isAcyclic() { const degrees = new Map(); this.nodesById.forEach(node => degrees.set(node.id, 0)); this.nodesById.forEach(node => node.dependencies.forEach(child => { degrees.set(child, degrees.get(child) + 1); })); const queue = new Array(); this.nodesById.forEach(node => { if (degrees.get(node.id) === 0) { queue.push(node.id); } }); let visitedNodeCount = 0; while (queue.length > 0) { const [nodeId] = queue.splice(0, 1); visitedNodeCount += 1; this.nodesById.get(nodeId).dependencies.forEach(child => { degrees.set(child, degrees.get(child) - 1); if (degrees.get(child) === 0) { queue.push(child); } }); } return visitedNodeCount === this.nodesById.size; } } function createDAG() { return new DAGraph(); } exports.createDAG = createDAG; exports.default = createDAG;