@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.
@@ -1,43 +1,65 @@
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
- return nodes.map(getSum).reduce((a, b) => a + b, 0);
16
+ return nodes.map(getSize).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 + 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 = getSum(values[i - 1]);
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 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
  }
@@ -139,7 +163,7 @@ export const buildModule = (NODE_SIZE = 8) => {
139
163
  };
140
164
 
141
165
  const push = (tree, value) => {
142
- return addAt(getSum(tree), tree, value);
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 = [...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);
@@ -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]) && getSum(returnValue) <= NODE_SIZE) {
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 = isFinite(node[0]) ? node[1] : node;
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
- return isFinite(node[0]) ? treeFrom(...values) : values;
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) && !isFinite(node[0]);
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 = isLeaf ? node : node[1];
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 getSum = (tree) => {
276
- if (!isArray(tree)) {
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 = getSum(tree);
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 - getSum(node) : 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.length) {
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 = getSum(candidateNode);
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
- from,
395
- fromValues,
407
+ treeFrom,
408
+ treeFromValues,
396
409
  findBalancePoint,
397
- split,
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
- getSum,
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
- from,
5
- fromValues,
6
-
4
+ defaultNodeSize,
5
+ treeFromValues,
7
6
  findBalancePoint,
8
- split,
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
- getSum,
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.1.1",
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
  },