@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.
@@ -1,20 +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];
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 = (tree) => {
15
- const values = isFinite(tree[0]) ? tree[1] : tree;
53
+ const findBalancePoint = (values) => {
16
54
  let leftSum = 0;
17
- let rightSum = getSum(tree);
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 split = (tree) => {
39
- const values = isFinite(tree[0]) ? tree[1] : tree;
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(tree);
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
- let left = isLeaf ? leftValues : [sumNodes(leftValues), leftValues];
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 = isLeaf ? node : node[1];
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
- const oldSize = isLeaf ? 1 : values[idx] ? getSum(values[idx]) : 0;
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
- freezeObject(newValues);
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 = [...getValues(node)];
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(index, 0, pushout);
95
- node = setValues(node, values);
96
- const { left, right } = split(node);
135
+ values.splice(finiteIndex, 0, pushout);
136
+ const { leftValues, rightValues } = splitValues(values);
97
137
 
98
- pushout = left;
99
- node = right;
138
+ pushout = treeFromValues(leftValues);
139
+ node = treeFromValues(rightValues);
100
140
  } else {
101
- values.splice(index, 0, pushout);
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 treeFrom(pushout, node);
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 = [...getValues(node)].slice(0, -1);
147
- 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]);
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 = isFinite(node[0]) ? node[1] : node;
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
- 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);
224
268
  };
225
269
 
226
270
  const isLeafNode = (node) => {
227
- return isArray(node) && !isFinite(node[0]);
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 = isLeaf ? node : node[1];
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 (!isArray(tree)) {
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.length) {
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
- split,
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
- treeFrom,
4
+ defaultNodeSize,
5
+ treeFromValues,
5
6
  findBalancePoint,
6
- split,
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.1.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
  },