@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.
@@ -1,36 +1,58 @@
1
1
  import emptyStack from '@iter-tools/imm-stack';
2
2
 
3
3
  const { isArray } = Array;
4
- const { freeze: freezeObject, isFrozen } = Object;
4
+ const { freeze } = Object;
5
5
  const { isFinite } = Number;
6
6
 
7
- export const buildModule = (NODE_SIZE = 8) => {
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 = (...trees) => [sumNodes(trees), trees];
13
-
14
- const from = (...values) => {
15
- let tree = [];
16
- for (const value of values) {
17
- tree = push(tree, value);
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 fromValues = (values) => {
23
- let tree = [];
24
- for (const value of values) {
25
- tree = push(tree, value);
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 = (tree) => {
31
- const values = isFinite(tree[0]) ? tree[1] : tree;
53
+ const findBalancePoint = (values) => {
32
54
  let leftSum = 0;
33
- let rightSum = getSum(tree);
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 split = (tree) => {
55
- const values = isFinite(tree[0]) ? tree[1] : tree;
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(tree);
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
- let left = isLeaf ? leftValues : [sumNodes(leftValues), leftValues];
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 = isLeaf ? node : node[1];
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
- const oldSize = isLeaf ? 1 : values[idx] ? getSum(values[idx]) : 0;
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
- freezeObject(newValues);
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 = [...getValues(node)];
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(index, 0, pushout);
111
- node = setValues(node, values);
112
- const { left, right } = split(node);
135
+ values.splice(finiteIndex, 0, pushout);
136
+ const { leftValues, rightValues } = splitValues(values);
113
137
 
114
- pushout = left;
115
- node = right;
138
+ pushout = treeFromValues(leftValues);
139
+ node = treeFromValues(rightValues);
116
140
  } else {
117
- values.splice(index, 0, pushout);
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 treeFrom(pushout, node);
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 = [...getValues(node)].slice(0, -1);
163
- let returnValue = isLeafNode(node) ? initialValues : [sumNodes(initialValues), initialValues];
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 = isFinite(node[0]) ? node[1] : node;
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
- return isFinite(node[0]) ? treeFrom(...values) : values;
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) && !isFinite(node[0]);
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 = isLeaf ? node : node[1];
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 (!isArray(tree)) {
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.length) {
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
- from,
395
- fromValues,
402
+ treeFrom,
403
+ treeFromValues,
396
404
  findBalancePoint,
397
- split,
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
- from,
5
- fromValues,
6
-
4
+ defaultNodeSize,
5
+ treeFromValues,
7
6
  findBalancePoint,
8
- split,
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.1.1",
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
  },