forked from 000alen/toposort
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathindex.js
148 lines (123 loc) · 3.61 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
/**
* Topological sorting function
*
* @param {Array} edges
* @returns {Array}
*/
module.exports = function (edges) {
return toposort(uniqueNodes(edges), edges);
};
module.exports.array = toposort;
module.exports.parallel = parallel_toposort;
function toposort(nodes, edges) {
var cursor = nodes.length,
sorted = new Array(cursor),
visited = {},
i = cursor,
// Better data structures make algorithm much faster.
outgoingEdges = makeOutgoingEdges(edges),
nodesHash = makeNodesHash(nodes);
// check for unknown nodes
edges.forEach(function (edge) {
if (!nodesHash.has(edge[0]) || !nodesHash.has(edge[1])) {
const missing = new Set(!nodesHash.has(edge[0]) ? edge[0] : edge[1]);
throw new Error(
`Unknown node. There is an unknown node in the supplied edges (${
Array.from(missing).join(", ") || "unknown"
}).`
);
}
});
while (i--) {
if (!visited[i]) visit(nodes[i], i, new Set());
}
return sorted;
function visit(node, i, predecessors) {
if (predecessors.has(node)) {
var nodeRep;
try {
nodeRep = ", node was:" + JSON.stringify(node);
} catch (e) {
nodeRep = "";
}
throw new Error("Cyclic dependency" + nodeRep);
}
if (!nodesHash.has(node)) {
throw new Error(
"Found unknown node. Make sure to provided all involved nodes. Unknown node: " +
JSON.stringify(node)
);
}
if (visited[i]) return;
visited[i] = true;
var outgoing = outgoingEdges.get(node) || new Set();
outgoing = Array.from(outgoing);
if ((i = outgoing.length)) {
predecessors.add(node);
do {
var child = outgoing[--i];
visit(child, nodesHash.get(child), predecessors);
} while (i);
predecessors.delete(node);
}
sorted[--cursor] = node;
}
}
// a, b, c, d, e, f
// d -> b, e -> b, f -> d
// parallel_toposort: [a, c, b], [d, e], [f]
function parallel_toposort(nodes, edges) {
const nodesHash = makeNodesHash(nodes);
edges.forEach((edge) => {
if (!nodesHash.has(edge[0]) || !nodesHash.has(edge[1])) {
const missing = new Set(!nodesHash.has(edge[0]) ? edge[0] : edge[1]);
throw new Error(
`Unknown node. There is an unknown node in the supplied edges (${
Array.from(missing).join(", ") || "unknown"
}).`
);
}
});
const visited = new Set();
const sorted = [];
while (visited.size < nodes.length) {
// visit nodes with no dependencies or nodes with all dependencies visited
const next = nodes.filter((node) => {
if (visited.has(node)) return false;
const dependencies = edges
.filter(([_, to]) => to === node)
.map(([from, _]) => from);
return dependencies.every((dependency) => visited.has(dependency));
});
if (next.length === 0) throw new Error("Circular dependency detected");
next.forEach((node) => visited.add(node));
sorted.push(next);
}
return sorted;
}
function uniqueNodes(arr) {
var res = new Set();
for (var i = 0, len = arr.length; i < len; i++) {
var edge = arr[i];
res.add(edge[0]);
res.add(edge[1]);
}
return Array.from(res);
}
function makeOutgoingEdges(arr) {
var edges = new Map();
for (var i = 0, len = arr.length; i < len; i++) {
var edge = arr[i];
if (!edges.has(edge[0])) edges.set(edge[0], new Set());
if (!edges.has(edge[1])) edges.set(edge[1], new Set());
edges.get(edge[0]).add(edge[1]);
}
return edges;
}
function makeNodesHash(arr) {
var res = new Map();
for (var i = 0, len = arr.length; i < len; i++) {
res.set(arr[i], i);
}
return res;
}