@parcel/graph 2.9.4-nightly.3003 → 2.9.4-nightly.3008
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/lib/AdjacencyList.js +14 -0
- package/lib/BitSet.js +73 -0
- package/lib/Graph.js +92 -23
- package/lib/index.js +7 -0
- package/package.json +3 -2
- package/src/AdjacencyList.js +18 -0
- package/src/BitSet.js +93 -0
- package/src/Graph.js +112 -21
- package/src/index.js +1 -0
- package/src/types.js +1 -1
- package/test/BitSet.test.js +98 -0
- package/test/Graph.test.js +9 -9
package/lib/AdjacencyList.js
CHANGED
@@ -354,6 +354,20 @@ class AdjacencyList {
|
|
354
354
|
}
|
355
355
|
return nodes;
|
356
356
|
}
|
357
|
+
forEachNodeIdConnectedFromReverse(from, fn) {
|
358
|
+
let node = this.#nodes.head(from);
|
359
|
+
while (node !== null) {
|
360
|
+
let edge = this.#nodes.lastOut(node);
|
361
|
+
while (edge !== null) {
|
362
|
+
let to = this.#edges.to(edge);
|
363
|
+
if (fn(to)) {
|
364
|
+
return;
|
365
|
+
}
|
366
|
+
edge = this.#edges.prevOut(edge);
|
367
|
+
}
|
368
|
+
node = this.#nodes.next(node);
|
369
|
+
}
|
370
|
+
}
|
357
371
|
|
358
372
|
/**
|
359
373
|
* Get the list of nodes connected to this node.
|
package/lib/BitSet.js
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
exports.BitSet = void 0;
|
7
|
+
// Small wasm program that exposes the `ctz` instruction.
|
8
|
+
// https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Count_trailing_zeros
|
9
|
+
const wasmBuf = new Uint8Array([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0d, 0x01, 0x09, 0x74, 0x72, 0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x30, 0x00, 0x00, 0x0a, 0x07, 0x01, 0x05, 0x00, 0x20, 0x00, 0x68, 0x0b, 0x00, 0x0f, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x02, 0x08, 0x01, 0x00, 0x01, 0x00, 0x03, 0x6e, 0x75, 0x6d]);
|
10
|
+
|
11
|
+
// eslint-disable-next-line
|
12
|
+
const {
|
13
|
+
trailing0
|
14
|
+
} = new WebAssembly.Instance(new WebAssembly.Module(wasmBuf)).exports;
|
15
|
+
class BitSet {
|
16
|
+
constructor(maxBits) {
|
17
|
+
this.bits = new Uint32Array(Math.ceil(maxBits / 32));
|
18
|
+
}
|
19
|
+
clone() {
|
20
|
+
let res = new BitSet(this.capacity);
|
21
|
+
res.bits.set(this.bits);
|
22
|
+
return res;
|
23
|
+
}
|
24
|
+
static union(a, b) {
|
25
|
+
let res = a.clone();
|
26
|
+
res.union(b);
|
27
|
+
return res;
|
28
|
+
}
|
29
|
+
get capacity() {
|
30
|
+
return this.bits.length * 32;
|
31
|
+
}
|
32
|
+
add(bit) {
|
33
|
+
this.bits[bit >>> 5] |= 1 << (bit & 31);
|
34
|
+
}
|
35
|
+
delete(bit) {
|
36
|
+
this.bits[bit >>> 5] &= ~(1 << (bit & 31));
|
37
|
+
}
|
38
|
+
has(bit) {
|
39
|
+
return Boolean(this.bits[bit >>> 5] & 1 << (bit & 31));
|
40
|
+
}
|
41
|
+
clear() {
|
42
|
+
this.bits.fill(0);
|
43
|
+
}
|
44
|
+
intersect(other) {
|
45
|
+
for (let i = 0; i < this.bits.length; i++) {
|
46
|
+
this.bits[i] &= other.bits[i];
|
47
|
+
}
|
48
|
+
}
|
49
|
+
union(other) {
|
50
|
+
for (let i = 0; i < this.bits.length; i++) {
|
51
|
+
this.bits[i] |= other.bits[i];
|
52
|
+
}
|
53
|
+
}
|
54
|
+
remove(other) {
|
55
|
+
for (let i = 0; i < this.bits.length; i++) {
|
56
|
+
this.bits[i] &= ~other.bits[i];
|
57
|
+
}
|
58
|
+
}
|
59
|
+
forEach(fn) {
|
60
|
+
// https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
|
61
|
+
let bits = this.bits;
|
62
|
+
for (let k = 0; k < bits.length; k++) {
|
63
|
+
let v = bits[k];
|
64
|
+
while (v !== 0) {
|
65
|
+
let t = (v & -v) >>> 0;
|
66
|
+
// $FlowFixMe
|
67
|
+
fn((k << 5) + trailing0(v));
|
68
|
+
v ^= t;
|
69
|
+
}
|
70
|
+
}
|
71
|
+
}
|
72
|
+
}
|
73
|
+
exports.BitSet = BitSet;
|
package/lib/Graph.js
CHANGED
@@ -7,13 +7,7 @@ exports.default = exports.ALL_EDGE_TYPES = void 0;
|
|
7
7
|
exports.mapVisitor = mapVisitor;
|
8
8
|
var _types = require("./types");
|
9
9
|
var _AdjacencyList = _interopRequireDefault(require("./AdjacencyList"));
|
10
|
-
|
11
|
-
const data = _interopRequireDefault(require("assert"));
|
12
|
-
_assert = function () {
|
13
|
-
return data;
|
14
|
-
};
|
15
|
-
return data;
|
16
|
-
}
|
10
|
+
var _BitSet = require("./BitSet");
|
17
11
|
function _nullthrows() {
|
18
12
|
const data = _interopRequireDefault(require("nullthrows"));
|
19
13
|
_nullthrows = function () {
|
@@ -26,7 +20,7 @@ const ALL_EDGE_TYPES = -1;
|
|
26
20
|
exports.ALL_EDGE_TYPES = ALL_EDGE_TYPES;
|
27
21
|
class Graph {
|
28
22
|
constructor(opts) {
|
29
|
-
this.nodes = (opts === null || opts === void 0 ? void 0 : opts.nodes) ||
|
23
|
+
this.nodes = (opts === null || opts === void 0 ? void 0 : opts.nodes) || [];
|
30
24
|
this.setRootNodeId(opts === null || opts === void 0 ? void 0 : opts.rootNodeId);
|
31
25
|
let adjacencyList = opts === null || opts === void 0 ? void 0 : opts.adjacencyList;
|
32
26
|
this.adjacencyList = adjacencyList ? _AdjacencyList.default.deserialize(adjacencyList) : new _AdjacencyList.default();
|
@@ -56,23 +50,23 @@ class Graph {
|
|
56
50
|
}
|
57
51
|
addNode(node) {
|
58
52
|
let id = this.adjacencyList.addNode();
|
59
|
-
this.nodes.
|
53
|
+
this.nodes.push(node);
|
60
54
|
return id;
|
61
55
|
}
|
62
56
|
hasNode(id) {
|
63
|
-
return this.nodes
|
57
|
+
return this.nodes[id] != null;
|
64
58
|
}
|
65
59
|
getNode(id) {
|
66
|
-
return this.nodes
|
60
|
+
return this.nodes[id];
|
67
61
|
}
|
68
62
|
addEdge(from, to, type = 1) {
|
69
63
|
if (Number(type) === 0) {
|
70
64
|
throw new Error(`Edge type "${type}" not allowed`);
|
71
65
|
}
|
72
|
-
if (
|
66
|
+
if (this.getNode(from) == null) {
|
73
67
|
throw new Error(`"from" node '${(0, _types.fromNodeId)(from)}' not found`);
|
74
68
|
}
|
75
|
-
if (
|
69
|
+
if (this.getNode(to) == null) {
|
76
70
|
throw new Error(`"to" node '${(0, _types.fromNodeId)(to)}' not found`);
|
77
71
|
}
|
78
72
|
return this.adjacencyList.addEdge(from, to, type);
|
@@ -109,8 +103,7 @@ class Graph {
|
|
109
103
|
} of this.adjacencyList.getOutboundEdgesByType(nodeId)) {
|
110
104
|
this._removeEdge(nodeId, to, type);
|
111
105
|
}
|
112
|
-
|
113
|
-
(0, _assert().default)(wasRemoved);
|
106
|
+
this.nodes[nodeId] = null;
|
114
107
|
}
|
115
108
|
removeEdges(nodeId, type = 1) {
|
116
109
|
if (!this.hasNode(nodeId)) {
|
@@ -164,7 +157,7 @@ class Graph {
|
|
164
157
|
}
|
165
158
|
updateNode(nodeId, node) {
|
166
159
|
this._assertHasNodeId(nodeId);
|
167
|
-
this.nodes
|
160
|
+
this.nodes[nodeId] = node;
|
168
161
|
}
|
169
162
|
|
170
163
|
// Update a node's downstream nodes making sure to prune any orphaned branches
|
@@ -183,11 +176,16 @@ class Graph {
|
|
183
176
|
}
|
184
177
|
}
|
185
178
|
traverse(visit, startNodeId, type = 1) {
|
186
|
-
|
187
|
-
|
188
|
-
startNodeId
|
189
|
-
|
190
|
-
|
179
|
+
let enter = typeof visit === 'function' ? visit : visit.enter;
|
180
|
+
if (type === ALL_EDGE_TYPES && enter && (typeof visit === 'function' || !visit.exit)) {
|
181
|
+
return this.dfsFast(enter, startNodeId);
|
182
|
+
} else {
|
183
|
+
return this.dfs({
|
184
|
+
visit,
|
185
|
+
startNodeId,
|
186
|
+
getChildren: nodeId => this.getNodeIdsConnectedFrom(nodeId, type)
|
187
|
+
});
|
188
|
+
}
|
191
189
|
}
|
192
190
|
filteredTraverse(filter, visit, startNodeId, type) {
|
193
191
|
return this.traverse(mapVisitor(filter, visit), startNodeId, type);
|
@@ -199,6 +197,66 @@ class Graph {
|
|
199
197
|
getChildren: nodeId => this.getNodeIdsConnectedTo(nodeId, type)
|
200
198
|
});
|
201
199
|
}
|
200
|
+
dfsFast(visit, startNodeId) {
|
201
|
+
let traversalStartNode = (0, _nullthrows().default)(startNodeId !== null && startNodeId !== void 0 ? startNodeId : this.rootNodeId, 'A start node is required to traverse');
|
202
|
+
this._assertHasNodeId(traversalStartNode);
|
203
|
+
let visited;
|
204
|
+
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
205
|
+
this._visited = new _BitSet.BitSet(this.nodes.length);
|
206
|
+
visited = this._visited;
|
207
|
+
} else {
|
208
|
+
visited = this._visited;
|
209
|
+
visited.clear();
|
210
|
+
}
|
211
|
+
// Take shared instance to avoid re-entrancy issues.
|
212
|
+
this._visited = null;
|
213
|
+
let stopped = false;
|
214
|
+
let skipped = false;
|
215
|
+
let actions = {
|
216
|
+
skipChildren() {
|
217
|
+
skipped = true;
|
218
|
+
},
|
219
|
+
stop() {
|
220
|
+
stopped = true;
|
221
|
+
}
|
222
|
+
};
|
223
|
+
let queue = [{
|
224
|
+
nodeId: traversalStartNode,
|
225
|
+
context: null
|
226
|
+
}];
|
227
|
+
while (queue.length !== 0) {
|
228
|
+
let {
|
229
|
+
nodeId,
|
230
|
+
context
|
231
|
+
} = queue.pop();
|
232
|
+
if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
|
233
|
+
visited.add(nodeId);
|
234
|
+
skipped = false;
|
235
|
+
let newContext = visit(nodeId, context, actions);
|
236
|
+
if (typeof newContext !== 'undefined') {
|
237
|
+
// $FlowFixMe[reassign-const]
|
238
|
+
context = newContext;
|
239
|
+
}
|
240
|
+
if (skipped) {
|
241
|
+
continue;
|
242
|
+
}
|
243
|
+
if (stopped) {
|
244
|
+
this._visited = visited;
|
245
|
+
return context;
|
246
|
+
}
|
247
|
+
this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, child => {
|
248
|
+
if (!visited.has(child)) {
|
249
|
+
queue.push({
|
250
|
+
nodeId: child,
|
251
|
+
context
|
252
|
+
});
|
253
|
+
}
|
254
|
+
return false;
|
255
|
+
});
|
256
|
+
}
|
257
|
+
this._visited = visited;
|
258
|
+
return null;
|
259
|
+
}
|
202
260
|
dfs({
|
203
261
|
visit,
|
204
262
|
startNodeId,
|
@@ -206,7 +264,16 @@ class Graph {
|
|
206
264
|
}) {
|
207
265
|
let traversalStartNode = (0, _nullthrows().default)(startNodeId !== null && startNodeId !== void 0 ? startNodeId : this.rootNodeId, 'A start node is required to traverse');
|
208
266
|
this._assertHasNodeId(traversalStartNode);
|
209
|
-
let visited
|
267
|
+
let visited;
|
268
|
+
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
269
|
+
this._visited = new _BitSet.BitSet(this.nodes.length);
|
270
|
+
visited = this._visited;
|
271
|
+
} else {
|
272
|
+
visited = this._visited;
|
273
|
+
visited.clear();
|
274
|
+
}
|
275
|
+
// Take shared instance to avoid re-entrancy issues.
|
276
|
+
this._visited = null;
|
210
277
|
let stopped = false;
|
211
278
|
let skipped = false;
|
212
279
|
let actions = {
|
@@ -261,7 +328,9 @@ class Graph {
|
|
261
328
|
return context;
|
262
329
|
}
|
263
330
|
};
|
264
|
-
|
331
|
+
let result = walk(traversalStartNode);
|
332
|
+
this._visited = visited;
|
333
|
+
return result;
|
265
334
|
}
|
266
335
|
bfs(visit) {
|
267
336
|
let rootNodeId = (0, _nullthrows().default)(this.rootNodeId, 'A root node is required to traverse');
|
package/lib/index.js
CHANGED
@@ -9,6 +9,12 @@ Object.defineProperty(exports, "ALL_EDGE_TYPES", {
|
|
9
9
|
return _Graph.ALL_EDGE_TYPES;
|
10
10
|
}
|
11
11
|
});
|
12
|
+
Object.defineProperty(exports, "BitSet", {
|
13
|
+
enumerable: true,
|
14
|
+
get: function () {
|
15
|
+
return _BitSet.BitSet;
|
16
|
+
}
|
17
|
+
});
|
12
18
|
Object.defineProperty(exports, "ContentGraph", {
|
13
19
|
enumerable: true,
|
14
20
|
get: function () {
|
@@ -42,6 +48,7 @@ Object.defineProperty(exports, "toNodeId", {
|
|
42
48
|
var _types = require("./types");
|
43
49
|
var _Graph = _interopRequireWildcard(require("./Graph"));
|
44
50
|
var _ContentGraph = _interopRequireDefault(require("./ContentGraph"));
|
51
|
+
var _BitSet = require("./BitSet");
|
45
52
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
46
53
|
function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
|
47
54
|
function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@parcel/graph",
|
3
|
-
"version": "2.9.4-nightly.
|
3
|
+
"version": "2.9.4-nightly.3008+4499a71ba",
|
4
4
|
"description": "Blazing fast, zero configuration web application bundler",
|
5
5
|
"license": "MIT",
|
6
6
|
"publishConfig": {
|
@@ -20,7 +20,8 @@
|
|
20
20
|
"node": ">= 12.0.0"
|
21
21
|
},
|
22
22
|
"dependencies": {
|
23
|
+
"@parcel/utils": "2.0.0-nightly.1385+4499a71ba",
|
23
24
|
"nullthrows": "^1.1.1"
|
24
25
|
},
|
25
|
-
"gitHead": "
|
26
|
+
"gitHead": "4499a71ba8ba8797117a07244f1c8848f2d5dedb"
|
26
27
|
}
|
package/src/AdjacencyList.js
CHANGED
@@ -467,6 +467,24 @@ export default class AdjacencyList<TEdgeType: number = 1> {
|
|
467
467
|
return nodes;
|
468
468
|
}
|
469
469
|
|
470
|
+
forEachNodeIdConnectedFromReverse(
|
471
|
+
from: NodeId,
|
472
|
+
fn: (nodeId: NodeId) => boolean,
|
473
|
+
) {
|
474
|
+
let node = this.#nodes.head(from);
|
475
|
+
while (node !== null) {
|
476
|
+
let edge = this.#nodes.lastOut(node);
|
477
|
+
while (edge !== null) {
|
478
|
+
let to = this.#edges.to(edge);
|
479
|
+
if (fn(to)) {
|
480
|
+
return;
|
481
|
+
}
|
482
|
+
edge = this.#edges.prevOut(edge);
|
483
|
+
}
|
484
|
+
node = this.#nodes.next(node);
|
485
|
+
}
|
486
|
+
}
|
487
|
+
|
470
488
|
/**
|
471
489
|
* Get the list of nodes connected to this node.
|
472
490
|
*/
|
package/src/BitSet.js
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
// @flow strict-local
|
2
|
+
|
3
|
+
// Small wasm program that exposes the `ctz` instruction.
|
4
|
+
// https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Numeric/Count_trailing_zeros
|
5
|
+
const wasmBuf = new Uint8Array([
|
6
|
+
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x06, 0x01, 0x60, 0x01,
|
7
|
+
0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07, 0x0d, 0x01, 0x09, 0x74, 0x72,
|
8
|
+
0x61, 0x69, 0x6c, 0x69, 0x6e, 0x67, 0x30, 0x00, 0x00, 0x0a, 0x07, 0x01, 0x05,
|
9
|
+
0x00, 0x20, 0x00, 0x68, 0x0b, 0x00, 0x0f, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x02,
|
10
|
+
0x08, 0x01, 0x00, 0x01, 0x00, 0x03, 0x6e, 0x75, 0x6d,
|
11
|
+
]);
|
12
|
+
|
13
|
+
// eslint-disable-next-line
|
14
|
+
const {trailing0} = new WebAssembly.Instance(new WebAssembly.Module(wasmBuf))
|
15
|
+
.exports;
|
16
|
+
|
17
|
+
export class BitSet {
|
18
|
+
bits: Uint32Array;
|
19
|
+
|
20
|
+
constructor(maxBits: number) {
|
21
|
+
this.bits = new Uint32Array(Math.ceil(maxBits / 32));
|
22
|
+
}
|
23
|
+
|
24
|
+
clone(): BitSet {
|
25
|
+
let res = new BitSet(this.capacity);
|
26
|
+
res.bits.set(this.bits);
|
27
|
+
return res;
|
28
|
+
}
|
29
|
+
|
30
|
+
static union(a: BitSet, b: BitSet): BitSet {
|
31
|
+
let res = a.clone();
|
32
|
+
res.union(b);
|
33
|
+
return res;
|
34
|
+
}
|
35
|
+
|
36
|
+
get capacity(): number {
|
37
|
+
return this.bits.length * 32;
|
38
|
+
}
|
39
|
+
|
40
|
+
add(bit: number) {
|
41
|
+
let i = bit >>> 5;
|
42
|
+
let b = bit & 31;
|
43
|
+
this.bits[i] |= 1 << b;
|
44
|
+
}
|
45
|
+
|
46
|
+
delete(bit: number) {
|
47
|
+
let i = bit >>> 5;
|
48
|
+
let b = bit & 31;
|
49
|
+
this.bits[i] &= ~(1 << b);
|
50
|
+
}
|
51
|
+
|
52
|
+
has(bit: number): boolean {
|
53
|
+
let i = bit >>> 5;
|
54
|
+
let b = bit & 31;
|
55
|
+
return Boolean(this.bits[i] & (1 << b));
|
56
|
+
}
|
57
|
+
|
58
|
+
clear() {
|
59
|
+
this.bits.fill(0);
|
60
|
+
}
|
61
|
+
|
62
|
+
intersect(other: BitSet) {
|
63
|
+
for (let i = 0; i < this.bits.length; i++) {
|
64
|
+
this.bits[i] &= other.bits[i];
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
union(other: BitSet) {
|
69
|
+
for (let i = 0; i < this.bits.length; i++) {
|
70
|
+
this.bits[i] |= other.bits[i];
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
remove(other: BitSet) {
|
75
|
+
for (let i = 0; i < this.bits.length; i++) {
|
76
|
+
this.bits[i] &= ~other.bits[i];
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
forEach(fn: (bit: number) => void) {
|
81
|
+
// https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
|
82
|
+
let bits = this.bits;
|
83
|
+
for (let k = 0; k < bits.length; k++) {
|
84
|
+
let v = bits[k];
|
85
|
+
while (v !== 0) {
|
86
|
+
let t = (v & -v) >>> 0;
|
87
|
+
// $FlowFixMe
|
88
|
+
fn((k << 5) + trailing0(v));
|
89
|
+
v ^= t;
|
90
|
+
}
|
91
|
+
}
|
92
|
+
}
|
93
|
+
}
|
package/src/Graph.js
CHANGED
@@ -3,20 +3,24 @@
|
|
3
3
|
import {fromNodeId} from './types';
|
4
4
|
import AdjacencyList, {type SerializedAdjacencyList} from './AdjacencyList';
|
5
5
|
import type {Edge, NodeId} from './types';
|
6
|
-
import type {
|
6
|
+
import type {
|
7
|
+
TraversalActions,
|
8
|
+
GraphVisitor,
|
9
|
+
GraphTraversalCallback,
|
10
|
+
} from '@parcel/types';
|
11
|
+
import {BitSet} from './BitSet';
|
7
12
|
|
8
|
-
import assert from 'assert';
|
9
13
|
import nullthrows from 'nullthrows';
|
10
14
|
|
11
15
|
export type NullEdgeType = 1;
|
12
16
|
export type GraphOpts<TNode, TEdgeType: number = 1> = {|
|
13
|
-
nodes?:
|
17
|
+
nodes?: Array<TNode | null>,
|
14
18
|
adjacencyList?: SerializedAdjacencyList<TEdgeType>,
|
15
19
|
rootNodeId?: ?NodeId,
|
16
20
|
|};
|
17
21
|
|
18
22
|
export type SerializedGraph<TNode, TEdgeType: number = 1> = {|
|
19
|
-
nodes:
|
23
|
+
nodes: Array<TNode | null>,
|
20
24
|
adjacencyList: SerializedAdjacencyList<TEdgeType>,
|
21
25
|
rootNodeId: ?NodeId,
|
22
26
|
|};
|
@@ -25,12 +29,13 @@ export type AllEdgeTypes = -1;
|
|
25
29
|
export const ALL_EDGE_TYPES: AllEdgeTypes = -1;
|
26
30
|
|
27
31
|
export default class Graph<TNode, TEdgeType: number = 1> {
|
28
|
-
nodes:
|
32
|
+
nodes: Array<TNode | null>;
|
29
33
|
adjacencyList: AdjacencyList<TEdgeType>;
|
30
34
|
rootNodeId: ?NodeId;
|
35
|
+
_visited: ?BitSet;
|
31
36
|
|
32
37
|
constructor(opts: ?GraphOpts<TNode, TEdgeType>) {
|
33
|
-
this.nodes = opts?.nodes ||
|
38
|
+
this.nodes = opts?.nodes || [];
|
34
39
|
this.setRootNodeId(opts?.rootNodeId);
|
35
40
|
|
36
41
|
let adjacencyList = opts?.adjacencyList;
|
@@ -69,16 +74,16 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
69
74
|
|
70
75
|
addNode(node: TNode): NodeId {
|
71
76
|
let id = this.adjacencyList.addNode();
|
72
|
-
this.nodes.
|
77
|
+
this.nodes.push(node);
|
73
78
|
return id;
|
74
79
|
}
|
75
80
|
|
76
81
|
hasNode(id: NodeId): boolean {
|
77
|
-
return this.nodes
|
82
|
+
return this.nodes[id] != null;
|
78
83
|
}
|
79
84
|
|
80
85
|
getNode(id: NodeId): ?TNode {
|
81
|
-
return this.nodes
|
86
|
+
return this.nodes[id];
|
82
87
|
}
|
83
88
|
|
84
89
|
addEdge(
|
@@ -90,11 +95,11 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
90
95
|
throw new Error(`Edge type "${type}" not allowed`);
|
91
96
|
}
|
92
97
|
|
93
|
-
if (
|
98
|
+
if (this.getNode(from) == null) {
|
94
99
|
throw new Error(`"from" node '${fromNodeId(from)}' not found`);
|
95
100
|
}
|
96
101
|
|
97
|
-
if (
|
102
|
+
if (this.getNode(to) == null) {
|
98
103
|
throw new Error(`"to" node '${fromNodeId(to)}' not found`);
|
99
104
|
}
|
100
105
|
|
@@ -156,8 +161,7 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
156
161
|
this._removeEdge(nodeId, to, type);
|
157
162
|
}
|
158
163
|
|
159
|
-
|
160
|
-
assert(wasRemoved);
|
164
|
+
this.nodes[nodeId] = null;
|
161
165
|
}
|
162
166
|
|
163
167
|
removeEdges(nodeId: NodeId, type: TEdgeType | NullEdgeType = 1) {
|
@@ -237,7 +241,7 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
237
241
|
|
238
242
|
updateNode(nodeId: NodeId, node: TNode): void {
|
239
243
|
this._assertHasNodeId(nodeId);
|
240
|
-
this.nodes
|
244
|
+
this.nodes[nodeId] = node;
|
241
245
|
}
|
242
246
|
|
243
247
|
// Update a node's downstream nodes making sure to prune any orphaned branches
|
@@ -277,11 +281,20 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
277
281
|
| Array<TEdgeType | NullEdgeType>
|
278
282
|
| AllEdgeTypes = 1,
|
279
283
|
): ?TContext {
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
284
|
+
let enter = typeof visit === 'function' ? visit : visit.enter;
|
285
|
+
if (
|
286
|
+
type === ALL_EDGE_TYPES &&
|
287
|
+
enter &&
|
288
|
+
(typeof visit === 'function' || !visit.exit)
|
289
|
+
) {
|
290
|
+
return this.dfsFast(enter, startNodeId);
|
291
|
+
} else {
|
292
|
+
return this.dfs({
|
293
|
+
visit,
|
294
|
+
startNodeId,
|
295
|
+
getChildren: nodeId => this.getNodeIdsConnectedFrom(nodeId, type),
|
296
|
+
});
|
297
|
+
}
|
285
298
|
}
|
286
299
|
|
287
300
|
filteredTraverse<TValue, TContext>(
|
@@ -309,6 +322,72 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
309
322
|
});
|
310
323
|
}
|
311
324
|
|
325
|
+
dfsFast<TContext>(
|
326
|
+
visit: GraphTraversalCallback<NodeId, TContext>,
|
327
|
+
startNodeId: ?NodeId,
|
328
|
+
): ?TContext {
|
329
|
+
let traversalStartNode = nullthrows(
|
330
|
+
startNodeId ?? this.rootNodeId,
|
331
|
+
'A start node is required to traverse',
|
332
|
+
);
|
333
|
+
this._assertHasNodeId(traversalStartNode);
|
334
|
+
|
335
|
+
let visited;
|
336
|
+
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
337
|
+
this._visited = new BitSet(this.nodes.length);
|
338
|
+
visited = this._visited;
|
339
|
+
} else {
|
340
|
+
visited = this._visited;
|
341
|
+
visited.clear();
|
342
|
+
}
|
343
|
+
// Take shared instance to avoid re-entrancy issues.
|
344
|
+
this._visited = null;
|
345
|
+
|
346
|
+
let stopped = false;
|
347
|
+
let skipped = false;
|
348
|
+
let actions: TraversalActions = {
|
349
|
+
skipChildren() {
|
350
|
+
skipped = true;
|
351
|
+
},
|
352
|
+
stop() {
|
353
|
+
stopped = true;
|
354
|
+
},
|
355
|
+
};
|
356
|
+
|
357
|
+
let queue = [{nodeId: traversalStartNode, context: null}];
|
358
|
+
while (queue.length !== 0) {
|
359
|
+
let {nodeId, context} = queue.pop();
|
360
|
+
if (!this.hasNode(nodeId) || visited.has(nodeId)) continue;
|
361
|
+
visited.add(nodeId);
|
362
|
+
|
363
|
+
skipped = false;
|
364
|
+
let newContext = visit(nodeId, context, actions);
|
365
|
+
if (typeof newContext !== 'undefined') {
|
366
|
+
// $FlowFixMe[reassign-const]
|
367
|
+
context = newContext;
|
368
|
+
}
|
369
|
+
|
370
|
+
if (skipped) {
|
371
|
+
continue;
|
372
|
+
}
|
373
|
+
|
374
|
+
if (stopped) {
|
375
|
+
this._visited = visited;
|
376
|
+
return context;
|
377
|
+
}
|
378
|
+
|
379
|
+
this.adjacencyList.forEachNodeIdConnectedFromReverse(nodeId, child => {
|
380
|
+
if (!visited.has(child)) {
|
381
|
+
queue.push({nodeId: child, context});
|
382
|
+
}
|
383
|
+
return false;
|
384
|
+
});
|
385
|
+
}
|
386
|
+
|
387
|
+
this._visited = visited;
|
388
|
+
return null;
|
389
|
+
}
|
390
|
+
|
312
391
|
dfs<TContext>({
|
313
392
|
visit,
|
314
393
|
startNodeId,
|
@@ -324,7 +403,17 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
324
403
|
);
|
325
404
|
this._assertHasNodeId(traversalStartNode);
|
326
405
|
|
327
|
-
let visited
|
406
|
+
let visited;
|
407
|
+
if (!this._visited || this._visited.capacity < this.nodes.length) {
|
408
|
+
this._visited = new BitSet(this.nodes.length);
|
409
|
+
visited = this._visited;
|
410
|
+
} else {
|
411
|
+
visited = this._visited;
|
412
|
+
visited.clear();
|
413
|
+
}
|
414
|
+
// Take shared instance to avoid re-entrancy issues.
|
415
|
+
this._visited = null;
|
416
|
+
|
328
417
|
let stopped = false;
|
329
418
|
let skipped = false;
|
330
419
|
let actions: TraversalActions = {
|
@@ -392,7 +481,9 @@ export default class Graph<TNode, TEdgeType: number = 1> {
|
|
392
481
|
}
|
393
482
|
};
|
394
483
|
|
395
|
-
|
484
|
+
let result = walk(traversalStartNode);
|
485
|
+
this._visited = visited;
|
486
|
+
return result;
|
396
487
|
}
|
397
488
|
|
398
489
|
bfs(visit: (nodeId: NodeId) => ?boolean): ?NodeId {
|
package/src/index.js
CHANGED
@@ -6,3 +6,4 @@ export type {ContentGraphOpts, SerializedContentGraph} from './ContentGraph';
|
|
6
6
|
export {toNodeId, fromNodeId} from './types';
|
7
7
|
export {default as Graph, ALL_EDGE_TYPES, mapVisitor} from './Graph';
|
8
8
|
export {default as ContentGraph} from './ContentGraph';
|
9
|
+
export {BitSet} from './BitSet';
|
package/src/types.js
CHANGED
@@ -0,0 +1,98 @@
|
|
1
|
+
// @flow strict-local
|
2
|
+
|
3
|
+
import assert from 'assert';
|
4
|
+
import {BitSet} from '../src/BitSet';
|
5
|
+
|
6
|
+
function assertValues(set: BitSet, values: Array<number>) {
|
7
|
+
let setValues = [];
|
8
|
+
set.forEach(bit => {
|
9
|
+
setValues.push(bit);
|
10
|
+
});
|
11
|
+
|
12
|
+
for (let value of values) {
|
13
|
+
assert(set.has(value), 'Set.has returned false');
|
14
|
+
assert(
|
15
|
+
setValues.some(v => v === value),
|
16
|
+
'Set values is missing value',
|
17
|
+
);
|
18
|
+
}
|
19
|
+
|
20
|
+
assert(
|
21
|
+
setValues.length === values.length,
|
22
|
+
`Expected ${values.length} values but got ${setValues.length}`,
|
23
|
+
);
|
24
|
+
}
|
25
|
+
|
26
|
+
describe('BitSet', () => {
|
27
|
+
it('clone should return a set with the same values', () => {
|
28
|
+
let set1 = new BitSet(5);
|
29
|
+
set1.add(1);
|
30
|
+
set1.add(3);
|
31
|
+
|
32
|
+
let set2 = set1.clone();
|
33
|
+
|
34
|
+
assertValues(set2, [1, 3]);
|
35
|
+
});
|
36
|
+
|
37
|
+
it('clear should remove all values from the set', () => {
|
38
|
+
let set1 = new BitSet(5);
|
39
|
+
set1.add(1);
|
40
|
+
set1.add(3);
|
41
|
+
|
42
|
+
set1.clear();
|
43
|
+
|
44
|
+
assertValues(set1, []);
|
45
|
+
});
|
46
|
+
|
47
|
+
it('delete should remove values from the set', () => {
|
48
|
+
let set1 = new BitSet(5);
|
49
|
+
set1.add(1);
|
50
|
+
set1.add(3);
|
51
|
+
set1.add(5);
|
52
|
+
|
53
|
+
set1.delete(3);
|
54
|
+
|
55
|
+
assertValues(set1, [1, 5]);
|
56
|
+
});
|
57
|
+
|
58
|
+
it('should intersect with another BitSet', () => {
|
59
|
+
let set1 = new BitSet(5);
|
60
|
+
set1.add(1);
|
61
|
+
set1.add(3);
|
62
|
+
|
63
|
+
let set2 = new BitSet(5);
|
64
|
+
set2.add(3);
|
65
|
+
set2.add(5);
|
66
|
+
|
67
|
+
set1.intersect(set2);
|
68
|
+
assertValues(set1, [3]);
|
69
|
+
});
|
70
|
+
|
71
|
+
it('should union with another BitSet', () => {
|
72
|
+
let set1 = new BitSet(5);
|
73
|
+
set1.add(1);
|
74
|
+
set1.add(3);
|
75
|
+
|
76
|
+
let set2 = new BitSet(5);
|
77
|
+
set2.add(3);
|
78
|
+
set2.add(5);
|
79
|
+
|
80
|
+
set1.union(set2);
|
81
|
+
assertValues(set1, [1, 3, 5]);
|
82
|
+
});
|
83
|
+
|
84
|
+
it('BitSet.union should create a new BitSet with the union', () => {
|
85
|
+
let set1 = new BitSet(5);
|
86
|
+
set1.add(1);
|
87
|
+
set1.add(3);
|
88
|
+
|
89
|
+
let set2 = new BitSet(5);
|
90
|
+
set2.add(3);
|
91
|
+
set2.add(5);
|
92
|
+
|
93
|
+
let set3 = BitSet.union(set1, set2);
|
94
|
+
assertValues(set1, [1, 3]);
|
95
|
+
assertValues(set2, [3, 5]);
|
96
|
+
assertValues(set3, [1, 3, 5]);
|
97
|
+
});
|
98
|
+
});
|
package/test/Graph.test.js
CHANGED
@@ -9,7 +9,7 @@ import {toNodeId} from '../src/types';
|
|
9
9
|
describe('Graph', () => {
|
10
10
|
it('constructor should initialize an empty graph', () => {
|
11
11
|
let graph = new Graph();
|
12
|
-
assert.deepEqual(graph.nodes,
|
12
|
+
assert.deepEqual(graph.nodes, []);
|
13
13
|
assert.deepEqual([...graph.getAllEdges()], []);
|
14
14
|
});
|
15
15
|
|
@@ -17,7 +17,7 @@ describe('Graph', () => {
|
|
17
17
|
let graph = new Graph();
|
18
18
|
let node = {};
|
19
19
|
let id = graph.addNode(node);
|
20
|
-
assert.equal(graph.
|
20
|
+
assert.equal(graph.getNode(id), node);
|
21
21
|
});
|
22
22
|
|
23
23
|
it('errors when traversing a graph with no root', () => {
|
@@ -117,10 +117,10 @@ describe('Graph', () => {
|
|
117
117
|
graph.addEdge(nodeB, nodeD);
|
118
118
|
|
119
119
|
graph.removeEdge(nodeA, nodeB);
|
120
|
-
assert(graph.
|
121
|
-
assert(graph.
|
122
|
-
assert(!graph.
|
123
|
-
assert(!graph.
|
120
|
+
assert(graph.hasNode(nodeA));
|
121
|
+
assert(graph.hasNode(nodeD));
|
122
|
+
assert(!graph.hasNode(nodeB));
|
123
|
+
assert(!graph.hasNode(nodeC));
|
124
124
|
assert.deepEqual(
|
125
125
|
[...graph.getAllEdges()],
|
126
126
|
[{from: nodeA, to: nodeD, type: 1}],
|
@@ -164,7 +164,7 @@ describe('Graph', () => {
|
|
164
164
|
|
165
165
|
graph.removeNode(nodeB);
|
166
166
|
|
167
|
-
assert.deepEqual(
|
167
|
+
assert.deepEqual(graph.nodes.filter(Boolean), ['a', 'c', 'f']);
|
168
168
|
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
169
169
|
{from: nodeA, to: nodeC, type: 1},
|
170
170
|
{from: nodeC, to: nodeF, type: 1},
|
@@ -209,7 +209,7 @@ describe('Graph', () => {
|
|
209
209
|
|
210
210
|
graph.removeNode(nodeB);
|
211
211
|
|
212
|
-
assert.deepEqual(
|
212
|
+
assert.deepEqual(graph.nodes.filter(Boolean), ['a', 'c', 'f']);
|
213
213
|
assert.deepEqual(Array.from(graph.getAllEdges()), [
|
214
214
|
{from: nodeA, to: nodeC, type: 1},
|
215
215
|
{from: nodeC, to: nodeF, type: 1},
|
@@ -337,7 +337,7 @@ describe('Graph', () => {
|
|
337
337
|
|
338
338
|
graph.removeNode(node1);
|
339
339
|
|
340
|
-
assert.
|
340
|
+
assert.deepEqual(graph.nodes.filter(Boolean), ['root']);
|
341
341
|
assert.deepStrictEqual(Array.from(graph.getAllEdges()), []);
|
342
342
|
});
|
343
343
|
});
|