@bablr/btree 0.3.1 → 0.4.1

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.
@@ -16,6 +16,18 @@ export const buildModule = (
16
16
  return nodes.map(getSize).reduce((a, b) => a + b, 0);
17
17
  };
18
18
 
19
+ const getHeight = (tree) => {
20
+ let height = 0;
21
+ let node = tree;
22
+
23
+ while (Array.isArray(node)) {
24
+ height++;
25
+ node = getValues(node)[0];
26
+ }
27
+
28
+ return height;
29
+ };
30
+
19
31
  const treeFrom = (...values) => {
20
32
  if (!statsReducer && values.length <= NODE_SIZE) {
21
33
  return freeze(values);
@@ -30,11 +42,12 @@ export const buildModule = (
30
42
 
31
43
  const buildStats = (values) => {
32
44
  let sums = values.reduce(statsReducer, getSumsInitial());
33
- finalizeSums(sums);
45
+ sums = finalizeSums(sums);
34
46
  return sums;
35
47
  };
36
48
 
37
49
  const treeFromValues = (values) => {
50
+ if (values.includes(undefined)) throw new Error();
38
51
  if (!statsReducer && values.length <= NODE_SIZE) {
39
52
  return isArray(values[0]) ? freeze([sumNodes(values), freeze(values)]) : freeze(values);
40
53
  } else {
@@ -91,42 +104,101 @@ export const buildModule = (
91
104
  };
92
105
 
93
106
  const setValuesAt = (idx, node, value) => {
94
- const isLeaf = isLeafNode(node);
95
107
  const values = getValues(node);
96
108
 
97
- if (isArray(value)) Error('cannot set arrays as btree values');
109
+ if (!Number.isFinite(idx)) throw new Error();
98
110
 
99
- if (!isLeaf && !isArray(value)) {
111
+ if (!value == null) {
100
112
  throw new Error();
101
113
  }
102
114
 
103
- if (isLeaf && isArray(value) && node.length) throw new Error();
104
-
105
- const newValues = [...values];
115
+ const newValues = values.slice();
106
116
  newValues[idx] = value;
107
117
  return treeFromValues(newValues);
108
118
  };
109
119
 
110
- const addAt = (idx, tree, value) => {
111
- if (idx < 0) throw new Error('invalid argument');
112
- if (!isArray(tree)) throw new Error();
120
+ const concat = (first, second) => {
121
+ if (!isArray(first) || !isArray(second)) throw new Error();
113
122
 
114
- let isLeaf = isLeafNode(tree);
123
+ let firstHeight = getHeight(first);
124
+ let secondHeight = getHeight(second);
125
+ let firstValues = getValues(first);
126
+ let secondValues = getValues(second);
115
127
 
116
- if (isArray(value)) throw new Error('cannot add arrays to btrees');
128
+ if (firstHeight === secondHeight) {
129
+ if (firstValues.length + secondValues.length < NODE_SIZE) {
130
+ return treeFromValues([...firstValues, ...secondValues]);
131
+ } else {
132
+ return treeFromValues([first, second]);
133
+ }
134
+ } else {
135
+ let targetDepth = Math.abs(firstHeight - secondHeight);
136
+ let path =
137
+ firstHeight > secondHeight
138
+ ? findPath(Infinity, first, targetDepth)
139
+ : findPath(0, second, targetDepth);
140
+
141
+ let { node, index } = path.value;
142
+
143
+ let pushout = firstHeight > secondHeight ? second : first;
144
+
145
+ let values = getValues(node);
146
+
147
+ for (;;) {
148
+ if (pushout) {
149
+ values = values.slice();
150
+
151
+ let finiteIndex = index === Infinity ? values.length : index;
152
+
153
+ if (!isFinite(finiteIndex)) throw new Error();
154
+ if (values.length + getValues(pushout).length > NODE_SIZE) {
155
+ values.splice(finiteIndex, 0, pushout);
156
+ const { leftValues, rightValues } = splitValues(values);
157
+
158
+ pushout = treeFromValues(leftValues);
159
+ node = treeFromValues(rightValues);
160
+ } else {
161
+ values.splice(finiteIndex, 0, pushout);
162
+ node = setValues(node, values);
163
+ pushout = null;
164
+ }
165
+ }
166
+
167
+ if (path.size === 1) {
168
+ if (pushout) {
169
+ return treeFromValues([pushout, node]);
170
+ } else {
171
+ return node;
172
+ }
173
+ }
174
+
175
+ const poppedNode = node;
176
+ path = path.pop();
177
+ ({ node, index } = path.value);
178
+
179
+ node = setValuesAt(index, node, poppedNode);
180
+ values = getValues(node);
181
+ path = path.replace({ node, index });
182
+ }
183
+ }
184
+ };
185
+
186
+ const addAt = (idx, tree, value) => {
187
+ if (idx < 0 || !Number.isFinite(idx)) throw new Error('invalid argument');
188
+ if (!isArray(tree)) throw new Error();
189
+ if (!value) throw new Error();
117
190
 
118
191
  let path = findPath(idx, tree);
119
192
 
120
193
  let { node, index } = path.value;
121
194
 
122
- // left pushout vs right pushout?
123
195
  let pushout = value;
124
196
 
125
197
  let values = getValues(node);
126
198
 
127
199
  for (;;) {
128
200
  if (pushout) {
129
- values = [...values];
201
+ values = values.slice();
130
202
 
131
203
  let finiteIndex = index === Infinity ? values.length : index;
132
204
 
@@ -163,7 +235,8 @@ export const buildModule = (
163
235
  };
164
236
 
165
237
  const push = (tree, value) => {
166
- return addAt(getSize(tree), tree, value);
238
+ let size = getSize(tree);
239
+ return size ? addAt(size, tree, value) : treeFromValues([value]);
167
240
  };
168
241
 
169
242
  const collapses = (size) => {
@@ -178,15 +251,16 @@ export const buildModule = (
178
251
  return collapses(getValues(node).length - 1);
179
252
  };
180
253
 
181
- const pop = (tree) => {
182
- let path = findPath(-1, tree);
254
+ const removeAt = (idx, tree) => {
255
+ let path = findPath(idx, tree);
183
256
 
184
257
  let { node, index } = path.value;
185
258
 
186
- const initialValues = freeze(getValues(node).slice(0, -1));
187
- let returnValue = isLeafNode(node)
188
- ? initialValues
189
- : freeze([sumNodes(initialValues), initialValues]);
259
+ const initialValues = [...getValues(node)];
260
+
261
+ initialValues.splice(index, 1);
262
+
263
+ let returnValue = !isFinite(node[0]) ? initialValues : treeFromValues(initialValues);
190
264
 
191
265
  for (;;) {
192
266
  let values = getValues(returnValue);
@@ -204,7 +278,7 @@ export const buildModule = (
204
278
  let targetSiblingIndex = targetSibling && (prevSibling ? parentIndex - 1 : parentIndex + 1);
205
279
 
206
280
  if (targetSibling) {
207
- let targetValues = [...getValues(targetSibling)];
281
+ let targetValues = getValues(targetSibling).slice();
208
282
 
209
283
  const donationIdx = targetSibling === prevSibling ? targetValues.length - 1 : 0;
210
284
  const donated = targetValues[donationIdx];
@@ -215,7 +289,7 @@ export const buildModule = (
215
289
  index: targetSiblingIndex,
216
290
  };
217
291
 
218
- values = [...values];
292
+ values = values.slice();
219
293
 
220
294
  values.splice(targetSibling === prevSibling ? values.length : 0, 0, donated);
221
295
 
@@ -224,8 +298,8 @@ export const buildModule = (
224
298
  }
225
299
 
226
300
  if (path.size === 1) {
227
- if (isFinite(returnValue[0]) && getSize(returnValue) <= NODE_SIZE) {
228
- returnValue = returnValue[1].flat();
301
+ if (getSize(returnValue) <= NODE_SIZE) {
302
+ returnValue = setValues(returnValue, getValues(returnValue).flat());
229
303
  }
230
304
  return returnValue;
231
305
  }
@@ -233,23 +307,33 @@ export const buildModule = (
233
307
  path = path.pop();
234
308
  ({ node, index } = path.value);
235
309
 
236
- values = [...getValues(node)];
310
+ values = getValues(node).slice();
237
311
 
238
312
  values.splice(index, 1, returnValue);
239
313
 
240
314
  if (adjustSibling) {
241
315
  const { index, node } = adjustSibling;
242
- values.splice(index, 1, ...(node ? [node] : []));
316
+ if (node) {
317
+ values.splice(index, 1, node);
318
+ } else {
319
+ values.splice(index, 1);
320
+ }
243
321
  }
244
322
 
245
323
  returnValue = node = setValues(node, values);
246
324
  }
247
325
  };
248
326
 
327
+ const pop = (tree) => {
328
+ return removeAt(-1, tree);
329
+ };
330
+
249
331
  const isValidNode = (node) => {
250
332
  if (!isArray(node)) return false;
251
333
  const values = getValues(node);
252
- return isArray(values); // && values.length <= NODE_SIZE;
334
+ if (!isArray(values) || values.length > NODE_SIZE) return false; // ;
335
+
336
+ return !node[0] || isFinite(node[0]) || ['object', 'string'].includes(typeof node[0]);
253
337
  };
254
338
 
255
339
  const assertValidNode = (node) => {
@@ -272,10 +356,6 @@ export const buildModule = (
272
356
  return isFinite(node[0]) || statsReducer ? treeFromValues(values) : freeze(values);
273
357
  };
274
358
 
275
- const isLeafNode = (node) => {
276
- return !isArray(getValues(node)[0]);
277
- };
278
-
279
359
  function* traverse(tree) {
280
360
  let states = emptyStack.push({ node: tree, i: 0 });
281
361
 
@@ -284,23 +364,23 @@ export const buildModule = (
284
364
  stack: while (states.size) {
285
365
  const s = states.value;
286
366
  const { node } = s;
287
- const isLeaf = isLeafNode(node);
288
367
 
289
368
  const values = getValues(node);
290
369
 
291
- if (isLeaf) {
292
- for (let i = 0; i < values.length; i++) {
293
- yield values[i];
294
- }
295
- } else {
296
- for (let { i } = s; s.i < values.length; ) {
297
- const node = values[i];
370
+ for (let { i } = s; s.i < values.length; ) {
371
+ const value = values[i];
372
+ if (isArray(value)) {
373
+ let node = value;
298
374
  assertValidNode(node);
299
375
  states = states.push({ node, i: 0 });
300
376
  i = ++s.i;
301
377
  continue stack;
378
+ } else {
379
+ yield value;
380
+ i = ++s.i;
302
381
  }
303
382
  }
383
+
304
384
  states = states.pop();
305
385
  }
306
386
  }
@@ -317,20 +397,13 @@ export const buildModule = (
317
397
  }
318
398
  };
319
399
 
320
- function* indexes(count, backwards = false) {
321
- const increment = backwards ? -1 : 1;
322
- for (let i = backwards ? count - 1 : 0; backwards ? i >= 0 : i < count; i += increment) {
323
- yield i;
324
- }
325
- }
326
-
327
- const findPath = (idx, tree) => {
400
+ const findPath = (idx, tree, depth = Infinity) => {
328
401
  if (idx == null) throw new Error();
329
402
 
330
403
  let path = emptyStack;
331
404
 
332
405
  let treeSum = getSize(tree);
333
- let currentIdx = idx < 0 ? treeSum : 0;
406
+ let currentIdx = idx < 0 ? treeSum - 1 : 0;
334
407
  let direction = idx < 0 ? -1 : 1;
335
408
  let targetIdx = idx < 0 ? treeSum + idx : idx;
336
409
 
@@ -338,33 +411,52 @@ export const buildModule = (
338
411
  stack: while (node) {
339
412
  assertValidNode(node);
340
413
 
341
- if (isLeafNode(node)) {
342
- const startIdx = idx < 0 ? currentIdx - getSize(node) : currentIdx;
343
- let index = isFinite(currentIdx) ? targetIdx - startIdx : currentIdx;
344
- if (index < 0) {
345
- index = -Infinity;
346
- } else if (index >= getSize(node)) {
347
- index = Infinity;
348
- }
349
- return path.push({ index, node });
350
- } else {
351
- const values = node[1];
352
- let candidateNode;
353
- let i;
414
+ const values = getValues(node);
415
+ let candidateNode;
416
+
417
+ let backwards = idx < 0;
418
+ const increment = backwards ? -1 : 1;
419
+ for (
420
+ let i = backwards ? values.length - 1 : 0;
421
+ backwards ? i >= 0 : i < values.length;
422
+ i += increment
423
+ ) {
424
+ let value = values[i];
425
+ if (isArray(value) && path.size < depth - 1) {
426
+ candidateNode = value;
354
427
 
355
- for (i of indexes(values.length, idx < 0)) {
356
- candidateNode = values[i];
357
428
  const sum = getSize(candidateNode);
429
+
358
430
  const nextCount = currentIdx + sum * direction;
359
- if (idx < 0 ? nextCount <= targetIdx : nextCount > targetIdx || nextCount >= treeSum) {
431
+ if (
432
+ (backwards ? nextCount <= targetIdx : nextCount > targetIdx) ||
433
+ (backwards ? nextCount < 0 : nextCount >= treeSum)
434
+ ) {
360
435
  path = path.push({ index: i, node });
361
436
  node = candidateNode;
362
437
  continue stack;
363
438
  } else {
364
439
  currentIdx += sum * direction;
365
440
  }
441
+ } else {
442
+ const sum = getSize(value);
443
+ const nextCount = currentIdx + sum * direction;
444
+ if (!isFinite(targetIdx)) {
445
+ return path.push({ index: targetIdx, node });
446
+ } else if (currentIdx === targetIdx) {
447
+ return path.push({ index: i, node });
448
+ } else if (
449
+ backwards
450
+ ? nextCount < targetIdx || nextCount < 0
451
+ : nextCount > targetIdx || nextCount >= treeSum
452
+ ) {
453
+ break;
454
+ } else {
455
+ currentIdx += direction;
456
+ }
366
457
  }
367
458
  }
459
+ return path.push({ index: backwards ? -Infinity : Infinity, node });
368
460
  }
369
461
 
370
462
  return null;
@@ -404,22 +496,25 @@ export const buildModule = (
404
496
 
405
497
  return {
406
498
  buildModule,
407
- treeFrom,
408
- treeFromValues,
499
+ btreeFrom: treeFrom,
500
+ from: treeFrom,
501
+ btreeFromValues: treeFromValues,
502
+ fromValues: treeFromValues,
409
503
  findBalancePoint,
410
504
  splitValues,
411
505
  collapses,
412
506
  nodeCollapses,
413
507
  nodeCanDonate,
414
508
  pop,
509
+ removeAt,
415
510
  push,
416
511
  addAt,
512
+ concat,
417
513
  isValidNode,
418
514
  assertValidNode,
419
515
  getValues,
420
516
  getSums,
421
517
  setValues,
422
- isLeafNode,
423
518
  traverse,
424
519
  getSize,
425
520
  findPath,
package/lib/index.js CHANGED
@@ -3,7 +3,10 @@ import { defaultNodeSize, buildModule } from './enhanceable.js';
3
3
  export { defaultNodeSize };
4
4
 
5
5
  export const {
6
- treeFromValues,
6
+ btreeFrom,
7
+ from,
8
+ btreeFromValues,
9
+ fromValues,
7
10
  findBalancePoint,
8
11
  splitValues,
9
12
  collapses,
@@ -17,7 +20,6 @@ export const {
17
20
  getValues,
18
21
  getSums,
19
22
  setValues,
20
- isLeafNode,
21
23
  traverse,
22
24
  getSize,
23
25
  findPath,
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.3.1",
4
+ "version": "0.4.1",
5
5
  "author": "Conrad Buck<conartist6@gmail.com>",
6
6
  "type": "module",
7
7
  "files": [
@@ -16,10 +16,10 @@
16
16
  "test": "mocha test/*.test.js"
17
17
  },
18
18
  "dependencies": {
19
- "@iter-tools/imm-stack": "1.1.0"
19
+ "@iter-tools/imm-stack": "1.2.0"
20
20
  },
21
21
  "devDependencies": {
22
- "@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#49f5952efed27f94ee9b94340eb1563c440bf64e",
22
+ "@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#c97bfa4b3663f8378e9b3e42bb5a41e685406cf9",
23
23
  "enhanced-resolve": "^5.12.0",
24
24
  "eslint": "^8.32.0",
25
25
  "eslint-import-resolver-enhanced-resolve": "^1.0.5",