@bablr/btree 0.1.0 → 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 +94 -70
- package/lib/index.js +3 -3
- package/package.json +4 -1
package/lib/enhanceable.js
CHANGED
|
@@ -1,20 +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 = (...
|
|
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;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
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
|
+
}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
13
52
|
|
|
14
|
-
const findBalancePoint = (
|
|
15
|
-
const values = isFinite(tree[0]) ? tree[1] : tree;
|
|
53
|
+
const findBalancePoint = (values) => {
|
|
16
54
|
let leftSum = 0;
|
|
17
|
-
let rightSum = getSum(
|
|
55
|
+
let rightSum = values.reduce((sum, v) => sum + getSum(v), 0);
|
|
18
56
|
let balance = leftSum / rightSum;
|
|
19
57
|
|
|
20
58
|
if (!values.length) return null;
|
|
@@ -35,48 +73,47 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
35
73
|
return values.length - 1;
|
|
36
74
|
};
|
|
37
75
|
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const isLeaf = isLeafNode(tree);
|
|
76
|
+
const splitValues = (values) => {
|
|
77
|
+
const isLeaf = !isArray(values[0]);
|
|
41
78
|
|
|
42
79
|
let midIndex;
|
|
43
80
|
|
|
44
81
|
if (isLeaf) {
|
|
45
82
|
midIndex = Math.floor(values.length / 2 + 0.01);
|
|
46
83
|
} else {
|
|
47
|
-
midIndex = findBalancePoint(
|
|
84
|
+
midIndex = findBalancePoint(values);
|
|
48
85
|
}
|
|
49
86
|
|
|
50
87
|
let leftValues = values.slice(0, midIndex);
|
|
51
88
|
let rightValues = values.slice(midIndex);
|
|
52
89
|
|
|
53
|
-
|
|
54
|
-
let right = isLeaf ? rightValues : [sumNodes(rightValues), rightValues];
|
|
55
|
-
|
|
56
|
-
return { left, right };
|
|
90
|
+
return { leftValues, rightValues };
|
|
57
91
|
};
|
|
58
92
|
|
|
59
93
|
const setValuesAt = (idx, node, value) => {
|
|
60
94
|
const isLeaf = isLeafNode(node);
|
|
61
|
-
const values =
|
|
95
|
+
const values = getValues(node);
|
|
96
|
+
|
|
97
|
+
if (isArray(value)) Error('cannot set arrays as btree values');
|
|
62
98
|
|
|
63
99
|
if (!isLeaf && !isArray(value)) {
|
|
64
100
|
throw new Error();
|
|
65
101
|
}
|
|
66
102
|
|
|
67
|
-
|
|
68
|
-
const newSize = isLeaf ? 1 : getSum(value);
|
|
69
|
-
|
|
70
|
-
// TODO mutable sets?
|
|
103
|
+
if (isLeaf && isArray(value) && node.length) throw new Error();
|
|
71
104
|
|
|
72
105
|
const newValues = [...values];
|
|
73
106
|
newValues[idx] = value;
|
|
74
|
-
|
|
75
|
-
return isLeaf ? newValues : freezeObject([node[0] + newSize - oldSize, newValues]);
|
|
107
|
+
return treeFromValues(newValues);
|
|
76
108
|
};
|
|
77
109
|
|
|
78
110
|
const addAt = (idx, tree, value) => {
|
|
79
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');
|
|
80
117
|
|
|
81
118
|
let path = findPath(idx, tree);
|
|
82
119
|
|
|
@@ -85,20 +122,23 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
85
122
|
// left pushout vs right pushout?
|
|
86
123
|
let pushout = value;
|
|
87
124
|
|
|
88
|
-
let values =
|
|
125
|
+
let values = getValues(node);
|
|
89
126
|
|
|
90
127
|
for (;;) {
|
|
91
128
|
if (pushout) {
|
|
92
129
|
values = [...values];
|
|
130
|
+
|
|
131
|
+
let finiteIndex = index === Infinity ? values.length : index;
|
|
132
|
+
|
|
133
|
+
if (!isFinite(finiteIndex)) throw new Error();
|
|
93
134
|
if (values.length + getValues(pushout).length > NODE_SIZE) {
|
|
94
|
-
values.splice(
|
|
95
|
-
|
|
96
|
-
const { left, right } = split(node);
|
|
135
|
+
values.splice(finiteIndex, 0, pushout);
|
|
136
|
+
const { leftValues, rightValues } = splitValues(values);
|
|
97
137
|
|
|
98
|
-
pushout =
|
|
99
|
-
node =
|
|
138
|
+
pushout = treeFromValues(leftValues);
|
|
139
|
+
node = treeFromValues(rightValues);
|
|
100
140
|
} else {
|
|
101
|
-
values.splice(
|
|
141
|
+
values.splice(finiteIndex, 0, pushout);
|
|
102
142
|
node = setValues(node, values);
|
|
103
143
|
pushout = null;
|
|
104
144
|
}
|
|
@@ -106,7 +146,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
106
146
|
|
|
107
147
|
if (path.size === 1) {
|
|
108
148
|
if (pushout) {
|
|
109
|
-
return
|
|
149
|
+
return treeFromValues([pushout, node]);
|
|
110
150
|
} else {
|
|
111
151
|
return node;
|
|
112
152
|
}
|
|
@@ -143,8 +183,10 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
143
183
|
|
|
144
184
|
let { node, index } = path.value;
|
|
145
185
|
|
|
146
|
-
const initialValues =
|
|
147
|
-
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]);
|
|
148
190
|
|
|
149
191
|
for (;;) {
|
|
150
192
|
let values = getValues(returnValue);
|
|
@@ -206,7 +248,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
206
248
|
|
|
207
249
|
const isValidNode = (node) => {
|
|
208
250
|
if (!isArray(node)) return false;
|
|
209
|
-
const values =
|
|
251
|
+
const values = getValues(node);
|
|
210
252
|
return isArray(values); // && values.length <= NODE_SIZE;
|
|
211
253
|
};
|
|
212
254
|
|
|
@@ -220,11 +262,13 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
220
262
|
};
|
|
221
263
|
|
|
222
264
|
const setValues = (node, values) => {
|
|
223
|
-
|
|
265
|
+
if (values.length > NODE_SIZE) throw new Error();
|
|
266
|
+
|
|
267
|
+
return isFinite(node[0]) || statsReducer ? treeFromValues(values) : freeze(values);
|
|
224
268
|
};
|
|
225
269
|
|
|
226
270
|
const isLeafNode = (node) => {
|
|
227
|
-
return isArray(node)
|
|
271
|
+
return !isArray(getValues(node)[0]);
|
|
228
272
|
};
|
|
229
273
|
|
|
230
274
|
function* traverse(tree) {
|
|
@@ -237,7 +281,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
237
281
|
const { node } = s;
|
|
238
282
|
const isLeaf = isLeafNode(node);
|
|
239
283
|
|
|
240
|
-
const values =
|
|
284
|
+
const values = getValues(node);
|
|
241
285
|
|
|
242
286
|
if (isLeaf) {
|
|
243
287
|
for (let i = 0; i < values.length; i++) {
|
|
@@ -257,7 +301,9 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
257
301
|
}
|
|
258
302
|
|
|
259
303
|
const getSum = (tree) => {
|
|
260
|
-
if (
|
|
304
|
+
if (tree == null) {
|
|
305
|
+
return 0;
|
|
306
|
+
} else if (!isArray(tree)) {
|
|
261
307
|
return 1;
|
|
262
308
|
} else if (isFinite(tree[0])) {
|
|
263
309
|
return tree[0];
|
|
@@ -274,6 +320,8 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
274
320
|
}
|
|
275
321
|
|
|
276
322
|
const findPath = (idx, tree) => {
|
|
323
|
+
if (idx == null) throw new Error();
|
|
324
|
+
|
|
277
325
|
let path = emptyStack;
|
|
278
326
|
|
|
279
327
|
let treeSum = getSum(tree);
|
|
@@ -290,7 +338,7 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
290
338
|
let index = isFinite(currentIdx) ? targetIdx - startIdx : currentIdx;
|
|
291
339
|
if (index < 0) {
|
|
292
340
|
index = -Infinity;
|
|
293
|
-
} else if (index >= node
|
|
341
|
+
} else if (index >= getSum(node)) {
|
|
294
342
|
index = Infinity;
|
|
295
343
|
}
|
|
296
344
|
return path.push({ index, node });
|
|
@@ -319,12 +367,18 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
319
367
|
|
|
320
368
|
const getAt = (idx, tree) => {
|
|
321
369
|
const v = findPath(idx, tree)?.value;
|
|
322
|
-
return v && v.node[v.index];
|
|
370
|
+
return v && getValues(v.node)[v.index];
|
|
323
371
|
};
|
|
324
372
|
|
|
325
373
|
const replaceAt = (idx, tree, value) => {
|
|
326
374
|
let path = findPath(idx, tree);
|
|
327
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
|
+
|
|
328
382
|
let { node, index } = path.value;
|
|
329
383
|
|
|
330
384
|
let returnValue = setValuesAt(index, node, value);
|
|
@@ -343,41 +397,12 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
343
397
|
}
|
|
344
398
|
};
|
|
345
399
|
|
|
346
|
-
const freeze = (tree) => {
|
|
347
|
-
let states = emptyStack.push({ node: tree, i: 0 });
|
|
348
|
-
|
|
349
|
-
stack: while (states.size) {
|
|
350
|
-
const s = states.value;
|
|
351
|
-
const { node } = s;
|
|
352
|
-
const isLeaf = isLeafNode(node);
|
|
353
|
-
|
|
354
|
-
assertValidNode(node);
|
|
355
|
-
|
|
356
|
-
freezeObject(node);
|
|
357
|
-
|
|
358
|
-
const values = isLeaf ? node : node[1];
|
|
359
|
-
|
|
360
|
-
if (!isLeaf) {
|
|
361
|
-
freezeObject(values);
|
|
362
|
-
|
|
363
|
-
for (let { i } = s; s.i < values.length; ) {
|
|
364
|
-
const node = values[i];
|
|
365
|
-
assertValidNode(node);
|
|
366
|
-
states = states.push({ node, i: 0 });
|
|
367
|
-
i = ++s.i;
|
|
368
|
-
continue stack;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
states = states.pop();
|
|
372
|
-
}
|
|
373
|
-
return tree;
|
|
374
|
-
};
|
|
375
|
-
|
|
376
400
|
return {
|
|
377
401
|
buildModule,
|
|
378
402
|
treeFrom,
|
|
403
|
+
treeFromValues,
|
|
379
404
|
findBalancePoint,
|
|
380
|
-
|
|
405
|
+
splitValues,
|
|
381
406
|
collapses,
|
|
382
407
|
nodeCollapses,
|
|
383
408
|
nodeCanDonate,
|
|
@@ -394,6 +419,5 @@ export const buildModule = (NODE_SIZE = 8) => {
|
|
|
394
419
|
findPath,
|
|
395
420
|
getAt,
|
|
396
421
|
replaceAt,
|
|
397
|
-
freeze,
|
|
398
422
|
};
|
|
399
423
|
};
|
package/lib/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { buildModule } from './enhanceable.js';
|
|
2
2
|
|
|
3
3
|
export const {
|
|
4
|
-
|
|
4
|
+
defaultNodeSize,
|
|
5
|
+
treeFromValues,
|
|
5
6
|
findBalancePoint,
|
|
6
|
-
|
|
7
|
+
splitValues,
|
|
7
8
|
collapses,
|
|
8
9
|
nodeCollapses,
|
|
9
10
|
nodeCanDonate,
|
|
@@ -20,5 +21,4 @@ export const {
|
|
|
20
21
|
findPath,
|
|
21
22
|
getAt,
|
|
22
23
|
replaceAt,
|
|
23
|
-
freeze,
|
|
24
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
|
},
|