pertain
Version:
Automated pub/sub across project dependencies. Run code from any installed package based on declarative rules in package.json
65 lines (55 loc) • 1.81 kB
text/typescript
import PertainError from './PertainError';
/* eslint-disable */
/**
* Topologically sort a list of items of any type, given a list of items and
* a map of outward relations between those items. (Could be a function, but
* it's a class for testability.)
*
* Credit to https://github.com/marcelklehr/toposort/ for a great
* implementation I had to adapt to be faster for our particular use case.
*
* That code appears here courtesy of the MIT license.
*/
export default class TopologicalSorter<T> {
private outgoingFrom: (node: T) => T[];
constructor(outgoingFrom: (node: T) => T[]) {
this.outgoingFrom = outgoingFrom;
}
public sort(nodes: T[]): T[] {
let cursor = nodes.length;
const sorted: T[] = new Array(cursor);
const visited: { [key: number]: boolean } = {};
let i = cursor;
// Better data structures make algorithm much faster.
const nodesHash = this.makeNodesHash(nodes);
const { outgoingFrom } = this;
while (i--) {
if (!visited[i]) visit(nodes[i], i, new Set());
}
return sorted;
function visit(node: T, i: number, predecessors: Set<T>): void {
if (predecessors.has(node)) {
throw new PertainError(`Cyclic dependency on ${node}`);
}
if (visited[i]) return;
visited[i] = true;
const outgoing = outgoingFrom(node);
if ((i = outgoing.length)) {
predecessors.add(node);
do {
let child = outgoing[--i];
visit(child, nodesHash.get(child) as number, predecessors);
} while (i);
predecessors.delete(node);
}
sorted[--cursor] = node;
}
}
private makeNodesHash(arr: T[]) {
const res = new Map<T, number>();
for (let i = 0, len = arr.length; i < len; i++) {
res.set(arr[i], i);
}
return res;
}
}