@bablr/btree 0.1.1 → 0.3.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/lib/enhanceable.js +105 -92
- package/lib/index.js +5 -6
- package/package.json +4 -1
package/lib/enhanceable.js
CHANGED
|
@@ -1,43 +1,65 @@
|
|
|
1
1
|
import emptyStack from '@iter-tools/imm-stack';
|
|
2
2
|
|
|
3
3
|
const { isArray } = Array;
|
|
4
|
-
const { freeze
|
|
4
|
+
const { freeze } = Object;
|
|
5
5
|
const { isFinite } = Number;
|
|
6
6
|
|
|
7
|
-
export const
|
|
7
|
+
export const defaultNodeSize = 8;
|
|
8
|
+
|
|
9
|
+
export const buildModule = (
|
|
10
|
+
NODE_SIZE = defaultNodeSize,
|
|
11
|
+
statsReducer,
|
|
12
|
+
getSumsInitial,
|
|
13
|
+
finalizeSums,
|
|
14
|
+
) => {
|
|
8
15
|
const sumNodes = (nodes) => {
|
|
9
|
-
return nodes.map(
|
|
16
|
+
return nodes.map(getSize).reduce((a, b) => a + b, 0);
|
|
10
17
|
};
|
|
11
18
|
|
|
12
|
-
const treeFrom = (...
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
const treeFrom = (...values) => {
|
|
20
|
+
if (!statsReducer && values.length <= NODE_SIZE) {
|
|
21
|
+
return freeze(values);
|
|
22
|
+
} else {
|
|
23
|
+
let tree = [];
|
|
24
|
+
for (const value of values) {
|
|
25
|
+
tree = push(tree, value);
|
|
26
|
+
}
|
|
27
|
+
return tree;
|
|
18
28
|
}
|
|
19
|
-
return tree;
|
|
20
29
|
};
|
|
21
30
|
|
|
22
|
-
const
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
const buildStats = (values) => {
|
|
32
|
+
let sums = values.reduce(statsReducer, getSumsInitial());
|
|
33
|
+
finalizeSums(sums);
|
|
34
|
+
return sums;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const treeFromValues = (values) => {
|
|
38
|
+
if (!statsReducer && values.length <= NODE_SIZE) {
|
|
39
|
+
return isArray(values[0]) ? freeze([sumNodes(values), freeze(values)]) : freeze(values);
|
|
40
|
+
} else {
|
|
41
|
+
if (values.length <= NODE_SIZE) {
|
|
42
|
+
if (statsReducer) {
|
|
43
|
+
return freeze([sumNodes(values), freeze(values), buildStats(values)]);
|
|
44
|
+
} else {
|
|
45
|
+
return freeze([sumNodes(values), freeze(values)]);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
throw new Error();
|
|
49
|
+
}
|
|
26
50
|
}
|
|
27
|
-
return tree;
|
|
28
51
|
};
|
|
29
52
|
|
|
30
|
-
const findBalancePoint = (
|
|
31
|
-
const values = isFinite(tree[0]) ? tree[1] : tree;
|
|
53
|
+
const findBalancePoint = (values) => {
|
|
32
54
|
let leftSum = 0;
|
|
33
|
-
let rightSum =
|
|
55
|
+
let rightSum = values.reduce((sum, v) => sum + getSize(v), 0);
|
|
34
56
|
let balance = leftSum / rightSum;
|
|
35
57
|
|
|
36
58
|
if (!values.length) return null;
|
|
37
59
|
if (values.length === 1) return 1;
|
|
38
60
|
|
|
39
61
|
for (let i = 1; i < values.length; i++) {
|
|
40
|
-
const sum =
|
|
62
|
+
const sum = getSize(values[i - 1]);
|
|
41
63
|
const lastBalance = balance;
|
|
42
64
|
leftSum += sum;
|
|
43
65
|
rightSum -= sum;
|
|
@@ -51,48 +73,47 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
51
73
|
return values.length - 1;
|
|
52
74
|
};
|
|
53
75
|
|
|
54
|
-
const
|
|
55
|
-
const
|
|
56
|
-
const isLeaf = isLeafNode(tree);
|
|
76
|
+
const splitValues = (values) => {
|
|
77
|
+
const isLeaf = !isArray(values[0]);
|
|
57
78
|
|
|
58
79
|
let midIndex;
|
|
59
80
|
|
|
60
81
|
if (isLeaf) {
|
|
61
82
|
midIndex = Math.floor(values.length / 2 + 0.01);
|
|
62
83
|
} else {
|
|
63
|
-
midIndex = findBalancePoint(
|
|
84
|
+
midIndex = findBalancePoint(values);
|
|
64
85
|
}
|
|
65
86
|
|
|
66
87
|
let leftValues = values.slice(0, midIndex);
|
|
67
88
|
let rightValues = values.slice(midIndex);
|
|
68
89
|
|
|
69
|
-
|
|
70
|
-
let right = isLeaf ? rightValues : [sumNodes(rightValues), rightValues];
|
|
71
|
-
|
|
72
|
-
return { left, right };
|
|
90
|
+
return { leftValues, rightValues };
|
|
73
91
|
};
|
|
74
92
|
|
|
75
93
|
const setValuesAt = (idx, node, value) => {
|
|
76
94
|
const isLeaf = isLeafNode(node);
|
|
77
|
-
const values =
|
|
95
|
+
const values = getValues(node);
|
|
96
|
+
|
|
97
|
+
if (isArray(value)) Error('cannot set arrays as btree values');
|
|
78
98
|
|
|
79
99
|
if (!isLeaf && !isArray(value)) {
|
|
80
100
|
throw new Error();
|
|
81
101
|
}
|
|
82
102
|
|
|
83
|
-
|
|
84
|
-
const newSize = isLeaf ? 1 : getSum(value);
|
|
85
|
-
|
|
86
|
-
// TODO mutable sets?
|
|
103
|
+
if (isLeaf && isArray(value) && node.length) throw new Error();
|
|
87
104
|
|
|
88
105
|
const newValues = [...values];
|
|
89
106
|
newValues[idx] = value;
|
|
90
|
-
|
|
91
|
-
return isLeaf ? newValues : freezeObject([node[0] + newSize - oldSize, newValues]);
|
|
107
|
+
return treeFromValues(newValues);
|
|
92
108
|
};
|
|
93
109
|
|
|
94
110
|
const addAt = (idx, tree, value) => {
|
|
95
111
|
if (idx < 0) throw new Error('invalid argument');
|
|
112
|
+
if (!isArray(tree)) throw new Error();
|
|
113
|
+
|
|
114
|
+
let isLeaf = isLeafNode(tree);
|
|
115
|
+
|
|
116
|
+
if (isArray(value)) throw new Error('cannot add arrays to btrees');
|
|
96
117
|
|
|
97
118
|
let path = findPath(idx, tree);
|
|
98
119
|
|
|
@@ -101,20 +122,23 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
101
122
|
// left pushout vs right pushout?
|
|
102
123
|
let pushout = value;
|
|
103
124
|
|
|
104
|
-
let values =
|
|
125
|
+
let values = getValues(node);
|
|
105
126
|
|
|
106
127
|
for (;;) {
|
|
107
128
|
if (pushout) {
|
|
108
129
|
values = [...values];
|
|
130
|
+
|
|
131
|
+
let finiteIndex = index === Infinity ? values.length : index;
|
|
132
|
+
|
|
133
|
+
if (!isFinite(finiteIndex)) throw new Error();
|
|
109
134
|
if (values.length + getValues(pushout).length > NODE_SIZE) {
|
|
110
|
-
values.splice(
|
|
111
|
-
|
|
112
|
-
const { left, right } = split(node);
|
|
135
|
+
values.splice(finiteIndex, 0, pushout);
|
|
136
|
+
const { leftValues, rightValues } = splitValues(values);
|
|
113
137
|
|
|
114
|
-
pushout =
|
|
115
|
-
node =
|
|
138
|
+
pushout = treeFromValues(leftValues);
|
|
139
|
+
node = treeFromValues(rightValues);
|
|
116
140
|
} else {
|
|
117
|
-
values.splice(
|
|
141
|
+
values.splice(finiteIndex, 0, pushout);
|
|
118
142
|
node = setValues(node, values);
|
|
119
143
|
pushout = null;
|
|
120
144
|
}
|
|
@@ -122,7 +146,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
122
146
|
|
|
123
147
|
if (path.size === 1) {
|
|
124
148
|
if (pushout) {
|
|
125
|
-
return
|
|
149
|
+
return treeFromValues([pushout, node]);
|
|
126
150
|
} else {
|
|
127
151
|
return node;
|
|
128
152
|
}
|
|
@@ -139,7 +163,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
139
163
|
};
|
|
140
164
|
|
|
141
165
|
const push = (tree, value) => {
|
|
142
|
-
return addAt(
|
|
166
|
+
return addAt(getSize(tree), tree, value);
|
|
143
167
|
};
|
|
144
168
|
|
|
145
169
|
const collapses = (size) => {
|
|
@@ -159,8 +183,10 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
159
183
|
|
|
160
184
|
let { node, index } = path.value;
|
|
161
185
|
|
|
162
|
-
const initialValues =
|
|
163
|
-
let returnValue = isLeafNode(node)
|
|
186
|
+
const initialValues = freeze(getValues(node).slice(0, -1));
|
|
187
|
+
let returnValue = isLeafNode(node)
|
|
188
|
+
? initialValues
|
|
189
|
+
: freeze([sumNodes(initialValues), initialValues]);
|
|
164
190
|
|
|
165
191
|
for (;;) {
|
|
166
192
|
let values = getValues(returnValue);
|
|
@@ -198,7 +224,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
198
224
|
}
|
|
199
225
|
|
|
200
226
|
if (path.size === 1) {
|
|
201
|
-
if (isFinite(returnValue[0]) &&
|
|
227
|
+
if (isFinite(returnValue[0]) && getSize(returnValue) <= NODE_SIZE) {
|
|
202
228
|
returnValue = returnValue[1].flat();
|
|
203
229
|
}
|
|
204
230
|
return returnValue;
|
|
@@ -222,7 +248,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
222
248
|
|
|
223
249
|
const isValidNode = (node) => {
|
|
224
250
|
if (!isArray(node)) return false;
|
|
225
|
-
const values =
|
|
251
|
+
const values = getValues(node);
|
|
226
252
|
return isArray(values); // && values.length <= NODE_SIZE;
|
|
227
253
|
};
|
|
228
254
|
|
|
@@ -235,12 +261,19 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
235
261
|
return node ? (isArray(node) ? (isFinite(node[0]) ? node[1] : node) : [node]) : [];
|
|
236
262
|
};
|
|
237
263
|
|
|
264
|
+
const getSums = (node) => {
|
|
265
|
+
if (!isValidNode(node)) throw new Error();
|
|
266
|
+
return node[2];
|
|
267
|
+
};
|
|
268
|
+
|
|
238
269
|
const setValues = (node, values) => {
|
|
239
|
-
|
|
270
|
+
if (values.length > NODE_SIZE) throw new Error();
|
|
271
|
+
|
|
272
|
+
return isFinite(node[0]) || statsReducer ? treeFromValues(values) : freeze(values);
|
|
240
273
|
};
|
|
241
274
|
|
|
242
275
|
const isLeafNode = (node) => {
|
|
243
|
-
return isArray(node)
|
|
276
|
+
return !isArray(getValues(node)[0]);
|
|
244
277
|
};
|
|
245
278
|
|
|
246
279
|
function* traverse(tree) {
|
|
@@ -253,7 +286,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
253
286
|
const { node } = s;
|
|
254
287
|
const isLeaf = isLeafNode(node);
|
|
255
288
|
|
|
256
|
-
const values =
|
|
289
|
+
const values = getValues(node);
|
|
257
290
|
|
|
258
291
|
if (isLeaf) {
|
|
259
292
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -272,8 +305,10 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
272
305
|
}
|
|
273
306
|
}
|
|
274
307
|
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
308
|
+
const getSize = (tree) => {
|
|
309
|
+
if (tree == null) {
|
|
310
|
+
return 0;
|
|
311
|
+
} else if (!isArray(tree)) {
|
|
277
312
|
return 1;
|
|
278
313
|
} else if (isFinite(tree[0])) {
|
|
279
314
|
return tree[0];
|
|
@@ -290,9 +325,11 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
290
325
|
}
|
|
291
326
|
|
|
292
327
|
const findPath = (idx, tree) => {
|
|
328
|
+
if (idx == null) throw new Error();
|
|
329
|
+
|
|
293
330
|
let path = emptyStack;
|
|
294
331
|
|
|
295
|
-
let treeSum =
|
|
332
|
+
let treeSum = getSize(tree);
|
|
296
333
|
let currentIdx = idx < 0 ? treeSum : 0;
|
|
297
334
|
let direction = idx < 0 ? -1 : 1;
|
|
298
335
|
let targetIdx = idx < 0 ? treeSum + idx : idx;
|
|
@@ -302,11 +339,11 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
302
339
|
assertValidNode(node);
|
|
303
340
|
|
|
304
341
|
if (isLeafNode(node)) {
|
|
305
|
-
const startIdx = idx < 0 ? currentIdx -
|
|
342
|
+
const startIdx = idx < 0 ? currentIdx - getSize(node) : currentIdx;
|
|
306
343
|
let index = isFinite(currentIdx) ? targetIdx - startIdx : currentIdx;
|
|
307
344
|
if (index < 0) {
|
|
308
345
|
index = -Infinity;
|
|
309
|
-
} else if (index >= node
|
|
346
|
+
} else if (index >= getSize(node)) {
|
|
310
347
|
index = Infinity;
|
|
311
348
|
}
|
|
312
349
|
return path.push({ index, node });
|
|
@@ -317,7 +354,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
317
354
|
|
|
318
355
|
for (i of indexes(values.length, idx < 0)) {
|
|
319
356
|
candidateNode = values[i];
|
|
320
|
-
const sum =
|
|
357
|
+
const sum = getSize(candidateNode);
|
|
321
358
|
const nextCount = currentIdx + sum * direction;
|
|
322
359
|
if (idx < 0 ? nextCount <= targetIdx : nextCount > targetIdx || nextCount >= treeSum) {
|
|
323
360
|
path = path.push({ index: i, node });
|
|
@@ -335,12 +372,18 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
335
372
|
|
|
336
373
|
const getAt = (idx, tree) => {
|
|
337
374
|
const v = findPath(idx, tree)?.value;
|
|
338
|
-
return v && v.node[v.index];
|
|
375
|
+
return v && getValues(v.node)[v.index];
|
|
339
376
|
};
|
|
340
377
|
|
|
341
378
|
const replaceAt = (idx, tree, value) => {
|
|
342
379
|
let path = findPath(idx, tree);
|
|
343
380
|
|
|
381
|
+
if (getSize(tree) < idx) {
|
|
382
|
+
throw new Error('Cannot add past the end of a list');
|
|
383
|
+
} else if (getSize(tree) === idx) {
|
|
384
|
+
return addAt(idx, tree, value);
|
|
385
|
+
}
|
|
386
|
+
|
|
344
387
|
let { node, index } = path.value;
|
|
345
388
|
|
|
346
389
|
let returnValue = setValuesAt(index, node, value);
|
|
@@ -359,42 +402,12 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
359
402
|
}
|
|
360
403
|
};
|
|
361
404
|
|
|
362
|
-
const freeze = (tree) => {
|
|
363
|
-
let states = emptyStack.push({ node: tree, i: 0 });
|
|
364
|
-
|
|
365
|
-
stack: while (states.size) {
|
|
366
|
-
const s = states.value;
|
|
367
|
-
const { node } = s;
|
|
368
|
-
const isLeaf = isLeafNode(node);
|
|
369
|
-
|
|
370
|
-
assertValidNode(node);
|
|
371
|
-
|
|
372
|
-
freezeObject(node);
|
|
373
|
-
|
|
374
|
-
const values = isLeaf ? node : node[1];
|
|
375
|
-
|
|
376
|
-
if (!isLeaf) {
|
|
377
|
-
freezeObject(values);
|
|
378
|
-
|
|
379
|
-
for (let { i } = s; s.i < values.length; ) {
|
|
380
|
-
const node = values[i];
|
|
381
|
-
assertValidNode(node);
|
|
382
|
-
states = states.push({ node, i: 0 });
|
|
383
|
-
i = ++s.i;
|
|
384
|
-
continue stack;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
states = states.pop();
|
|
388
|
-
}
|
|
389
|
-
return tree;
|
|
390
|
-
};
|
|
391
|
-
|
|
392
405
|
return {
|
|
393
406
|
buildModule,
|
|
394
|
-
|
|
395
|
-
|
|
407
|
+
treeFrom,
|
|
408
|
+
treeFromValues,
|
|
396
409
|
findBalancePoint,
|
|
397
|
-
|
|
410
|
+
splitValues,
|
|
398
411
|
collapses,
|
|
399
412
|
nodeCollapses,
|
|
400
413
|
nodeCanDonate,
|
|
@@ -404,13 +417,13 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
404
417
|
isValidNode,
|
|
405
418
|
assertValidNode,
|
|
406
419
|
getValues,
|
|
420
|
+
getSums,
|
|
407
421
|
setValues,
|
|
408
422
|
isLeafNode,
|
|
409
423
|
traverse,
|
|
410
|
-
|
|
424
|
+
getSize,
|
|
411
425
|
findPath,
|
|
412
426
|
getAt,
|
|
413
427
|
replaceAt,
|
|
414
|
-
freeze,
|
|
415
428
|
};
|
|
416
429
|
};
|
package/lib/index.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { buildModule } from './enhanceable.js';
|
|
2
2
|
|
|
3
3
|
export const {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
defaultNodeSize,
|
|
5
|
+
treeFromValues,
|
|
7
6
|
findBalancePoint,
|
|
8
|
-
|
|
7
|
+
splitValues,
|
|
9
8
|
collapses,
|
|
10
9
|
nodeCollapses,
|
|
11
10
|
nodeCanDonate,
|
|
@@ -15,12 +14,12 @@ export const {
|
|
|
15
14
|
isValidNode,
|
|
16
15
|
assertValidNode,
|
|
17
16
|
getValues,
|
|
17
|
+
getSums,
|
|
18
18
|
setValues,
|
|
19
19
|
isLeafNode,
|
|
20
20
|
traverse,
|
|
21
|
-
|
|
21
|
+
getSize,
|
|
22
22
|
findPath,
|
|
23
23
|
getAt,
|
|
24
24
|
replaceAt,
|
|
25
|
-
freeze,
|
|
26
25
|
} = buildModule();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/btree",
|
|
3
3
|
"description": "Functional utilities for working with btrees such as those used in agAST",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"author": "Conrad Buck<conartist6@gmail.com>",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"files": [
|
|
@@ -12,6 +12,9 @@
|
|
|
12
12
|
"./enhanceable": "./lib/enhanceable.js"
|
|
13
13
|
},
|
|
14
14
|
"sideEffects": false,
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "mocha test/*.test.js"
|
|
17
|
+
},
|
|
15
18
|
"dependencies": {
|
|
16
19
|
"@iter-tools/imm-stack": "1.1.0"
|
|
17
20
|
},
|