@atlaspack/graph 3.3.2-dev.3689 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/package.json +3 -3
- package/LICENSE +0 -201
- package/lib/AdjacencyList.js +0 -1435
- package/lib/BitSet.js +0 -80
- package/lib/ContentGraph.js +0 -83
- package/lib/Graph.js +0 -536
- package/lib/index.js +0 -54
- package/lib/shared-buffer.js +0 -28
- package/lib/types.js +0 -14
package/lib/BitSet.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.BitSet = void 0;
|
|
7
|
-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/clz32#implementing_count_leading_ones_and_beyond
|
|
8
|
-
function ctz32(n) {
|
|
9
|
-
if (n === 0) {
|
|
10
|
-
return 32;
|
|
11
|
-
}
|
|
12
|
-
return 31 - Math.clz32(n & -n);
|
|
13
|
-
}
|
|
14
|
-
class BitSet {
|
|
15
|
-
constructor(maxBits) {
|
|
16
|
-
this.bits = new Uint32Array(Math.ceil(maxBits / 32));
|
|
17
|
-
}
|
|
18
|
-
clone() {
|
|
19
|
-
let res = new BitSet(this.capacity);
|
|
20
|
-
res.bits.set(this.bits);
|
|
21
|
-
return res;
|
|
22
|
-
}
|
|
23
|
-
static union(a, b) {
|
|
24
|
-
let res = a.clone();
|
|
25
|
-
res.union(b);
|
|
26
|
-
return res;
|
|
27
|
-
}
|
|
28
|
-
get capacity() {
|
|
29
|
-
return this.bits.length * 32;
|
|
30
|
-
}
|
|
31
|
-
add(bit) {
|
|
32
|
-
this.bits[bit >>> 5] |= 1 << (bit & 31);
|
|
33
|
-
}
|
|
34
|
-
delete(bit) {
|
|
35
|
-
this.bits[bit >>> 5] &= ~(1 << (bit & 31));
|
|
36
|
-
}
|
|
37
|
-
has(bit) {
|
|
38
|
-
return Boolean(this.bits[bit >>> 5] & 1 << (bit & 31));
|
|
39
|
-
}
|
|
40
|
-
empty() {
|
|
41
|
-
for (let k = 0; k < this.bits.length; k++) {
|
|
42
|
-
if (this.bits[k] !== 0) {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
clear() {
|
|
49
|
-
this.bits.fill(0);
|
|
50
|
-
}
|
|
51
|
-
intersect(other) {
|
|
52
|
-
for (let i = 0; i < this.bits.length; i++) {
|
|
53
|
-
this.bits[i] &= other.bits[i];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
union(other) {
|
|
57
|
-
for (let i = 0; i < this.bits.length; i++) {
|
|
58
|
-
this.bits[i] |= other.bits[i];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
remove(other) {
|
|
62
|
-
for (let i = 0; i < this.bits.length; i++) {
|
|
63
|
-
this.bits[i] &= ~other.bits[i];
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
forEach(fn) {
|
|
67
|
-
// https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
|
|
68
|
-
let bits = this.bits;
|
|
69
|
-
for (let k = 0; k < bits.length; k++) {
|
|
70
|
-
let v = bits[k];
|
|
71
|
-
while (v !== 0) {
|
|
72
|
-
let t = (v & -v) >>> 0;
|
|
73
|
-
// $FlowFixMe
|
|
74
|
-
fn((k << 5) + ctz32(v));
|
|
75
|
-
v ^= t;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
exports.BitSet = BitSet;
|
package/lib/ContentGraph.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = void 0;
|
|
7
|
-
var _Graph = _interopRequireDefault(require("./Graph"));
|
|
8
|
-
function _nullthrows() {
|
|
9
|
-
const data = _interopRequireDefault(require("nullthrows"));
|
|
10
|
-
_nullthrows = function () {
|
|
11
|
-
return data;
|
|
12
|
-
};
|
|
13
|
-
return data;
|
|
14
|
-
}
|
|
15
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
16
|
-
class ContentGraph extends _Graph.default {
|
|
17
|
-
constructor(opts) {
|
|
18
|
-
if (opts) {
|
|
19
|
-
let {
|
|
20
|
-
_contentKeyToNodeId,
|
|
21
|
-
_nodeIdToContentKey,
|
|
22
|
-
...rest
|
|
23
|
-
} = opts;
|
|
24
|
-
super(rest);
|
|
25
|
-
this._contentKeyToNodeId = _contentKeyToNodeId;
|
|
26
|
-
this._nodeIdToContentKey = _nodeIdToContentKey;
|
|
27
|
-
} else {
|
|
28
|
-
super();
|
|
29
|
-
this._contentKeyToNodeId = new Map();
|
|
30
|
-
this._nodeIdToContentKey = new Map();
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// $FlowFixMe[prop-missing]
|
|
35
|
-
static deserialize(opts) {
|
|
36
|
-
return new ContentGraph(opts);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// $FlowFixMe[prop-missing]
|
|
40
|
-
serialize() {
|
|
41
|
-
// $FlowFixMe[prop-missing]
|
|
42
|
-
return {
|
|
43
|
-
...super.serialize(),
|
|
44
|
-
_contentKeyToNodeId: this._contentKeyToNodeId,
|
|
45
|
-
_nodeIdToContentKey: this._nodeIdToContentKey
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
addNodeByContentKey(contentKey, node) {
|
|
49
|
-
if (this.hasContentKey(contentKey)) {
|
|
50
|
-
throw new Error('Graph already has content key ' + contentKey);
|
|
51
|
-
}
|
|
52
|
-
let nodeId = super.addNode(node);
|
|
53
|
-
this._contentKeyToNodeId.set(contentKey, nodeId);
|
|
54
|
-
this._nodeIdToContentKey.set(nodeId, contentKey);
|
|
55
|
-
return nodeId;
|
|
56
|
-
}
|
|
57
|
-
addNodeByContentKeyIfNeeded(contentKey, node) {
|
|
58
|
-
return this.hasContentKey(contentKey) ? this.getNodeIdByContentKey(contentKey) : this.addNodeByContentKey(contentKey, node);
|
|
59
|
-
}
|
|
60
|
-
getNodeByContentKey(contentKey) {
|
|
61
|
-
let nodeId = this._contentKeyToNodeId.get(contentKey);
|
|
62
|
-
if (nodeId != null) {
|
|
63
|
-
return super.getNode(nodeId);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
getContentKeyByNodeId(nodeId) {
|
|
67
|
-
return (0, _nullthrows().default)(this._nodeIdToContentKey.get(nodeId), `Expected node id ${nodeId} to exist`);
|
|
68
|
-
}
|
|
69
|
-
getNodeIdByContentKey(contentKey) {
|
|
70
|
-
return (0, _nullthrows().default)(this._contentKeyToNodeId.get(contentKey), `Expected content key ${contentKey} to exist`);
|
|
71
|
-
}
|
|
72
|
-
hasContentKey(contentKey) {
|
|
73
|
-
return this._contentKeyToNodeId.has(contentKey);
|
|
74
|
-
}
|
|
75
|
-
removeNode(nodeId, removeOrphans = true) {
|
|
76
|
-
this._assertHasNodeId(nodeId);
|
|
77
|
-
let contentKey = (0, _nullthrows().default)(this._nodeIdToContentKey.get(nodeId));
|
|
78
|
-
this._contentKeyToNodeId.delete(contentKey);
|
|
79
|
-
this._nodeIdToContentKey.delete(nodeId);
|
|
80
|
-
super.removeNode(nodeId, removeOrphans);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
exports.default = ContentGraph;
|
package/lib/Graph.js
DELETED
|
@@ -1,536 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, "__esModule", {
|
|
4
|
-
value: true
|
|
5
|
-
});
|
|
6
|
-
exports.default = exports.NULL_EDGE_TYPE = exports.ALL_EDGE_TYPES = void 0;
|
|
7
|
-
exports.mapVisitor = mapVisitor;
|
|
8
|
-
var _types = require("./types");
|
|
9
|
-
var _AdjacencyList = _interopRequireDefault(require("./AdjacencyList"));
|
|
10
|
-
var _BitSet = require("./BitSet");
|
|
11
|
-
function _nullthrows() {
|
|
12
|
-
const data = _interopRequireDefault(require("nullthrows"));
|
|
13
|
-
_nullthrows = function () {
|
|
14
|
-
return data;
|
|
15
|
-
};
|
|
16
|
-
return data;
|
|
17
|
-
}
|
|
18
|
-
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
19
|
-
const NULL_EDGE_TYPE = exports.NULL_EDGE_TYPE = 1;
|
|
20
|
-
const ALL_EDGE_TYPES = exports.ALL_EDGE_TYPES = -1;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Internal type used for queue iterative DFS implementation.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Options for DFS traversal.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
class Graph {
|
|
31
|
-
constructor(opts) {
|
|
32
|
-
this.nodes = (opts === null || opts === void 0 ? void 0 : opts.nodes) || [];
|
|
33
|
-
this.setRootNodeId(opts === null || opts === void 0 ? void 0 : opts.rootNodeId);
|
|
34
|
-
let adjacencyList = opts === null || opts === void 0 ? void 0 : opts.adjacencyList;
|
|
35
|
-
let initialCapacity = opts === null || opts === void 0 ? void 0 : opts.initialCapacity;
|
|
36
|
-
this.adjacencyList = adjacencyList ? _AdjacencyList.default.deserialize(adjacencyList) : new _AdjacencyList.default(typeof initialCapacity === 'number' ? {
|
|
37
|
-
initialCapacity
|
|
38
|
-
} : undefined);
|
|
39
|
-
}
|
|
40
|
-
setRootNodeId(id) {
|
|
41
|
-
this.rootNodeId = id;
|
|
42
|
-
}
|
|
43
|
-
static deserialize(opts) {
|
|
44
|
-
return new this({
|
|
45
|
-
nodes: opts.nodes,
|
|
46
|
-
adjacencyList: opts.adjacencyList,
|
|
47
|
-
rootNodeId: opts.rootNodeId
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
serialize() {
|
|
51
|
-
return {
|
|
52
|
-
nodes: this.nodes,
|
|
53
|
-
adjacencyList: this.adjacencyList.serialize(),
|
|
54
|
-
rootNodeId: this.rootNodeId
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Returns an iterator of all edges in the graph. This can be large, so iterating
|
|
59
|
-
// the complete list can be costly in large graphs. Used when merging graphs.
|
|
60
|
-
getAllEdges() {
|
|
61
|
-
return this.adjacencyList.getAllEdges();
|
|
62
|
-
}
|
|
63
|
-
addNode(node) {
|
|
64
|
-
let id = this.adjacencyList.addNode();
|
|
65
|
-
this.nodes.push(node);
|
|
66
|
-
return id;
|
|
67
|
-
}
|
|
68
|
-
hasNode(id) {
|
|
69
|
-
return this.nodes[id] != null;
|
|
70
|
-
}
|
|
71
|
-
getNode(id) {
|
|
72
|
-
return this.nodes[id];
|
|
73
|
-
}
|
|
74
|
-
addEdge(from, to, type = NULL_EDGE_TYPE) {
|
|
75
|
-
if (Number(type) === 0) {
|
|
76
|
-
throw new Error(`Edge type "${type}" not allowed`);
|
|
77
|
-
}
|
|
78
|
-
if (this.getNode(from) == null) {
|
|
79
|
-
throw new Error(`"from" node '${(0, _types.fromNodeId)(from)}' not found`);
|
|
80
|
-
}
|
|
81
|
-
if (this.getNode(to) == null) {
|
|
82
|
-
throw new Error(`"to" node '${(0, _types.fromNodeId)(to)}' not found`);
|
|
83
|
-
}
|
|
84
|
-
return this.adjacencyList.addEdge(from, to, type);
|
|
85
|
-
}
|
|
86
|
-
hasEdge(from, to, type = NULL_EDGE_TYPE) {
|
|
87
|
-
return this.adjacencyList.hasEdge(from, to, type);
|
|
88
|
-
}
|
|
89
|
-
forEachNodeIdConnectedTo(to, fn, type = NULL_EDGE_TYPE) {
|
|
90
|
-
this._assertHasNodeId(to);
|
|
91
|
-
this.adjacencyList.forEachNodeIdConnectedTo(to, fn, type);
|
|
92
|
-
}
|
|
93
|
-
forEachNodeIdConnectedFrom(from, fn, type = NULL_EDGE_TYPE) {
|
|
94
|
-
this._assertHasNodeId(from);
|
|
95
|
-
this.adjacencyList.forEachNodeIdConnectedFromReverse(from, id => {
|
|
96
|
-
fn(id);
|
|
97
|
-
return false;
|
|
98
|
-
}, type);
|
|
99
|
-
}
|
|
100
|
-
getNodeIdsConnectedTo(nodeId, type = NULL_EDGE_TYPE) {
|
|
101
|
-
this._assertHasNodeId(nodeId);
|
|
102
|
-
return this.adjacencyList.getNodeIdsConnectedTo(nodeId, type);
|
|
103
|
-
}
|
|
104
|
-
getNodeIdsConnectedFrom(nodeId, type = NULL_EDGE_TYPE) {
|
|
105
|
-
this._assertHasNodeId(nodeId);
|
|
106
|
-
return this.adjacencyList.getNodeIdsConnectedFrom(nodeId, type);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Removes node and any edges coming from or to that node
|
|
110
|
-
removeNode(nodeId, removeOrphans = true) {
|
|
111
|
-
if (!this.hasNode(nodeId)) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
for (let {
|
|
115
|
-
type,
|
|
116
|
-
from
|
|
117
|
-
} of this.adjacencyList.getInboundEdgesByType(nodeId)) {
|
|
118
|
-
this._removeEdge(from, nodeId, type,
|
|
119
|
-
// Do not allow orphans to be removed as this node could be one
|
|
120
|
-
// and is already being removed.
|
|
121
|
-
false);
|
|
122
|
-
}
|
|
123
|
-
for (let {
|
|
124
|
-
type,
|
|
125
|
-
to
|
|
126
|
-
} of this.adjacencyList.getOutboundEdgesByType(nodeId)) {
|
|
127
|
-
this._removeEdge(nodeId, to, type, removeOrphans);
|
|
128
|
-
}
|
|
129
|
-
this.nodes[nodeId] = null;
|
|
130
|
-
}
|
|
131
|
-
removeEdges(nodeId, type = NULL_EDGE_TYPE) {
|
|
132
|
-
if (!this.hasNode(nodeId)) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
for (let to of this.getNodeIdsConnectedFrom(nodeId, type)) {
|
|
136
|
-
this._removeEdge(nodeId, to, type);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
removeEdge(from, to, type = NULL_EDGE_TYPE, removeOrphans = true) {
|
|
140
|
-
if (!this.adjacencyList.hasEdge(from, to, type)) {
|
|
141
|
-
throw new Error(`Edge from ${(0, _types.fromNodeId)(from)} to ${(0, _types.fromNodeId)(to)} not found!`);
|
|
142
|
-
}
|
|
143
|
-
this._removeEdge(from, to, type, removeOrphans);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Removes edge and node the edge is to if the node is orphaned
|
|
147
|
-
_removeEdge(from, to, type = NULL_EDGE_TYPE, removeOrphans = true) {
|
|
148
|
-
if (!this.adjacencyList.hasEdge(from, to, type)) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
this.adjacencyList.removeEdge(from, to, type);
|
|
152
|
-
if (removeOrphans && this.isOrphanedNode(to)) {
|
|
153
|
-
this.removeNode(to);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
isOrphanedNode(nodeId) {
|
|
157
|
-
if (!this.hasNode(nodeId)) {
|
|
158
|
-
return false;
|
|
159
|
-
}
|
|
160
|
-
if (this.rootNodeId == null) {
|
|
161
|
-
// If the graph does not have a root, and there are inbound edges,
|
|
162
|
-
// this node should not be considered orphaned.
|
|
163
|
-
return !this.adjacencyList.hasInboundEdges(nodeId);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Otherwise, attempt to traverse backwards to the root. If there is a path,
|
|
167
|
-
// then this is not an orphaned node.
|
|
168
|
-
let hasPathToRoot = false;
|
|
169
|
-
// go back to traverseAncestors
|
|
170
|
-
this.traverseAncestors(nodeId, (ancestorId, _, actions) => {
|
|
171
|
-
if (ancestorId === this.rootNodeId) {
|
|
172
|
-
hasPathToRoot = true;
|
|
173
|
-
actions.stop();
|
|
174
|
-
}
|
|
175
|
-
}, ALL_EDGE_TYPES);
|
|
176
|
-
if (hasPathToRoot) {
|
|
177
|
-
return false;
|
|
178
|
-
}
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
updateNode(nodeId, node) {
|
|
182
|
-
this._assertHasNodeId(nodeId);
|
|
183
|
-
this.nodes[nodeId] = node;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Update a node's downstream nodes making sure to prune any orphaned branches
|
|
187
|
-
replaceNodeIdsConnectedTo(fromNodeId, toNodeIds, replaceFilter, type = NULL_EDGE_TYPE, removeOrphans = true) {
|
|
188
|
-
this._assertHasNodeId(fromNodeId);
|
|
189
|
-
let outboundEdges = this.getNodeIdsConnectedFrom(fromNodeId, type);
|
|
190
|
-
let childrenToRemove = new Set(replaceFilter ? outboundEdges.filter(toNodeId => replaceFilter(toNodeId)) : outboundEdges);
|
|
191
|
-
for (let toNodeId of toNodeIds) {
|
|
192
|
-
childrenToRemove.delete(toNodeId);
|
|
193
|
-
if (!this.hasEdge(fromNodeId, toNodeId, type)) {
|
|
194
|
-
this.addEdge(fromNodeId, toNodeId, type);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
for (let child of childrenToRemove) {
|
|
198
|
-
this._removeEdge(fromNodeId, child, type, removeOrphans);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
traverse(visit, startNodeId, type = NULL_EDGE_TYPE) {
|
|
202
|
-
let enter = typeof visit === 'function' ? visit : visit.enter;
|
|
203
|
-
if (type === ALL_EDGE_TYPES && enter && (typeof visit === 'function' || !visit.exit)) {
|
|
204
|
-
return this.dfsFast(enter, startNodeId);
|
|
205
|
-
} else {
|
|
206
|
-
return this.dfs({
|
|
207
|
-
visit,
|
|
208
|
-
startNodeId,
|
|
209
|
-
getChildren: nodeId => this.getNodeIdsConnectedFrom(nodeId, type)
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
filteredTraverse(filter, visit, startNodeId, type) {
|
|
214
|
-
return this.traverse(mapVisitor(filter, visit), startNodeId, type);
|
|
215
|
-
}
|
|
216
|
-
traverseAncestors(startNodeId, visit, type = NULL_EDGE_TYPE) {
|
|
217
|
-
return this.dfs({
|
|
218
|
-
visit,
|
|
219
|
-
startNodeId,
|
|
220
|
-
getChildren: nodeId => this.getNodeIdsConnectedTo(nodeId, type)
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
dfsFast(visit, startNodeId) {
|
|
224
|
-
let traversalStartNode = (0, _nullthrows().default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
|
|
225
|
-
this._assertHasNodeId(traversalStartNode);
|
|
226
|
-
let visited;
|
|
227
|
-
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
|
228
|
-
this._visited = new _BitSet.BitSet(this.nodes.length);
|
|
229
|
-
visited = this._visited;
|
|
230
|
-
} else {
|
|
231
|
-
visited = this._visited;
|
|
232
|
-
visited.clear();
|
|
233
|
-
}
|
|
234
|
-
// Take shared instance to avoid re-entrancy issues.
|
|
235
|
-
this._visited = null;
|
|
236
|
-
let stopped = false;
|
|
237
|
-
let skipped = false;
|
|
238
|
-
let actions = {
|
|
239
|
-
skipChildren() {
|
|
240
|
-
skipped = true;
|
|
241
|
-
},
|
|
242
|
-
stop() {
|
|
243
|
-
stopped = true;
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
let queue = [{
|
|
247
|
-
nodeId: traversalStartNode,
|
|
248
|
-
context: null
|
|
249
|
-
}];
|
|
250
|
-
while (queue.length !== 0) {
|
|
251
|
-
let {
|
|
252
|
-
nodeId,
|
|
253
|
-
context
|
|
254
|
-
} = queue.pop();
|
|
255
|
-
if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
|
|
256
|
-
visited.add(nodeId);
|
|
257
|
-
skipped = false;
|
|
258
|
-
let newContext = visit(nodeId, context, actions);
|
|
259
|
-
if (typeof newContext !== 'undefined') {
|
|
260
|
-
// $FlowFixMe[reassign-const]
|
|
261
|
-
context = newContext;
|
|
262
|
-
}
|
|
263
|
-
if (skipped) {
|
|
264
|
-
continue;
|
|
265
|
-
}
|
|
266
|
-
if (stopped) {
|
|
267
|
-
this._visited = visited;
|
|
268
|
-
return context;
|
|
269
|
-
}
|
|
270
|
-
this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, child => {
|
|
271
|
-
if (!visited.has(child)) {
|
|
272
|
-
queue.push({
|
|
273
|
-
nodeId: child,
|
|
274
|
-
context
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
return false;
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
this._visited = visited;
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// A post-order implementation of dfsFast
|
|
285
|
-
postOrderDfsFast(visit, startNodeId) {
|
|
286
|
-
let traversalStartNode = (0, _nullthrows().default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
|
|
287
|
-
this._assertHasNodeId(traversalStartNode);
|
|
288
|
-
let visited;
|
|
289
|
-
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
|
290
|
-
this._visited = new _BitSet.BitSet(this.nodes.length);
|
|
291
|
-
visited = this._visited;
|
|
292
|
-
} else {
|
|
293
|
-
visited = this._visited;
|
|
294
|
-
visited.clear();
|
|
295
|
-
}
|
|
296
|
-
this._visited = null;
|
|
297
|
-
let stopped = false;
|
|
298
|
-
let actions = {
|
|
299
|
-
stop() {
|
|
300
|
-
stopped = true;
|
|
301
|
-
},
|
|
302
|
-
skipChildren() {
|
|
303
|
-
throw new Error('Calling skipChildren inside a post-order traversal is not allowed');
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
let queue = [traversalStartNode];
|
|
307
|
-
while (queue.length !== 0) {
|
|
308
|
-
let nodeId = queue[queue.length - 1];
|
|
309
|
-
if (!visited.has(nodeId)) {
|
|
310
|
-
visited.add(nodeId);
|
|
311
|
-
this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, child => {
|
|
312
|
-
if (!visited.has(child)) {
|
|
313
|
-
queue.push(child);
|
|
314
|
-
}
|
|
315
|
-
return false;
|
|
316
|
-
});
|
|
317
|
-
} else {
|
|
318
|
-
queue.pop();
|
|
319
|
-
visit(nodeId, null, actions);
|
|
320
|
-
if (stopped) {
|
|
321
|
-
this._visited = visited;
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
this._visited = visited;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Iterative implementation of DFS that supports all use-cases.
|
|
331
|
-
*
|
|
332
|
-
* This replaces `dfs` and will replace `dfsFast`.
|
|
333
|
-
*/
|
|
334
|
-
dfs({
|
|
335
|
-
visit,
|
|
336
|
-
startNodeId,
|
|
337
|
-
getChildren
|
|
338
|
-
}) {
|
|
339
|
-
let traversalStartNode = (0, _nullthrows().default)(startNodeId ?? this.rootNodeId, 'A start node is required to traverse');
|
|
340
|
-
this._assertHasNodeId(traversalStartNode);
|
|
341
|
-
let visited;
|
|
342
|
-
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
|
343
|
-
this._visited = new _BitSet.BitSet(this.nodes.length);
|
|
344
|
-
visited = this._visited;
|
|
345
|
-
} else {
|
|
346
|
-
visited = this._visited;
|
|
347
|
-
visited.clear();
|
|
348
|
-
}
|
|
349
|
-
// Take shared instance to avoid re-entrancy issues.
|
|
350
|
-
this._visited = null;
|
|
351
|
-
let stopped = false;
|
|
352
|
-
let skipped = false;
|
|
353
|
-
let actions = {
|
|
354
|
-
skipChildren() {
|
|
355
|
-
skipped = true;
|
|
356
|
-
},
|
|
357
|
-
stop() {
|
|
358
|
-
stopped = true;
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
const queue = [{
|
|
362
|
-
nodeId: traversalStartNode,
|
|
363
|
-
context: null
|
|
364
|
-
}];
|
|
365
|
-
const enter = typeof visit === 'function' ? visit : visit.enter;
|
|
366
|
-
while (queue.length !== 0) {
|
|
367
|
-
const command = queue.pop();
|
|
368
|
-
if (command.exit != null) {
|
|
369
|
-
let {
|
|
370
|
-
nodeId,
|
|
371
|
-
context,
|
|
372
|
-
exit
|
|
373
|
-
} = command;
|
|
374
|
-
let newContext = exit(nodeId, command.context, actions);
|
|
375
|
-
if (typeof newContext !== 'undefined') {
|
|
376
|
-
// $FlowFixMe[reassign-const]
|
|
377
|
-
context = newContext;
|
|
378
|
-
}
|
|
379
|
-
if (skipped) {
|
|
380
|
-
continue;
|
|
381
|
-
}
|
|
382
|
-
if (stopped) {
|
|
383
|
-
this._visited = visited;
|
|
384
|
-
return context;
|
|
385
|
-
}
|
|
386
|
-
} else {
|
|
387
|
-
let {
|
|
388
|
-
nodeId,
|
|
389
|
-
context
|
|
390
|
-
} = command;
|
|
391
|
-
if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
|
|
392
|
-
visited.add(nodeId);
|
|
393
|
-
skipped = false;
|
|
394
|
-
if (enter) {
|
|
395
|
-
let newContext = enter(nodeId, context, actions);
|
|
396
|
-
if (typeof newContext !== 'undefined') {
|
|
397
|
-
// $FlowFixMe[reassign-const]
|
|
398
|
-
context = newContext;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
if (skipped) {
|
|
402
|
-
continue;
|
|
403
|
-
}
|
|
404
|
-
if (stopped) {
|
|
405
|
-
this._visited = visited;
|
|
406
|
-
return context;
|
|
407
|
-
}
|
|
408
|
-
if (typeof visit !== 'function' && visit.exit) {
|
|
409
|
-
queue.push({
|
|
410
|
-
nodeId,
|
|
411
|
-
exit: visit.exit,
|
|
412
|
-
context
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// TODO turn into generator function
|
|
417
|
-
const children = getChildren(nodeId);
|
|
418
|
-
for (let i = children.length - 1; i > -1; i -= 1) {
|
|
419
|
-
const child = children[i];
|
|
420
|
-
if (visited.has(child)) {
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
queue.push({
|
|
424
|
-
nodeId: child,
|
|
425
|
-
context
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
this._visited = visited;
|
|
431
|
-
}
|
|
432
|
-
bfs(visit) {
|
|
433
|
-
let rootNodeId = (0, _nullthrows().default)(this.rootNodeId, 'A root node is required to traverse');
|
|
434
|
-
let queue = [rootNodeId];
|
|
435
|
-
let visited = new Set([rootNodeId]);
|
|
436
|
-
while (queue.length > 0) {
|
|
437
|
-
let node = queue.shift();
|
|
438
|
-
let stop = visit(rootNodeId);
|
|
439
|
-
if (stop === true) {
|
|
440
|
-
return node;
|
|
441
|
-
}
|
|
442
|
-
for (let child of this.getNodeIdsConnectedFrom(node)) {
|
|
443
|
-
if (!visited.has(child)) {
|
|
444
|
-
visited.add(child);
|
|
445
|
-
queue.push(child);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
return null;
|
|
450
|
-
}
|
|
451
|
-
topoSort(type) {
|
|
452
|
-
let sorted = [];
|
|
453
|
-
this.traverse({
|
|
454
|
-
exit: nodeId => {
|
|
455
|
-
sorted.push(nodeId);
|
|
456
|
-
}
|
|
457
|
-
}, null, type);
|
|
458
|
-
return sorted.reverse();
|
|
459
|
-
}
|
|
460
|
-
findAncestor(nodeId, fn) {
|
|
461
|
-
let res = null;
|
|
462
|
-
this.traverseAncestors(nodeId, (nodeId, ctx, traversal) => {
|
|
463
|
-
if (fn(nodeId)) {
|
|
464
|
-
res = nodeId;
|
|
465
|
-
traversal.stop();
|
|
466
|
-
}
|
|
467
|
-
});
|
|
468
|
-
return res;
|
|
469
|
-
}
|
|
470
|
-
findAncestors(nodeId, fn) {
|
|
471
|
-
let res = [];
|
|
472
|
-
this.traverseAncestors(nodeId, (nodeId, ctx, traversal) => {
|
|
473
|
-
if (fn(nodeId)) {
|
|
474
|
-
res.push(nodeId);
|
|
475
|
-
traversal.skipChildren();
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
return res;
|
|
479
|
-
}
|
|
480
|
-
findDescendant(nodeId, fn) {
|
|
481
|
-
let res = null;
|
|
482
|
-
this.traverse((nodeId, ctx, traversal) => {
|
|
483
|
-
if (fn(nodeId)) {
|
|
484
|
-
res = nodeId;
|
|
485
|
-
traversal.stop();
|
|
486
|
-
}
|
|
487
|
-
}, nodeId);
|
|
488
|
-
return res;
|
|
489
|
-
}
|
|
490
|
-
findDescendants(nodeId, fn) {
|
|
491
|
-
let res = [];
|
|
492
|
-
this.traverse((nodeId, ctx, traversal) => {
|
|
493
|
-
if (fn(nodeId)) {
|
|
494
|
-
res.push(nodeId);
|
|
495
|
-
traversal.skipChildren();
|
|
496
|
-
}
|
|
497
|
-
}, nodeId);
|
|
498
|
-
return res;
|
|
499
|
-
}
|
|
500
|
-
_assertHasNodeId(nodeId) {
|
|
501
|
-
if (!this.hasNode(nodeId)) {
|
|
502
|
-
throw new Error('Does not have node ' + (0, _types.fromNodeId)(nodeId));
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
exports.default = Graph;
|
|
507
|
-
function mapVisitor(filter, visit) {
|
|
508
|
-
function makeEnter(visit) {
|
|
509
|
-
return function (nodeId, context, actions) {
|
|
510
|
-
let value = filter(nodeId, actions);
|
|
511
|
-
if (value != null) {
|
|
512
|
-
return visit(value, context, actions);
|
|
513
|
-
}
|
|
514
|
-
};
|
|
515
|
-
}
|
|
516
|
-
if (typeof visit === 'function') {
|
|
517
|
-
return makeEnter(visit);
|
|
518
|
-
}
|
|
519
|
-
let mapped = {};
|
|
520
|
-
if (visit.enter != null) {
|
|
521
|
-
mapped.enter = makeEnter(visit.enter);
|
|
522
|
-
}
|
|
523
|
-
if (visit.exit != null) {
|
|
524
|
-
mapped.exit = function (nodeId, context, actions) {
|
|
525
|
-
let exit = visit.exit;
|
|
526
|
-
if (!exit) {
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
let value = filter(nodeId, actions);
|
|
530
|
-
if (value != null) {
|
|
531
|
-
return exit(value, context, actions);
|
|
532
|
-
}
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
return mapped;
|
|
536
|
-
}
|