@mark1russell7/collections 0.9.7 → 0.9.8

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@mark1russell7/collections",
4
- "version": "0.9.7",
4
+ "version": "0.9.8",
5
5
  "description": "Collections with no dependencies: lists, maps, queues, a summary tree, channels, lazy operations and dependency graphs.",
6
6
  "keywords": [
7
7
  "collections",
@@ -19,10 +19,11 @@
19
19
  import type { Middleware } from "../core/middleware.js";
20
20
  import type { List } from "../interfaces/list.js";
21
21
  import type { Queue, Deque } from "../interfaces/queue.js";
22
- import type { MapLike } from "../interfaces/map.js";
22
+ import type { Entry, MapLike } from "../interfaces/map.js";
23
23
  import type { Collection } from "../interfaces/collection.js";
24
24
  import { Emitter, type Unsubscribe } from "../core/events.js";
25
25
  import { removalsOf, type ElementRemoval, type MapRemoval, type RemovalReason } from "../core/removals.js";
26
+ import { compositeMapMethod } from "./composite.js";
26
27
 
27
28
  /** The number of removals that a layer heard from the layers below it, and the number from the first end. */
28
29
  interface Heard {
@@ -48,6 +49,62 @@ function hear<R extends ElementRemoval<unknown> | MapRemoval<unknown, unknown>>(
48
49
  const added = (size: number, sizeBefore: number, heard: Heard, heardBefore: number): boolean =>
49
50
  size - sizeBefore + heard.all - heardBefore > 0;
50
51
 
52
+ /**
53
+ * This function gives the removals that change `before` to `after`, in the order of a replay.
54
+ * Each index is the place of the value at the time of its removal. It gives undefined when `after` is not in the order of `before`.
55
+ */
56
+ function removalsInOrder<T>(before: readonly T[], after: readonly T[]): { value: T; index: number }[] | undefined {
57
+ const removed: { value: T; index: number }[] = [];
58
+ let kept = 0;
59
+ for (const value of before) {
60
+ if (kept < after.length && Object.is(value, after[kept])) kept++;
61
+ else removed.push({ value, index: kept });
62
+ }
63
+ return kept === after.length ? removed : undefined;
64
+ }
65
+
66
+ /** This function gives the values of `before` that are not in `after`, with each copy counted. The order does not matter. */
67
+ function removedValues<T>(before: readonly T[], after: readonly T[]): T[] {
68
+ const left = new Map<T, number>();
69
+ for (const value of after) left.set(value, (left.get(value) ?? 0) + 1);
70
+ const removed: T[] = [];
71
+ for (const value of before) {
72
+ const count = left.get(value) ?? 0;
73
+ if (count > 0) left.set(value, count - 1);
74
+ else removed.push(value);
75
+ }
76
+ return removed;
77
+ }
78
+
79
+ /**
80
+ * This function wraps a bulk removal (removeAll, retainAll, removeRange) of the collection below.
81
+ * The base does the removal with its own equality, and `report` gets the contents before and after it.
82
+ */
83
+ function bulkRemoval<T>(
84
+ target: { readonly size: number; toArray(): T[] },
85
+ method: Function,
86
+ report: (before: T[], after: T[]) => void,
87
+ ): (...args: unknown[]) => unknown {
88
+ return function (...args: unknown[]): unknown {
89
+ const before = target.toArray();
90
+ const result = method.apply(target, args);
91
+ if (target.size !== before.length) report(before, target.toArray());
92
+ return result;
93
+ };
94
+ }
95
+
96
+ /**
97
+ * This function adds each element with `add` of the layer, one after the other.
98
+ * Thus each element gives its own event, with the rules of a single add. It gives true when one element goes in.
99
+ */
100
+ function addEach<T>(elements: Iterable<T>, add: (element: T) => boolean): boolean {
101
+ let changed = false;
102
+ for (const element of Array.from(elements)) {
103
+ if (add(element)) changed = true;
104
+ }
105
+ return changed;
106
+ }
107
+
51
108
  // ============================================================================
52
109
  // Event type definitions
53
110
  // ============================================================================
@@ -78,6 +135,8 @@ export interface QueueEvents<T> {
78
135
  enqueue: { value: T };
79
136
  /** Element was removed from queue. A removal by a layer below (a drop) has a `reason`. */
80
137
  dequeue: { value: T; reason?: RemovalReason };
138
+ /** An element left from a place that is not the head: `remove`, `removeAll` or `retainAll`. */
139
+ remove: { value: T };
81
140
  /** Collection was cleared */
82
141
  clear: {};
83
142
  }
@@ -94,6 +153,8 @@ export interface DequeEvents<T> {
94
153
  removeFirst: { value: T; reason?: RemovalReason };
95
154
  /** Element was removed from back. A removal by a layer below (a drop) has a `reason`. */
96
155
  removeLast: { value: T; reason?: RemovalReason };
156
+ /** An element left from `index`, not with a method of an end: `remove`, `removeAll` or `retainAll`. */
157
+ remove: { value: T; index: number };
97
158
  /** Collection was cleared */
98
159
  clear: {};
99
160
  }
@@ -232,6 +293,46 @@ export function eventedList<T>(): Middleware<
232
293
  emitter.emit("clear", {});
233
294
  };
234
295
 
296
+ // The base writes a bulk change into its storage, and does not call its own add or remove (C24)
297
+ case "addAll":
298
+ return function (elements: Iterable<T>): boolean {
299
+ return addEach(elements, (element) => {
300
+ const size = target.size;
301
+ const all = heard.all;
302
+ (receiver as List<T>).add(element);
303
+ return added(target.size, size, heard, all);
304
+ });
305
+ };
306
+
307
+ case "insertAll":
308
+ return function (index: number, elements: Iterable<T>): boolean {
309
+ let at = index;
310
+ return addEach(elements, (element) => {
311
+ const size = target.size;
312
+ const all = heard.all;
313
+ const first = heard.first;
314
+ (receiver as List<T>).insert(at, element);
315
+ const wentIn = added(target.size, size, heard, all);
316
+ // A drop of the first element below moved the place one lower. The next element goes after a new one.
317
+ at = Math.min(target.size, Math.max(0, at - (heard.first - first)) + (wentIn ? 1 : 0));
318
+ return wentIn;
319
+ });
320
+ };
321
+
322
+ case "removeAll":
323
+ case "retainAll":
324
+ case "removeRange":
325
+ return bulkRemoval<T>(target, value as Function, (before, after) => {
326
+ const removed = removalsInOrder(before, after);
327
+ if (removed) {
328
+ for (const removal of removed) emitter.emit("remove", removal);
329
+ return;
330
+ }
331
+ // A base that changed the order of the rest: a clear and the adds give the same contents
332
+ emitter.emit("clear", {});
333
+ after.forEach((element, at) => emitter.emit("add", { value: element, index: at }));
334
+ });
335
+
235
336
  case "sort":
236
337
  return function (...args: any[]): void {
237
338
  (value as Function).apply(target, args);
@@ -297,11 +398,32 @@ export function eventedQueue<T>(): Middleware<
297
398
  return removed;
298
399
  };
299
400
 
401
+ case "pollOrUndefined":
402
+ return function (): T | undefined {
403
+ const size = target.size;
404
+ const removed = (value as Function).call(target);
405
+ if (target.size < size) emitter.emit("dequeue", { value: removed });
406
+ return removed;
407
+ };
408
+
300
409
  case "clear":
301
410
  return function (): void {
302
411
  (value as Function).call(target);
303
412
  emitter.emit("clear", {});
304
413
  };
414
+
415
+ // The base writes a bulk change into its storage, and does not call its own add or remove (C24)
416
+ case "addAll":
417
+ return function (elements: Iterable<T>): boolean {
418
+ return addEach(elements, (element) => (receiver as Queue<T>).add(element) !== false);
419
+ };
420
+
421
+ case "remove":
422
+ case "removeAll":
423
+ case "retainAll":
424
+ return bulkRemoval<T>(target, value as Function, (before, after) => {
425
+ for (const removed of removedValues(before, after)) emitter.emit("remove", { value: removed });
426
+ });
305
427
  }
306
428
  }
307
429
 
@@ -380,11 +502,44 @@ export function eventedDeque<T>(): Middleware<
380
502
  return removed;
381
503
  };
382
504
 
505
+ case "pollOrUndefined":
506
+ return function (): T | undefined {
507
+ const size = target.size;
508
+ const removed = (value as Function).call(target);
509
+ if (target.size < size) emitter.emit("removeFirst", { value: removed });
510
+ return removed;
511
+ };
512
+
383
513
  case "clear":
384
514
  return function (): void {
385
515
  (value as Function).call(target);
386
516
  emitter.emit("clear", {});
387
517
  };
518
+
519
+ // The base writes a bulk change into its storage, and does not call its own add or remove (C24)
520
+ case "addAll":
521
+ return function (elements: Iterable<T>): boolean {
522
+ return addEach(elements, (element) => {
523
+ const size = target.size;
524
+ const all = heard.all;
525
+ (receiver as Deque<T>).addLast(element);
526
+ return added(target.size, size, heard, all);
527
+ });
528
+ };
529
+
530
+ case "remove":
531
+ case "removeAll":
532
+ case "retainAll":
533
+ return bulkRemoval<T>(target, value as Function, (before, after) => {
534
+ const removed = removalsInOrder(before, after);
535
+ if (removed) {
536
+ for (const removal of removed) emitter.emit("remove", removal);
537
+ return;
538
+ }
539
+ // A base that changed the order of the rest: a clear and the adds give the same contents
540
+ emitter.emit("clear", {});
541
+ for (const element of after) emitter.emit("addLast", { value: element });
542
+ });
388
543
  }
389
544
  }
390
545
 
@@ -416,6 +571,13 @@ export function eventedMap<K, V>(): Middleware<
416
571
  return emitter.on.bind(emitter);
417
572
  }
418
573
 
574
+ // Composite methods (computeIfAbsent, compute, merge, putAll and others) go through this
575
+ // proxy's own set and delete, so each change gives its event (C24). The base calls its own set.
576
+ const composite = compositeMapMethod(receiver as MapLike<K, V>, prop);
577
+ if (composite) {
578
+ return composite;
579
+ }
580
+
419
581
  const value = Reflect.get(target, prop, receiver);
420
582
 
421
583
  if (typeof value === "function") {
@@ -441,6 +603,15 @@ export function eventedMap<K, V>(): Middleware<
441
603
  return deletedValue;
442
604
  };
443
605
 
606
+ // The first or last entry of a navigable map (treeMap)
607
+ case "pollFirstEntry":
608
+ case "pollLastEntry":
609
+ return function (): Entry<K, V> | undefined {
610
+ const entry: Entry<K, V> | undefined = (value as Function).call(target);
611
+ if (entry !== undefined) emitter.emit("delete", { key: entry.key, value: entry.value });
612
+ return entry;
613
+ };
614
+
444
615
  case "clear":
445
616
  return function (): void {
446
617
  (value as Function).call(target);
@@ -504,6 +675,32 @@ export function evented<T>(): Middleware<
504
675
  (value as Function).call(target);
505
676
  emitter.emit("clear", {});
506
677
  };
678
+
679
+ // The base writes a bulk change into its storage, and does not call its own add or remove (C24)
680
+ case "addAll":
681
+ return function (elements: Iterable<T>): boolean {
682
+ let changed = false;
683
+ for (const element of Array.from(elements)) {
684
+ if ((receiver as Collection<T>).add(element)) changed = true;
685
+ }
686
+ return changed;
687
+ };
688
+
689
+ case "removeAll":
690
+ case "retainAll":
691
+ return bulkRemoval<T>(target, value as Function, (before, after) => {
692
+ for (const removed of removedValues(before, after)) emitter.emit("remove", { value: removed });
693
+ });
694
+
695
+ // The first or last element of a sorted set (treeSet)
696
+ case "pollFirst":
697
+ case "pollLast":
698
+ return function (): T | undefined {
699
+ const size = target.size;
700
+ const removed = (value as Function).call(target);
701
+ if (target.size < size) emitter.emit("remove", { value: removed });
702
+ return removed;
703
+ };
507
704
  }
508
705
  }
509
706