@bablr/btree 0.1.1 → 0.2.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 +90 -83
- package/lib/index.js +3 -5
- package/package.json +4 -1
package/lib/enhanceable.js
CHANGED
|
@@ -1,36 +1,58 @@
|
|
|
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
16
|
return nodes.map(getSum).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 = getSum(
|
|
55
|
+
let rightSum = values.reduce((sum, v) => sum + getSum(v), 0);
|
|
34
56
|
let balance = leftSum / rightSum;
|
|
35
57
|
|
|
36
58
|
if (!values.length) return null;
|
|
@@ -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
|
}
|
|
@@ -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);
|
|
@@ -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
|
|
|
@@ -236,11 +262,13 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
236
262
|
};
|
|
237
263
|
|
|
238
264
|
const setValues = (node, values) => {
|
|
239
|
-
|
|
265
|
+
if (values.length > NODE_SIZE) throw new Error();
|
|
266
|
+
|
|
267
|
+
return isFinite(node[0]) || statsReducer ? treeFromValues(values) : freeze(values);
|
|
240
268
|
};
|
|
241
269
|
|
|
242
270
|
const isLeafNode = (node) => {
|
|
243
|
-
return isArray(node)
|
|
271
|
+
return !isArray(getValues(node)[0]);
|
|
244
272
|
};
|
|
245
273
|
|
|
246
274
|
function* traverse(tree) {
|
|
@@ -253,7 +281,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
253
281
|
const { node } = s;
|
|
254
282
|
const isLeaf = isLeafNode(node);
|
|
255
283
|
|
|
256
|
-
const values =
|
|
284
|
+
const values = getValues(node);
|
|
257
285
|
|
|
258
286
|
if (isLeaf) {
|
|
259
287
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -273,7 +301,9 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
273
301
|
}
|
|
274
302
|
|
|
275
303
|
const getSum = (tree) => {
|
|
276
|
-
if (
|
|
304
|
+
if (tree == null) {
|
|
305
|
+
return 0;
|
|
306
|
+
} else if (!isArray(tree)) {
|
|
277
307
|
return 1;
|
|
278
308
|
} else if (isFinite(tree[0])) {
|
|
279
309
|
return tree[0];
|
|
@@ -290,6 +320,8 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
290
320
|
}
|
|
291
321
|
|
|
292
322
|
const findPath = (idx, tree) => {
|
|
323
|
+
if (idx == null) throw new Error();
|
|
324
|
+
|
|
293
325
|
let path = emptyStack;
|
|
294
326
|
|
|
295
327
|
let treeSum = getSum(tree);
|
|
@@ -306,7 +338,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
306
338
|
let index = isFinite(currentIdx) ? targetIdx - startIdx : currentIdx;
|
|
307
339
|
if (index < 0) {
|
|
308
340
|
index = -Infinity;
|
|
309
|
-
} else if (index >= node
|
|
341
|
+
} else if (index >= getSum(node)) {
|
|
310
342
|
index = Infinity;
|
|
311
343
|
}
|
|
312
344
|
return path.push({ index, node });
|
|
@@ -335,12 +367,18 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
335
367
|
|
|
336
368
|
const getAt = (idx, tree) => {
|
|
337
369
|
const v = findPath(idx, tree)?.value;
|
|
338
|
-
return v && v.node[v.index];
|
|
370
|
+
return v && getValues(v.node)[v.index];
|
|
339
371
|
};
|
|
340
372
|
|
|
341
373
|
const replaceAt = (idx, tree, value) => {
|
|
342
374
|
let path = findPath(idx, tree);
|
|
343
375
|
|
|
376
|
+
if (getSum(tree) < idx) {
|
|
377
|
+
throw new Error('Cannot add past the end of a list');
|
|
378
|
+
} else if (getSum(tree) === idx) {
|
|
379
|
+
return addAt(idx, tree, value);
|
|
380
|
+
}
|
|
381
|
+
|
|
344
382
|
let { node, index } = path.value;
|
|
345
383
|
|
|
346
384
|
let returnValue = setValuesAt(index, node, value);
|
|
@@ -359,42 +397,12 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
359
397
|
}
|
|
360
398
|
};
|
|
361
399
|
|
|
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
400
|
return {
|
|
393
401
|
buildModule,
|
|
394
|
-
|
|
395
|
-
|
|
402
|
+
treeFrom,
|
|
403
|
+
treeFromValues,
|
|
396
404
|
findBalancePoint,
|
|
397
|
-
|
|
405
|
+
splitValues,
|
|
398
406
|
collapses,
|
|
399
407
|
nodeCollapses,
|
|
400
408
|
nodeCanDonate,
|
|
@@ -411,6 +419,5 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
411
419
|
findPath,
|
|
412
420
|
getAt,
|
|
413
421
|
replaceAt,
|
|
414
|
-
freeze,
|
|
415
422
|
};
|
|
416
423
|
};
|
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,
|
|
@@ -22,5 +21,4 @@ export const {
|
|
|
22
21
|
findPath,
|
|
23
22
|
getAt,
|
|
24
23
|
replaceAt,
|
|
25
|
-
freeze,
|
|
26
24
|
} = 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.2.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
|
},
|