graphology-dag
Version:
Directed acyclic graph functions for graphology.
150 lines (115 loc) • 3.57 kB
JavaScript
/**
* Graphology Topological Sort
* ============================
*
* Function performing topological sort over the given DAG using Kahn's
* algorithm.
*
* This function also works on disconnected graphs.
*
* [Reference]:
* https://en.wikipedia.org/wiki/Topological_sorting
*/
const isGraph = require('graphology-utils/is-graph');
const FixedDeque = require('mnemonist/fixed-deque');
function simpleInDegree(graph, node) {
let degree = 0;
graph.forEachInNeighbor(node, () => {
degree++;
});
return degree;
}
function forEachNodeInTopologicalOrder(graph, callback) {
if (!isGraph(graph))
throw new Error(
'graphology-dag/topological-sort: the given graph is not a valid graphology instance.'
);
// NOTE: falsely mixed graph representing directed graphs will work
if (graph.type === 'undirected' || graph.undirectedSize !== 0)
throw new Error(
'graphology-dag/topological-sort: cannot work if graph is not directed.'
);
if (graph.order === 0) return;
const queue = new FixedDeque(Array, graph.order);
const inDegrees = {};
let total = 0;
graph.forEachNode((node, attr) => {
const inDegree = graph.multi
? simpleInDegree(graph, node)
: graph.inDegree(node);
if (inDegree === 0) {
queue.push([node, attr, 0]);
} else {
inDegrees[node] = inDegree;
total += inDegree;
}
});
let currentGeneration = 0;
function neighborCallback(neighbor, attr) {
const neighborInDegree = --inDegrees[neighbor];
total--;
if (neighborInDegree === 0)
queue.push([neighbor, attr, currentGeneration + 1]);
inDegrees[neighbor] = neighborInDegree;
// NOTE: key deletion is expensive in JS and in this case pointless so
// we just skip it for performance reasons
}
while (queue.size !== 0) {
const [node, attr, gen] = queue.shift();
currentGeneration = gen;
callback(node, attr, gen);
graph.forEachOutNeighbor(node, neighborCallback);
}
if (total !== 0)
throw new Error(
'graphology-dag/topological-sort: given graph is not acyclic.'
);
}
function topologicalSort(graph) {
if (!isGraph(graph))
throw new Error(
'graphology-dag/topological-sort: the given graph is not a valid graphology instance.'
);
const sortedNodes = new Array(graph.order);
let i = 0;
forEachNodeInTopologicalOrder(graph, node => {
sortedNodes[i++] = node;
});
return sortedNodes;
}
function forEachTopologicalGeneration(graph, callback) {
if (!isGraph(graph))
throw new Error(
'graphology-dag/topological-generations: the given graph is not a valid graphology instance.'
);
if (graph.order === 0) return;
let lastGenLevel = 0;
let lastGen = [];
forEachNodeInTopologicalOrder(graph, (node, _, gen) => {
if (gen > lastGenLevel) {
callback(lastGen);
lastGenLevel = gen;
lastGen = [];
}
lastGen.push(node);
});
callback(lastGen);
}
function topologicalGenerations(graph) {
if (!isGraph(graph))
throw new Error(
'graphology-dag/topological-generations: the given graph is not a valid graphology instance.'
);
const generations = [];
forEachTopologicalGeneration(graph, generation => {
generations.push(generation);
});
return generations;
}
/**
* Exporting.
*/
exports.topologicalSort = topologicalSort;
exports.forEachNodeInTopologicalOrder = forEachNodeInTopologicalOrder;
exports.topologicalGenerations = topologicalGenerations;
exports.forEachTopologicalGeneration = forEachTopologicalGeneration;