@mark1russell7/collections 0.9.1 → 0.9.3

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.
@@ -160,10 +160,7 @@ export class TreeSet<T> implements NavigableSet<T> {
160
160
  }
161
161
 
162
162
  retainAll(elements: Iterable<T>): boolean {
163
- const toRetain = new Set<T>();
164
- for (const element of elements) {
165
- toRetain.add(element);
166
- }
163
+ const toRetain = this.setOf(elements);
167
164
 
168
165
  let modified = false;
169
166
  for (const element of Array.from(this)) {
@@ -201,10 +198,7 @@ export class TreeSet<T> implements NavigableSet<T> {
201
198
  eq: this._eq,
202
199
  });
203
200
 
204
- const otherSet = new Set<T>();
205
- for (const element of other) {
206
- otherSet.add(element);
207
- }
201
+ const otherSet = this.setOf(other);
208
202
 
209
203
  for (const element of this) {
210
204
  if (otherSet.has(element)) {
@@ -221,10 +215,7 @@ export class TreeSet<T> implements NavigableSet<T> {
221
215
  eq: this._eq,
222
216
  });
223
217
 
224
- const otherSet = new Set<T>();
225
- for (const element of other) {
226
- otherSet.add(element);
227
- }
218
+ const otherSet = this.setOf(other);
228
219
 
229
220
  for (const element of this) {
230
221
  if (!otherSet.has(element)) {
@@ -246,8 +237,8 @@ export class TreeSet<T> implements NavigableSet<T> {
246
237
  result.add(element);
247
238
  }
248
239
 
249
- // Toggle elements from other set
250
- for (const element of other) {
240
+ // Toggle each element of the other set one time, so a repeated element does not toggle back
241
+ for (const element of this.setOf(other)) {
251
242
  if (result.has(element)) {
252
243
  result.remove(element);
253
244
  } else {
@@ -259,10 +250,7 @@ export class TreeSet<T> implements NavigableSet<T> {
259
250
  }
260
251
 
261
252
  isSubsetOf(other: Iterable<T>): boolean {
262
- const otherSet = new Set<T>();
263
- for (const element of other) {
264
- otherSet.add(element);
265
- }
253
+ const otherSet = this.setOf(other);
266
254
 
267
255
  for (const element of this) {
268
256
  if (!otherSet.has(element)) {
@@ -272,6 +260,18 @@ export class TreeSet<T> implements NavigableSet<T> {
272
260
  return true;
273
261
  }
274
262
 
263
+ /**
264
+ * This method makes a set of the elements, with the comparator and the equality of this set.
265
+ * Before, the set operations used a native `Set`, which compares with `===` and ignores the comparator.
266
+ */
267
+ private setOf(elements: Iterable<T>): TreeSet<T> {
268
+ const set = new TreeSet<T>({ compare: this._compare, eq: this._eq });
269
+ for (const element of elements) {
270
+ set.add(element);
271
+ }
272
+ return set;
273
+ }
274
+
275
275
  isSupersetOf(other: Iterable<T>): boolean {
276
276
  for (const element of other) {
277
277
  if (!this.has(element)) {
package/src/index.ts CHANGED
@@ -54,7 +54,16 @@ export * from "./impl/tree-set.js";
54
54
  export * from "./impl/linked-list.js";
55
55
  export * from "./impl/hash-set.js";
56
56
  export * from "./impl/priority-queue.js";
57
- export { SummaryTree, summaryTree, type SummaryLocation, type SummaryMonoid, type SummarySearch, type SummarySpec } from "./impl/summary-tree.js";
57
+ export {
58
+ SummaryTree,
59
+ summaryTree,
60
+ type SummaryLocation,
61
+ type SummaryMonoid,
62
+ type SummaryNode,
63
+ type SummarySearch,
64
+ type SummarySpec,
65
+ type SummaryTreeRecord,
66
+ } from "./impl/summary-tree.js";
58
67
 
59
68
  // ============================================================================
60
69
  // Behaviors
@@ -0,0 +1,581 @@
1
+ /**
2
+ * The summary tree as plain functions over a plain record: a persistent sequence of leaves with a summary of each part.
3
+ *
4
+ * The tree is a balanced binary tree (AVL) with one leaf in each node. Each node keeps the summary of its subtree.
5
+ * A summary is a value of a monoid: the caller gives the monoid as data, with `empty` and `combine`. The order of the
6
+ * leaves is kept, so `combine` must be associative, but it can be non-commutative.
7
+ *
8
+ * A tree is a record of its spec and its root. Each function that changes the sequence gives a new record and keeps the
9
+ * old record. The two records share the nodes that did not change. Concatenation, a split at an index or at a summary
10
+ * search, an edit and a lookup are O(log n).
11
+ *
12
+ * Each function is a separate export, so a bundler keeps only the functions that a program calls. The class
13
+ * `SummaryTree` of the root entry wraps the same functions.
14
+ *
15
+ * The algorithms are the "join" algorithms of Blelloch, Ferizovic and Sun, "Just Join for Parallel Ordered Sets"
16
+ * (SPAA 2016).
17
+ *
18
+ * @example
19
+ * // The number of leaves and the total length of the strings
20
+ * import * as Tree from "@mark1russell7/collections/summary-tree";
21
+ * const spec = {
22
+ * monoid: { empty: { count: 0, length: 0 }, combine: (a, b) => ({ count: a.count + b.count, length: a.length + b.length }) },
23
+ * measure: (leaf: string) => ({ count: 1, length: leaf.length }),
24
+ * };
25
+ * const tree = Tree.of(spec, ["ab", "cde", "f"]);
26
+ * Tree.summary(tree); // { count: 3, length: 6 }
27
+ * const [left, right] = Tree.split(tree, (prefix) => prefix.length > 2); // left: ["ab"], right: ["cde", "f"]
28
+ */
29
+
30
+ /** A monoid as data: the identity value `empty` and an associative operation `combine`. */
31
+ export interface SummaryMonoid<Summary> {
32
+ /** The summary of a sequence with no leaf. `combine(empty, s)` and `combine(s, empty)` are equal to `s`. */
33
+ readonly empty: Summary;
34
+ /** This function gives the summary of a sequence from the summaries of its two parts, left first. */
35
+ readonly combine: (left: Summary, right: Summary) => Summary;
36
+ }
37
+
38
+ /** The data that a tree needs: the monoid of the summaries and the summary of one leaf. */
39
+ export interface SummarySpec<Leaf, Summary> {
40
+ readonly monoid: SummaryMonoid<Summary>;
41
+ /** This function gives the summary of one leaf. */
42
+ readonly measure: (leaf: Leaf) => Summary;
43
+ /**
44
+ * This optional function joins two neighbor leaves at a seam into one leaf, or gives `undefined` to keep both. A seam
45
+ * is the place where `concat`, `append`, `prepend`, `splice`, `insert` or `remove` puts two leaves next to each other.
46
+ * The tree calls it once at each seam, and not between the leaves that one call inserts. `set` does not call it.
47
+ */
48
+ readonly merge?: (left: Leaf, right: Leaf) => Leaf | undefined;
49
+ }
50
+
51
+ /** The result of a summary search: the first leaf whose prefix summary satisfies the predicate. */
52
+ export interface SummarySearch<Leaf, Summary> {
53
+ /** The index of the leaf. */
54
+ readonly index: number;
55
+ readonly leaf: Leaf;
56
+ /** The summary of the leaves before the leaf. */
57
+ readonly before: Summary;
58
+ /** The summary of the leaves before the leaf, and of the leaf. */
59
+ readonly through: Summary;
60
+ }
61
+
62
+ /** The result of `locate`: the leaf that holds a position of an additive key, and the position in that leaf. */
63
+ export interface SummaryLocation<Leaf> {
64
+ /** The index of the leaf. */
65
+ readonly index: number;
66
+ readonly leaf: Leaf;
67
+ /** The position in the leaf: the target minus the key of the leaves before the leaf. */
68
+ readonly offset: number;
69
+ }
70
+
71
+ /** One node of a tree. The functions do not change a node after they make it. */
72
+ export interface SummaryNode<Leaf, Summary> {
73
+ readonly left: SummaryNode<Leaf, Summary> | null;
74
+ readonly leaf: Leaf;
75
+ /** The summary of the leaf of this node only. */
76
+ readonly own: Summary;
77
+ /** The summary of the leaves of the left subtree and the leaf of this node. A copy with the same left subtree and leaf keeps it. */
78
+ readonly inner: Summary;
79
+ readonly right: SummaryNode<Leaf, Summary> | null;
80
+ readonly height: number;
81
+ readonly size: number;
82
+ /** The summary of all the leaves of this subtree. */
83
+ readonly summary: Summary;
84
+ }
85
+
86
+ /**
87
+ * A tree as a plain record: its spec and its root. `of` makes one. A function that changes nothing gives the same
88
+ * record. Two trees join only when they have the same `spec` object.
89
+ */
90
+ export interface SummaryTreeRecord<Leaf, Summary> {
91
+ readonly spec: SummarySpec<Leaf, Summary>;
92
+ /** The root node, or `null` for a tree with no leaf. */
93
+ readonly root: SummaryNode<Leaf, Summary> | null;
94
+ }
95
+
96
+ type Tree<L, S> = SummaryNode<L, S> | null;
97
+ type Spec<L, S> = SummarySpec<L, S>;
98
+
99
+ function heightOf<L, S>(tree: Tree<L, S>): number {
100
+ return tree === null ? 0 : tree.height;
101
+ }
102
+
103
+ function sizeOf<L, S>(tree: Tree<L, S>): number {
104
+ return tree === null ? 0 : tree.size;
105
+ }
106
+
107
+ function summaryOfRoot<L, S>(spec: Spec<L, S>, tree: Tree<L, S>): S {
108
+ return tree === null ? spec.monoid.empty : tree.summary;
109
+ }
110
+
111
+ /** This function makes a node. Give `inner` only when it is the summary of `left` and `own`, to save one `combine`. */
112
+ function node<L, S>(spec: Spec<L, S>, left: Tree<L, S>, leaf: L, own: S, right: Tree<L, S>, inner?: S): SummaryNode<L, S> {
113
+ if (inner === undefined) inner = left === null ? own : spec.monoid.combine(left.summary, own);
114
+ return {
115
+ left,
116
+ leaf,
117
+ own,
118
+ inner,
119
+ right,
120
+ height: 1 + Math.max(heightOf(left), heightOf(right)),
121
+ size: sizeOf(left) + 1 + sizeOf(right),
122
+ summary: right === null ? inner : spec.monoid.combine(inner, right.summary),
123
+ };
124
+ }
125
+
126
+ /** This function copies a node with a new right subtree. It keeps the summary `inner`, so it calls `combine` once at most. */
127
+ function withRight<L, S>(spec: Spec<L, S>, n: SummaryNode<L, S>, right: Tree<L, S>): SummaryNode<L, S> {
128
+ return node(spec, n.left, n.leaf, n.own, right, n.inner);
129
+ }
130
+
131
+ function rotateLeft<L, S>(spec: Spec<L, S>, n: SummaryNode<L, S>): SummaryNode<L, S> {
132
+ const r = n.right!;
133
+ return node(spec, withRight(spec, n, r.left), r.leaf, r.own, r.right);
134
+ }
135
+
136
+ function rotateRight<L, S>(spec: Spec<L, S>, n: SummaryNode<L, S>): SummaryNode<L, S> {
137
+ const l = n.left!;
138
+ return withRight(spec, l, node(spec, l.right, n.leaf, n.own, n.right));
139
+ }
140
+
141
+ /** This function joins a tree that is higher by more than 1 with a leaf and a lower tree on its right. */
142
+ function joinRight<L, S>(spec: Spec<L, S>, left: SummaryNode<L, S>, leaf: L, own: S, right: Tree<L, S>): SummaryNode<L, S> {
143
+ const c = left.right;
144
+ if (heightOf(c) <= heightOf(right) + 1) {
145
+ const t = node(spec, c, leaf, own, right);
146
+ if (t.height <= heightOf(left.left) + 1) return withRight(spec, left, t);
147
+ return rotateLeft(spec, withRight(spec, left, rotateRight(spec, t)));
148
+ }
149
+ const t = joinRight(spec, c!, leaf, own, right);
150
+ const joined = withRight(spec, left, t);
151
+ return t.height <= heightOf(left.left) + 1 ? joined : rotateLeft(spec, joined);
152
+ }
153
+
154
+ /** This function joins a lower tree on the left with a leaf and a tree that is higher by more than 1. */
155
+ function joinLeft<L, S>(spec: Spec<L, S>, left: Tree<L, S>, leaf: L, own: S, right: SummaryNode<L, S>, inner?: S): SummaryNode<L, S> {
156
+ const c = right.left;
157
+ if (heightOf(c) <= heightOf(left) + 1) {
158
+ const t = node(spec, left, leaf, own, c, inner);
159
+ if (t.height <= heightOf(right.right) + 1) return node(spec, t, right.leaf, right.own, right.right);
160
+ return rotateRight(spec, node(spec, rotateLeft(spec, t), right.leaf, right.own, right.right));
161
+ }
162
+ const t = joinLeft(spec, left, leaf, own, c!, inner);
163
+ const joined = node(spec, t, right.leaf, right.own, right.right);
164
+ return t.height <= heightOf(right.right) + 1 ? joined : rotateRight(spec, joined);
165
+ }
166
+
167
+ /**
168
+ * This function gives the tree of the leaves of `left`, then `leaf`, then the leaves of `right`. Give `inner` only
169
+ * when it is the summary of `left` and `own`.
170
+ */
171
+ function join<L, S>(spec: Spec<L, S>, left: Tree<L, S>, leaf: L, own: S, right: Tree<L, S>, inner?: S): SummaryNode<L, S> {
172
+ if (heightOf(left) > heightOf(right) + 1) return joinRight(spec, left!, leaf, own, right);
173
+ if (heightOf(right) > heightOf(left) + 1) return joinLeft(spec, left, leaf, own, right!, inner);
174
+ return node(spec, left, leaf, own, right, inner);
175
+ }
176
+
177
+ /** This function removes the last leaf of a tree, and gives the rest and the node of that leaf. */
178
+ function splitLast<L, S>(spec: Spec<L, S>, tree: SummaryNode<L, S>): [Tree<L, S>, SummaryNode<L, S>] {
179
+ if (tree.right === null) return [tree.left, tree];
180
+ const [rest, last] = splitLast(spec, tree.right);
181
+ return [join(spec, tree.left, tree.leaf, tree.own, rest, tree.inner), last];
182
+ }
183
+
184
+ function concatRoots<L, S>(spec: Spec<L, S>, left: Tree<L, S>, right: Tree<L, S>): Tree<L, S> {
185
+ if (left === null) return right;
186
+ if (right === null) return left;
187
+ const [rest, last] = splitLast(spec, left);
188
+ return join(spec, rest, last.leaf, last.own, right);
189
+ }
190
+
191
+ /** This function splits a tree into its first `index` leaves and the other leaves. */
192
+ function splitRoot<L, S>(spec: Spec<L, S>, tree: Tree<L, S>, index: number): [Tree<L, S>, Tree<L, S>] {
193
+ if (tree === null) return [null, null];
194
+ const before = sizeOf(tree.left);
195
+ if (index <= before) {
196
+ const [a, b] = splitRoot(spec, tree.left, index);
197
+ return [a, join(spec, b, tree.leaf, tree.own, tree.right)];
198
+ }
199
+ const [a, b] = splitRoot(spec, tree.right, index - before - 1);
200
+ return [join(spec, tree.left, tree.leaf, tree.own, a, tree.inner), b];
201
+ }
202
+
203
+ /** This function splits a tree before the first leaf whose prefix summary, after `acc`, satisfies the predicate. */
204
+ function splitRootBy<L, S>(spec: Spec<L, S>, tree: Tree<L, S>, predicate: (prefix: S) => boolean, acc: S): [Tree<L, S>, Tree<L, S>] {
205
+ if (tree === null) return [null, null];
206
+ const { combine } = spec.monoid;
207
+ const throughLeft = tree.left === null ? acc : combine(acc, tree.left.summary);
208
+ if (tree.left !== null && predicate(throughLeft)) {
209
+ const [a, b] = splitRootBy(spec, tree.left, predicate, acc);
210
+ return [a, join(spec, b, tree.leaf, tree.own, tree.right)];
211
+ }
212
+ const throughLeaf = combine(throughLeft, tree.own);
213
+ if (predicate(throughLeaf)) return [tree.left, join(spec, null, tree.leaf, tree.own, tree.right)];
214
+ const [a, b] = splitRootBy(spec, tree.right, predicate, throughLeaf);
215
+ return [join(spec, tree.left, tree.leaf, tree.own, a, tree.inner), b];
216
+ }
217
+
218
+ /** This function replaces the leaf at an index in the tree. It copies only the path to the leaf. */
219
+ function replaceAt<L, S>(spec: Spec<L, S>, tree: SummaryNode<L, S>, index: number, leaf: L, own: S): SummaryNode<L, S> {
220
+ const before = sizeOf(tree.left);
221
+ if (index === before) return node(spec, tree.left, leaf, own, tree.right);
222
+ if (index < before) return node(spec, replaceAt(spec, tree.left!, index, leaf, own), tree.leaf, tree.own, tree.right);
223
+ return withRight(spec, tree, replaceAt(spec, tree.right!, index - before - 1, leaf, own));
224
+ }
225
+
226
+ /** This function puts one leaf before the leaf at an index. It copies only the path, and `join` keeps the balance. */
227
+ function insertAt<L, S>(spec: Spec<L, S>, tree: Tree<L, S>, index: number, leaf: L, own: S): SummaryNode<L, S> {
228
+ if (tree === null) return node(spec, null, leaf, own, null);
229
+ const before = sizeOf(tree.left);
230
+ if (index <= before) return join(spec, insertAt(spec, tree.left, index, leaf, own), tree.leaf, tree.own, tree.right);
231
+ return join(spec, tree.left, tree.leaf, tree.own, insertAt(spec, tree.right, index - before - 1, leaf, own), tree.inner);
232
+ }
233
+
234
+ /**
235
+ * This function replaces the leaf at an index with one or more leaves. It descends once.
236
+ * At the node of the leaf, it puts the first new leaf in the node.
237
+ * It puts the other new leaves at the start of the right subtree of that node.
238
+ */
239
+ function replaceSpan<L, S>(spec: Spec<L, S>, tree: SummaryNode<L, S>, index: number, leaves: readonly L[]): SummaryNode<L, S> {
240
+ const before = sizeOf(tree.left);
241
+ if (index < before) return join(spec, replaceSpan(spec, tree.left!, index, leaves), tree.leaf, tree.own, tree.right);
242
+ if (index > before) return join(spec, tree.left, tree.leaf, tree.own, replaceSpan(spec, tree.right!, index - before - 1, leaves), tree.inner);
243
+ let right = tree.right;
244
+ for (let i = 1; i < leaves.length; i++) right = insertAt(spec, right, i - 1, leaves[i]!, spec.measure(leaves[i]!));
245
+ return join(spec, tree.left, leaves[0]!, spec.measure(leaves[0]!), right);
246
+ }
247
+
248
+ /** This function removes the leaf at an index. It copies only the path, and `join` keeps the balance. */
249
+ function removeAt<L, S>(spec: Spec<L, S>, tree: SummaryNode<L, S>, index: number): Tree<L, S> {
250
+ const before = sizeOf(tree.left);
251
+ if (index === before) return concatRoots(spec, tree.left, tree.right);
252
+ if (index < before) return join(spec, removeAt(spec, tree.left!, index), tree.leaf, tree.own, tree.right);
253
+ return join(spec, tree.left, tree.leaf, tree.own, removeAt(spec, tree.right!, index - before - 1), tree.inner);
254
+ }
255
+
256
+ /** This function gives the leaf at an index of the tree. The index must be in the tree. */
257
+ function leafAt<L, S>(tree: SummaryNode<L, S>, index: number): L {
258
+ let n = tree;
259
+ for (;;) {
260
+ const before = sizeOf(n.left);
261
+ if (index === before) return n.leaf;
262
+ if (index < before) n = n.left!;
263
+ else {
264
+ index -= before + 1;
265
+ n = n.right!;
266
+ }
267
+ }
268
+ }
269
+
270
+ /** This function joins the leaves at `index` and `index + 1` when `spec.merge` gives one leaf for them. */
271
+ function seam<L, S>(spec: Spec<L, S>, tree: Tree<L, S>, index: number): Tree<L, S> {
272
+ const { merge } = spec;
273
+ if (merge === undefined || tree === null || index < 0 || index + 1 >= tree.size) return tree;
274
+ const merged = merge(leafAt(tree, index), leafAt(tree, index + 1));
275
+ if (merged === undefined) return tree;
276
+ return removeAt(spec, replaceAt(spec, tree, index, merged, spec.measure(merged)), index + 1);
277
+ }
278
+
279
+ /** This function makes a balanced tree of the leaves `items[lo]` to `items[hi - 1]`. */
280
+ function build<L, S>(spec: Spec<L, S>, items: readonly L[], lo: number, hi: number): Tree<L, S> {
281
+ if (lo >= hi) return null;
282
+ const mid = (lo + hi) >>> 1;
283
+ const leaf = items[mid]!;
284
+ return node(spec, build(spec, items, lo, mid), leaf, spec.measure(leaf), build(spec, items, mid + 1, hi));
285
+ }
286
+
287
+ /** The largest number of removed and inserted leaves for which `splice` copies paths instead of splitting the tree. */
288
+ const SMALL_EDIT = 8;
289
+
290
+ /** This function gives the index of a leaf, with a negative index counted from the end, or -1 when it is not in the tree. */
291
+ function indexOf(index: number, size: number): number {
292
+ if (!Number.isInteger(index)) return -1;
293
+ const at = index < 0 ? size + index : index;
294
+ return at >= 0 && at < size ? at : -1;
295
+ }
296
+
297
+ function clamp(value: number, size: number): number {
298
+ if (Number.isNaN(value)) return 0;
299
+ const whole = Math.trunc(value);
300
+ return whole < 0 ? Math.max(0, size + whole) : Math.min(whole, size);
301
+ }
302
+
303
+ /** This function gives the record with a new root, or the same record when the root does not change. */
304
+ function withRoot<L, S>(tree: SummaryTreeRecord<L, S>, root: Tree<L, S>): SummaryTreeRecord<L, S> {
305
+ return root === tree.root ? tree : { spec: tree.spec, root };
306
+ }
307
+
308
+ /** A record has no iterator. Each other value of the union is an iterable of leaves, a string too. */
309
+ function isRecord<L, S>(value: SummaryTreeRecord<L, S> | Iterable<L>): value is SummaryTreeRecord<L, S> {
310
+ return typeof value === "object" && value !== null && !(Symbol.iterator in value);
311
+ }
312
+
313
+ /** This function gives the root of a record of the same spec, or the root of a new tree of the leaves. */
314
+ function rootOf<L, S>(spec: Spec<L, S>, leaves: SummaryTreeRecord<L, S> | Iterable<L>): Tree<L, S> {
315
+ if (isRecord(leaves)) {
316
+ if (leaves.spec !== spec) throw new TypeError("SummaryTree: the two trees have different specs");
317
+ return leaves.root;
318
+ }
319
+ const items = [...leaves];
320
+ return build(spec, items, 0, items.length);
321
+ }
322
+
323
+ /**
324
+ * This function makes a persistent tree of leaves with a summary of each part. Use the same `spec` object for each
325
+ * tree that `concat` or `splice` joins.
326
+ */
327
+ export function of<Leaf, Summary>(spec: SummarySpec<Leaf, Summary>, leaves: Iterable<Leaf> = []): SummaryTreeRecord<Leaf, Summary> {
328
+ const items = [...leaves];
329
+ return { spec, root: build(spec, items, 0, items.length) };
330
+ }
331
+
332
+ /** This function gives the number of leaves. */
333
+ export function size<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>): number {
334
+ return sizeOf(tree.root);
335
+ }
336
+
337
+ /** This function tells if the tree has no leaf. */
338
+ export function isEmpty<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>): boolean {
339
+ return tree.root === null;
340
+ }
341
+
342
+ /** This function gives the summary of all the leaves, in their order. An empty tree has the summary `monoid.empty`. */
343
+ export function summary<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>): Summary {
344
+ return summaryOfRoot(tree.spec, tree.root);
345
+ }
346
+
347
+ /** This function gives the height of the tree: 0 for an empty tree. The height is at most 1.45 log2(size + 2). */
348
+ export function height<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>): number {
349
+ return heightOf(tree.root);
350
+ }
351
+
352
+ /**
353
+ * This function gives the leaf at an index, or `undefined` when the index is not in the tree. A negative index counts
354
+ * from the end, as `Array.prototype.at` does.
355
+ */
356
+ export function at<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>, index: number): Leaf | undefined {
357
+ const i = indexOf(index, sizeOf(tree.root));
358
+ return i === -1 ? undefined : leafAt(tree.root!, i);
359
+ }
360
+
361
+ /** This function gives the summary of the leaves from `start` to `end`, with `end` not included. */
362
+ export function summaryOf<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>, start = 0, end: number = sizeOf(tree.root)): Summary {
363
+ return summary(slice(tree, start, end));
364
+ }
365
+
366
+ /** This function gives the tree of the leaves of `tree`, then the leaves of `other`. Two records must have the same spec. */
367
+ export function concat<Leaf, Summary>(
368
+ tree: SummaryTreeRecord<Leaf, Summary>,
369
+ other: SummaryTreeRecord<Leaf, Summary> | Iterable<Leaf>,
370
+ ): SummaryTreeRecord<Leaf, Summary> {
371
+ const { spec } = tree;
372
+ const joined = concatRoots(spec, tree.root, rootOf(spec, other));
373
+ return withRoot(tree, seam(spec, joined, sizeOf(tree.root) - 1));
374
+ }
375
+
376
+ /** This function splits the tree into its first `index` leaves and the other leaves. */
377
+ export function splitAt<Leaf, Summary>(
378
+ tree: SummaryTreeRecord<Leaf, Summary>,
379
+ index: number,
380
+ ): [SummaryTreeRecord<Leaf, Summary>, SummaryTreeRecord<Leaf, Summary>] {
381
+ const [left, right] = splitRoot(tree.spec, tree.root, clamp(index, sizeOf(tree.root)));
382
+ return [withRoot(tree, left), withRoot(tree, right)];
383
+ }
384
+
385
+ /**
386
+ * This function splits the tree before the first leaf whose prefix summary satisfies the predicate. The prefix of a
387
+ * leaf includes the leaf. The predicate must be monotone: after it is true for a prefix, it is true for each longer
388
+ * prefix. When no prefix satisfies it, the right tree is empty.
389
+ */
390
+ export function split<Leaf, Summary>(
391
+ tree: SummaryTreeRecord<Leaf, Summary>,
392
+ predicate: (prefix: Summary) => boolean,
393
+ ): [SummaryTreeRecord<Leaf, Summary>, SummaryTreeRecord<Leaf, Summary>] {
394
+ const [left, right] = splitRootBy(tree.spec, tree.root, predicate, tree.spec.monoid.empty);
395
+ return [withRoot(tree, left), withRoot(tree, right)];
396
+ }
397
+
398
+ /**
399
+ * This function finds the first leaf whose prefix summary satisfies a monotone predicate, as `split` does. It changes
400
+ * nothing. It gives `undefined` when no prefix satisfies the predicate.
401
+ */
402
+ export function search<Leaf, Summary>(
403
+ tree: SummaryTreeRecord<Leaf, Summary>,
404
+ predicate: (prefix: Summary) => boolean,
405
+ ): SummarySearch<Leaf, Summary> | undefined {
406
+ const { combine, empty } = tree.spec.monoid;
407
+ let n = tree.root;
408
+ let acc = empty;
409
+ let index = 0;
410
+ while (n !== null) {
411
+ const throughLeft = n.left === null ? acc : combine(acc, n.left.summary);
412
+ if (n.left !== null && predicate(throughLeft)) {
413
+ n = n.left;
414
+ continue;
415
+ }
416
+ const through = combine(throughLeft, n.own);
417
+ if (predicate(through)) return { index: index + sizeOf(n.left), leaf: n.leaf, before: throughLeft, through };
418
+ index += sizeOf(n.left) + 1;
419
+ acc = through;
420
+ n = n.right;
421
+ }
422
+ return undefined;
423
+ }
424
+
425
+ /**
426
+ * This function finds the leaf that holds a position of an additive key, and it calls no `combine`. The key must be
427
+ * additive and not negative: `key(combine(a, b))` is `key(a) + key(b)`. The function descends by the key of each left
428
+ * subtree and of each leaf, and subtracts it from the target. It gives the first leaf whose prefix key is more than
429
+ * the target, or `undefined` when the target is not in `[0, key(summary))`.
430
+ *
431
+ * @example
432
+ * // The leaf and the offset in it of the code unit at position 1000 of a rope
433
+ * const at = Tree.locate(rope, (s) => s.length, 1000);
434
+ */
435
+ export function locate<Leaf, Summary>(
436
+ tree: SummaryTreeRecord<Leaf, Summary>,
437
+ key: (summary: Summary) => number,
438
+ target: number,
439
+ ): SummaryLocation<Leaf> | undefined {
440
+ if (!(target >= 0)) return undefined;
441
+ let n = tree.root;
442
+ let index = 0;
443
+ let rest = target;
444
+ while (n !== null) {
445
+ if (n.left !== null) {
446
+ const left = key(n.left.summary);
447
+ if (rest < left) {
448
+ n = n.left;
449
+ continue;
450
+ }
451
+ rest -= left;
452
+ index += n.left.size;
453
+ }
454
+ const own = key(n.own);
455
+ if (rest < own) return { index, leaf: n.leaf, offset: rest };
456
+ rest -= own;
457
+ index += 1;
458
+ n = n.right;
459
+ }
460
+ return undefined;
461
+ }
462
+
463
+ /** This function gives the tree of the leaves from `start` to `end`, with `end` not included. A negative index counts from the end. */
464
+ export function slice<Leaf, Summary>(
465
+ tree: SummaryTreeRecord<Leaf, Summary>,
466
+ start = 0,
467
+ end: number = sizeOf(tree.root),
468
+ ): SummaryTreeRecord<Leaf, Summary> {
469
+ const n = sizeOf(tree.root);
470
+ const from = clamp(start, n);
471
+ const to = clamp(end, n);
472
+ if (to <= from) return withRoot(tree, null);
473
+ const [, rest] = splitRoot(tree.spec, tree.root, from);
474
+ const [middle] = splitRoot(tree.spec, rest, to - from);
475
+ return withRoot(tree, middle);
476
+ }
477
+
478
+ /**
479
+ * This function removes `deleteCount` leaves at `start` and puts the leaves of `inserted` there, as
480
+ * `Array.prototype.splice` does. It gives the new tree, not the removed leaves.
481
+ */
482
+ export function splice<Leaf, Summary>(
483
+ tree: SummaryTreeRecord<Leaf, Summary>,
484
+ start: number,
485
+ deleteCount: number = sizeOf(tree.root),
486
+ inserted: SummaryTreeRecord<Leaf, Summary> | Iterable<Leaf> = [],
487
+ ): SummaryTreeRecord<Leaf, Summary> {
488
+ const { spec } = tree;
489
+ const n = sizeOf(tree.root);
490
+ const from = clamp(start, n);
491
+ const count = Math.max(0, Math.min(Number.isNaN(deleteCount) ? 0 : Math.trunc(deleteCount), n - from));
492
+ const leaves = isRecord(inserted) ? undefined : [...inserted];
493
+ const added = leaves === undefined ? sizeOf((inserted as SummaryTreeRecord<Leaf, Summary>).root) : leaves.length;
494
+ if (count === 0 && added === 0) return tree;
495
+ let root = tree.root;
496
+ if (leaves !== undefined && count + added <= SMALL_EDIT) {
497
+ // A small edit copies only the paths to its leaves: 1 or 2 combines for each level of each path
498
+ const replaced = Math.min(count, added);
499
+ if (count > 0 && added > count) {
500
+ // The last removed leaf takes all the extra leaves in one descent
501
+ for (let i = 0; i < count - 1; i++) root = replaceAt(spec, root!, from + i, leaves[i]!, spec.measure(leaves[i]!));
502
+ root = replaceSpan(spec, root!, from + count - 1, leaves.slice(count - 1));
503
+ } else for (let i = 0; i < added; i++) {
504
+ const leaf = leaves[i]!;
505
+ const own = spec.measure(leaf);
506
+ root = i < replaced ? replaceAt(spec, root!, from + i, leaf, own) : insertAt(spec, root, from + i, leaf, own);
507
+ }
508
+ for (let i = replaced; i < count; i++) root = removeAt(spec, root!, from + added);
509
+ } else {
510
+ const middle = leaves === undefined ? rootOf(spec, inserted) : build(spec, leaves, 0, leaves.length);
511
+ const [left, rest] = splitRoot(spec, root, from);
512
+ const [, right] = splitRoot(spec, rest, count);
513
+ root = concatRoots(spec, concatRoots(spec, left, middle), right);
514
+ }
515
+ // The right seam first, so that a merge there does not move the index of the left seam
516
+ if (added > 0) root = seam(spec, root, from + added - 1);
517
+ return withRoot(tree, seam(spec, root, from - 1));
518
+ }
519
+
520
+ /** This function puts the leaves of `inserted` before the leaf at `index`. */
521
+ export function insert<Leaf, Summary>(
522
+ tree: SummaryTreeRecord<Leaf, Summary>,
523
+ index: number,
524
+ inserted: SummaryTreeRecord<Leaf, Summary> | Iterable<Leaf>,
525
+ ): SummaryTreeRecord<Leaf, Summary> {
526
+ return splice(tree, index, 0, inserted);
527
+ }
528
+
529
+ /** This function removes the leaves from `start` to `end`, with `end` not included. */
530
+ export function remove<Leaf, Summary>(
531
+ tree: SummaryTreeRecord<Leaf, Summary>,
532
+ start: number,
533
+ end: number = sizeOf(tree.root),
534
+ ): SummaryTreeRecord<Leaf, Summary> {
535
+ const n = sizeOf(tree.root);
536
+ const from = clamp(start, n);
537
+ return splice(tree, from, Math.max(0, clamp(end, n) - from));
538
+ }
539
+
540
+ /**
541
+ * This function replaces the leaf at an index. A negative index counts from the end, as `Array.prototype.with` does.
542
+ * An index that is not in the tree changes nothing. It copies only the path to the leaf, and it does not call
543
+ * `spec.merge`.
544
+ */
545
+ export function set<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>, index: number, leaf: Leaf): SummaryTreeRecord<Leaf, Summary> {
546
+ const i = indexOf(index, sizeOf(tree.root));
547
+ return i === -1 ? tree : withRoot(tree, replaceAt(tree.spec, tree.root!, i, leaf, tree.spec.measure(leaf)));
548
+ }
549
+
550
+ /** This function adds leaves after the last leaf. */
551
+ export function append<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>, ...leaves: Leaf[]): SummaryTreeRecord<Leaf, Summary> {
552
+ return concat(tree, leaves);
553
+ }
554
+
555
+ /** This function adds leaves before the first leaf. */
556
+ export function prepend<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>, ...leaves: Leaf[]): SummaryTreeRecord<Leaf, Summary> {
557
+ const { spec } = tree;
558
+ const joined = concatRoots(spec, rootOf(spec, leaves), tree.root);
559
+ return withRoot(tree, seam(spec, joined, leaves.length - 1));
560
+ }
561
+
562
+ /** This function gives the leaves in their order. */
563
+ export function* leaves<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>): Generator<Leaf, void, undefined> {
564
+ // An explicit stack: the depth of the tree is O(log n), so the stack stays small
565
+ const stack: SummaryNode<Leaf, Summary>[] = [];
566
+ let n = tree.root;
567
+ while (n !== null || stack.length > 0) {
568
+ while (n !== null) {
569
+ stack.push(n);
570
+ n = n.left;
571
+ }
572
+ const top = stack.pop()!;
573
+ yield top.leaf;
574
+ n = top.right;
575
+ }
576
+ }
577
+
578
+ /** This function gives the leaves in an array. */
579
+ export function toArray<Leaf, Summary>(tree: SummaryTreeRecord<Leaf, Summary>): Leaf[] {
580
+ return [...leaves(tree)];
581
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The entry `@mark1russell7/collections/summary-tree`: the summary tree as plain functions over a plain record.
3
+ * The functions are in `functions.ts`, which the class of the root entry imports too.
4
+ */
5
+
6
+ export * from "./functions.js";