atomic-fns 1.1.7 → 2.0.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.
- package/README.md +405 -60
- package/dist/array/index.d.ts +9 -0
- package/dist/array/index.js +9 -0
- package/dist/array/list.d.ts +99 -0
- package/dist/array/list.js +133 -0
- package/dist/array/sets.d.ts +130 -0
- package/dist/array/sets.js +213 -0
- package/dist/async/index.d.ts +6 -0
- package/dist/async/index.js +6 -0
- package/dist/async/retry.d.ts +38 -0
- package/dist/async/retry.js +56 -0
- package/dist/async/sleep.d.ts +42 -0
- package/dist/async/sleep.js +76 -0
- package/dist/collections/bisect.d.ts +103 -0
- package/dist/collections/bisect.js +157 -0
- package/dist/collections/deque.d.ts +67 -97
- package/dist/collections/deque.js +197 -249
- package/dist/collections/entries.d.ts +89 -0
- package/dist/collections/entries.js +131 -0
- package/dist/collections/frozen-set.d.ts +42 -0
- package/dist/collections/frozen-set.js +64 -0
- package/dist/collections/group.d.ts +141 -0
- package/dist/collections/group.js +241 -0
- package/dist/collections/heap.d.ts +64 -0
- package/dist/collections/heap.js +205 -0
- package/dist/collections/index.d.ts +15 -642
- package/dist/collections/index.js +14 -716
- package/dist/collections/internal/arrayKeys.d.ts +19 -0
- package/dist/collections/internal/arrayKeys.js +83 -0
- package/dist/collections/internal/invariants.d.ts +4 -0
- package/dist/collections/internal/invariants.js +12 -0
- package/dist/collections/internal/iterable.d.ts +25 -0
- package/dist/collections/internal/iterable.js +62 -0
- package/dist/collections/iterate.d.ts +162 -0
- package/dist/collections/iterate.js +188 -0
- package/dist/collections/lru-cache.d.ts +58 -0
- package/dist/collections/lru-cache.js +164 -0
- package/dist/collections/order.d.ts +19 -0
- package/dist/collections/order.js +38 -0
- package/dist/collections/shape.d.ts +32 -0
- package/dist/collections/shape.js +86 -0
- package/dist/collections/sorted-map.d.ts +171 -0
- package/dist/collections/sorted-map.js +845 -0
- package/dist/collections/sorted-set.d.ts +103 -0
- package/dist/collections/sorted-set.js +147 -0
- package/dist/collections/splay-tree.d.ts +82 -0
- package/dist/collections/splay-tree.js +354 -0
- package/dist/collections/trie.d.ts +58 -0
- package/dist/collections/trie.js +196 -0
- package/dist/decimal/currency.d.ts +211 -0
- package/dist/decimal/currency.js +321 -0
- package/dist/decimal/decimal.d.ts +299 -0
- package/dist/decimal/decimal.js +660 -0
- package/dist/decimal/index.d.ts +11 -145
- package/dist/decimal/index.js +10 -374
- package/dist/decimal/internal/bits.d.ts +2 -0
- package/dist/decimal/internal/bits.js +7 -0
- package/dist/decimal/internal/echo.d.ts +4 -0
- package/dist/decimal/internal/echo.js +6 -0
- package/dist/decimal/internal/isqrt.d.ts +9 -0
- package/dist/decimal/internal/isqrt.js +34 -0
- package/dist/decimal/internal/locale.d.ts +7 -0
- package/dist/decimal/internal/locale.js +24 -0
- package/dist/decimal/internal/minor-units.d.ts +2 -0
- package/dist/decimal/internal/minor-units.js +33 -0
- package/dist/decimal/internal/pow10.d.ts +24 -0
- package/dist/decimal/internal/pow10.js +96 -0
- package/dist/encoding/base64.d.ts +39 -0
- package/dist/encoding/base64.js +53 -0
- package/dist/encoding/hex.d.ts +23 -0
- package/dist/encoding/hex.js +34 -0
- package/dist/encoding/index.d.ts +6 -0
- package/dist/encoding/index.js +6 -0
- package/dist/function/debounce.d.ts +68 -0
- package/dist/function/debounce.js +203 -0
- package/dist/function/index.d.ts +6 -0
- package/dist/function/index.js +6 -0
- package/dist/function/shared.d.ts +2 -0
- package/dist/function/shared.js +5 -0
- package/dist/function/wrap.d.ts +130 -0
- package/dist/function/wrap.js +113 -0
- package/dist/hash/digest.d.ts +18 -0
- package/dist/hash/digest.js +26 -0
- package/dist/hash/hash.d.ts +22 -0
- package/dist/hash/hash.js +252 -0
- package/dist/hash/hashString.d.ts +13 -0
- package/dist/hash/hashString.js +53 -0
- package/dist/hash/index.d.ts +8 -0
- package/dist/hash/index.js +8 -0
- package/dist/hash/stableStringify.d.ts +19 -0
- package/dist/hash/stableStringify.js +102 -0
- package/dist/id/index.d.ts +7 -0
- package/dist/id/index.js +7 -0
- package/dist/id/nanoid.d.ts +16 -0
- package/dist/id/nanoid.js +65 -0
- package/dist/id/uniqueId.d.ts +14 -0
- package/dist/id/uniqueId.js +20 -0
- package/dist/id/uuid.d.ts +25 -0
- package/dist/id/uuid.js +64 -0
- package/dist/index.d.ts +15 -8
- package/dist/index.js +15 -8
- package/dist/internal/bytes.d.ts +11 -0
- package/dist/internal/bytes.js +33 -0
- package/dist/internal/codec.d.ts +11 -0
- package/dist/internal/codec.js +84 -0
- package/dist/internal/compare.d.ts +10 -0
- package/dist/internal/compare.js +26 -0
- package/dist/internal/crypto.d.ts +5 -0
- package/dist/internal/crypto.js +11 -0
- package/dist/internal/keys.d.ts +12 -0
- package/dist/internal/keys.js +23 -0
- package/dist/internal/kind.d.ts +44 -0
- package/dist/internal/kind.js +139 -0
- package/dist/internal/sparse.d.ts +51 -0
- package/dist/internal/sparse.js +104 -0
- package/dist/internal/text.d.ts +7 -0
- package/dist/internal/text.js +15 -0
- package/dist/internal/timer.d.ts +10 -0
- package/dist/internal/timer.js +17 -0
- package/dist/internal/truncate.d.ts +10 -0
- package/dist/internal/truncate.js +22 -0
- package/dist/internal/walk.d.ts +47 -0
- package/dist/internal/walk.js +100 -0
- package/dist/is/index.d.ts +8 -0
- package/dist/is/index.js +8 -0
- package/dist/is/isArrayLike.d.ts +18 -0
- package/dist/is/isArrayLike.js +25 -0
- package/dist/is/isError.d.ts +13 -0
- package/dist/is/isError.js +18 -0
- package/dist/is/isNil.d.ts +12 -0
- package/dist/is/isNil.js +14 -0
- package/dist/is/isPlainObject.d.ts +15 -0
- package/dist/is/isPlainObject.js +34 -0
- package/dist/number/index.d.ts +7 -0
- package/dist/number/index.js +7 -0
- package/dist/number/round.d.ts +86 -0
- package/dist/number/round.js +130 -0
- package/dist/number/stats.d.ts +52 -0
- package/dist/number/stats.js +115 -0
- package/dist/object/cloneDeep.d.ts +28 -0
- package/dist/object/cloneDeep.js +200 -0
- package/dist/object/index.d.ts +14 -0
- package/dist/object/index.js +13 -0
- package/dist/object/isEqual.d.ts +64 -0
- package/dist/object/isEqual.js +289 -0
- package/dist/object/matches.d.ts +40 -0
- package/dist/object/matches.js +284 -0
- package/dist/object/merge.d.ts +66 -0
- package/dist/object/merge.js +272 -0
- package/dist/object/parsePath.d.ts +22 -0
- package/dist/object/parsePath.js +111 -0
- package/dist/object/path.d.ts +85 -0
- package/dist/object/path.js +208 -0
- package/dist/object/pick.d.ts +91 -0
- package/dist/object/pick.js +180 -0
- package/dist/object/reshape.d.ts +73 -0
- package/dist/object/reshape.js +144 -0
- package/dist/random/index.d.ts +96 -32
- package/dist/random/index.js +215 -50
- package/dist/string/case.d.ts +71 -0
- package/dist/string/case.js +152 -0
- package/dist/string/fold.d.ts +35 -0
- package/dist/string/fold.js +100 -0
- package/dist/string/index.d.ts +6 -187
- package/dist/string/index.js +6 -226
- package/dist/string/truncate.d.ts +25 -0
- package/dist/string/truncate.js +44 -0
- package/dist/string/words.d.ts +27 -0
- package/dist/string/words.js +241 -0
- package/package.json +101 -58
- package/dist/collections/BTree.d.ts +0 -229
- package/dist/collections/BTree.js +0 -1165
- package/dist/collections/Heap.d.ts +0 -69
- package/dist/collections/Heap.js +0 -181
- package/dist/collections/LRUCache.d.ts +0 -80
- package/dist/collections/LRUCache.js +0 -126
- package/dist/collections/SortedMap.d.ts +0 -92
- package/dist/collections/SortedMap.js +0 -131
- package/dist/collections/SortedSet.d.ts +0 -69
- package/dist/collections/SortedSet.js +0 -99
- package/dist/collections/SortedTree.d.ts +0 -103
- package/dist/collections/SortedTree.js +0 -141
- package/dist/collections/SplayTree.d.ts +0 -105
- package/dist/collections/SplayTree.js +0 -338
- package/dist/collections/Trie.d.ts +0 -42
- package/dist/collections/Trie.js +0 -104
- package/dist/collections/abc.d.ts +0 -152
- package/dist/collections/abc.js +0 -39
- package/dist/collections/frozenset.d.ts +0 -19
- package/dist/collections/frozenset.js +0 -30
- package/dist/functools/index.d.ts +0 -130
- package/dist/functools/index.js +0 -235
- package/dist/globals/index.d.ts +0 -285
- package/dist/globals/index.js +0 -463
- package/dist/i18n/currency.d.ts +0 -24
- package/dist/i18n/currency.js +0 -85
- package/dist/i18n/date/index.d.ts +0 -203
- package/dist/i18n/date/index.js +0 -531
- package/dist/i18n/date/utils.d.ts +0 -65
- package/dist/i18n/date/utils.js +0 -290
- package/dist/i18n/duration.d.ts +0 -112
- package/dist/i18n/duration.js +0 -392
- package/dist/i18n/format.d.ts +0 -31
- package/dist/i18n/format.js +0 -506
- package/dist/i18n/index.d.ts +0 -8
- package/dist/i18n/index.js +0 -8
- package/dist/itertools/index.d.ts +0 -255
- package/dist/itertools/index.js +0 -437
- package/dist/math/index.d.ts +0 -96
- package/dist/math/index.js +0 -98
- package/dist/mod.d.ts +0 -1
- package/dist/mod.js +0 -1
- package/dist/operators/index.d.ts +0 -152
- package/dist/operators/index.js +0 -305
- package/dist/types.d.ts +0 -19
- package/dist/types.js +0 -1
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
import { defaultCompare } from '../internal/compare.js';
|
|
2
|
+
import { registerSortedMapCheck } from './internal/invariants.js';
|
|
3
|
+
import { arrayFromIterableMap } from './internal/iterable.js';
|
|
4
|
+
const NODE_CAPACITY = 64;
|
|
5
|
+
const MIN_FILL = 32;
|
|
6
|
+
const ASCENDING = 1;
|
|
7
|
+
const DESCENDING = -1;
|
|
8
|
+
function countOf(n) {
|
|
9
|
+
return n.kind === 'leaf' ? n.keys.length : n.total;
|
|
10
|
+
}
|
|
11
|
+
function itemCount(n) {
|
|
12
|
+
return n.kind === 'leaf' ? n.keys.length : n.children.length;
|
|
13
|
+
}
|
|
14
|
+
/** The largest key under `n`. Only an empty root leaf holds no key, and nothing asks it. */
|
|
15
|
+
function lastKeyOf(n) {
|
|
16
|
+
return n.kind === 'leaf' ? n.keys[n.keys.length - 1] : n.maxKeys[n.maxKeys.length - 1];
|
|
17
|
+
}
|
|
18
|
+
function emptyLeaf(owner, keyOnly) {
|
|
19
|
+
return { kind: 'leaf', owner, keys: [], values: keyOnly ? undefined : [] };
|
|
20
|
+
}
|
|
21
|
+
function valueAt(leaf, index) {
|
|
22
|
+
return leaf.values === undefined ? true : leaf.values[index];
|
|
23
|
+
}
|
|
24
|
+
export let createSetMap;
|
|
25
|
+
export let insertIfAbsent;
|
|
26
|
+
function isNonPrimitive(value) {
|
|
27
|
+
return Object(value) === value;
|
|
28
|
+
}
|
|
29
|
+
function assertOptions(options) {
|
|
30
|
+
if (options !== undefined && !isNonPrimitive(options)) {
|
|
31
|
+
throw new TypeError('options must be an object');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** A `-0` key is stored as `+0`, as `Map` does. */
|
|
35
|
+
function positiveZero(key) {
|
|
36
|
+
return key === 0 ? 0 : key;
|
|
37
|
+
}
|
|
38
|
+
/** Columns rather than `[key, value]` boxes: a bulk load holds every item at once. */
|
|
39
|
+
function readEntries(entries) {
|
|
40
|
+
const values = [];
|
|
41
|
+
const keys = arrayFromIterableMap(entries, (entry) => {
|
|
42
|
+
if (!isNonPrimitive(entry))
|
|
43
|
+
throw new TypeError('entries must be [key, value] pairs');
|
|
44
|
+
const pair = entry;
|
|
45
|
+
values.push(pair[1]);
|
|
46
|
+
return positiveZero(pair[0]);
|
|
47
|
+
});
|
|
48
|
+
return { keys, values };
|
|
49
|
+
}
|
|
50
|
+
function resolveLimit(limit) {
|
|
51
|
+
if (limit === undefined || limit === Infinity)
|
|
52
|
+
return Infinity;
|
|
53
|
+
if (typeof limit !== 'number')
|
|
54
|
+
throw new TypeError('limit must be a number');
|
|
55
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
56
|
+
throw new RangeError('limit must be a non-negative integer or Infinity');
|
|
57
|
+
}
|
|
58
|
+
return limit;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A map whose keys stay sorted, on a copy-on-write B+ tree with subtree counts. It has `Map`'s
|
|
62
|
+
* method names and iterates in key order, adds order queries (`firstKey`, `lastKey`, `lower`,
|
|
63
|
+
* `higher`, `floor`, `ceiling`), position queries (`at`, `rank`) and range operations
|
|
64
|
+
* (`entriesBetween`, `updateRange`, `removeRange`), and clones in O(1): a clone shares nodes with
|
|
65
|
+
* its source until either one writes. Keys the comparator calls equal are the
|
|
66
|
+
* same key, and a `-0` key is stored as `+0`, as `Map` does. With the default comparator, keys are
|
|
67
|
+
* numbers, bigints or strings: `NaN` throws `RangeError`, and `null`, `undefined` and other values
|
|
68
|
+
* throw `TypeError`; pass a comparator to order them.
|
|
69
|
+
*
|
|
70
|
+
* The comparator must be pure, return a finite number for every pair, and return 0 for a key
|
|
71
|
+
* compared with itself; every new key is checked, and a comparator that breaks these rules throws
|
|
72
|
+
* `TypeError` and leaves the map unchanged. Subtracting numbers fails for `Infinity` and `NaN`
|
|
73
|
+
* keys (`Infinity - x` is not finite); the default comparator handles every number but `NaN`.
|
|
74
|
+
*
|
|
75
|
+
* Under a strict total order, changing the map while iterating it never repeats a key: after a
|
|
76
|
+
* structural change the cursor re-seeks to the key after the last one it yielded. The walk ends
|
|
77
|
+
* unless the loop keeps inserting keys ahead of the cursor, as with `Map`.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```ts
|
|
81
|
+
* const scores = new SortedMap([
|
|
82
|
+
* ['carol', 7],
|
|
83
|
+
* ['alice', 9]
|
|
84
|
+
* ])
|
|
85
|
+
* scores.set('bob', 8)
|
|
86
|
+
* ;[...scores.keys()] // ['alice', 'bob', 'carol']
|
|
87
|
+
* scores.at(-1) // ['carol', 7]
|
|
88
|
+
* scores.rank('bob') // 1
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export class SortedMap {
|
|
92
|
+
#root;
|
|
93
|
+
#size = 0;
|
|
94
|
+
#compareFn;
|
|
95
|
+
#token = {};
|
|
96
|
+
#version = 0;
|
|
97
|
+
#keyOnly = false;
|
|
98
|
+
static {
|
|
99
|
+
registerSortedMapCheck((map) => map.#checkInvariants());
|
|
100
|
+
createSetMap = (values, compareFn) => {
|
|
101
|
+
const keys = values == null
|
|
102
|
+
? undefined
|
|
103
|
+
: arrayFromIterableMap(values, (key) => positiveZero(key));
|
|
104
|
+
const map = new SortedMap(undefined, compareFn);
|
|
105
|
+
map.#keyOnly = true;
|
|
106
|
+
map.#root = emptyLeaf(map.#token, true);
|
|
107
|
+
if (keys !== undefined)
|
|
108
|
+
map.#bulkLoad(keys, undefined);
|
|
109
|
+
return map;
|
|
110
|
+
};
|
|
111
|
+
insertIfAbsent = (map, key, value) => map.#set(key, value, false);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Create a map holding `entries`. Sorted input loads in linear time; when keys repeat, the last
|
|
115
|
+
* entry wins, as for `Map`.
|
|
116
|
+
* @param entries Initial `[key, value]` pairs. `null` means none, as for `Map`.
|
|
117
|
+
* @param compareFn Key comparator. Defaults to the primitive order (numbers, bigints and strings).
|
|
118
|
+
* @throws {TypeError} when `entries` is not iterable or holds something that is not an entry,
|
|
119
|
+
* or the comparator cannot order a key.
|
|
120
|
+
*/
|
|
121
|
+
constructor(entries, compareFn) {
|
|
122
|
+
this.#compareFn = compareFn ?? defaultCompare;
|
|
123
|
+
if (typeof this.#compareFn !== 'function') {
|
|
124
|
+
throw new TypeError('comparator must be a function');
|
|
125
|
+
}
|
|
126
|
+
this.#root = emptyLeaf(this.#token, this.#keyOnly);
|
|
127
|
+
if (entries != null) {
|
|
128
|
+
const { keys, values } = readEntries(entries);
|
|
129
|
+
this.#bulkLoad(keys, values);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/** The number of entries. */
|
|
133
|
+
get size() {
|
|
134
|
+
return this.#size;
|
|
135
|
+
}
|
|
136
|
+
/** `'SortedMap'`, for `Object.prototype.toString`. */
|
|
137
|
+
get [Symbol.toStringTag]() {
|
|
138
|
+
return 'SortedMap';
|
|
139
|
+
}
|
|
140
|
+
/** Remove every entry. */
|
|
141
|
+
clear() {
|
|
142
|
+
this.#root = emptyLeaf(this.#token, this.#keyOnly);
|
|
143
|
+
this.#size = 0;
|
|
144
|
+
this.#version++;
|
|
145
|
+
}
|
|
146
|
+
/** The value for `key`, or `undefined` when it is absent. O(log n). */
|
|
147
|
+
get(key) {
|
|
148
|
+
const { leaf, pos, found } = this.#locate(key);
|
|
149
|
+
return found ? valueAt(leaf, pos) : undefined;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Insert or replace `key`. Replacing also stores the new key object. O(log n).
|
|
153
|
+
* @returns `this`
|
|
154
|
+
* @throws {TypeError} when the comparator cannot order `key`; the map is unchanged.
|
|
155
|
+
*/
|
|
156
|
+
set(key, value) {
|
|
157
|
+
this.#set(key, value, true);
|
|
158
|
+
return this;
|
|
159
|
+
}
|
|
160
|
+
#set(key, value, replace) {
|
|
161
|
+
key = positiveZero(key);
|
|
162
|
+
const { path, leaf, pos, found } = this.#locate(key);
|
|
163
|
+
if (!found)
|
|
164
|
+
this.#assertReflexive(key);
|
|
165
|
+
if (found && !replace)
|
|
166
|
+
return false;
|
|
167
|
+
const ownedLeaf = this.#ownPath(path, leaf);
|
|
168
|
+
if (found) {
|
|
169
|
+
ownedLeaf.keys[pos] = key;
|
|
170
|
+
if (ownedLeaf.values !== undefined)
|
|
171
|
+
ownedLeaf.values[pos] = value;
|
|
172
|
+
this.#refreshSeparators(path);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
ownedLeaf.keys.splice(pos, 0, key);
|
|
176
|
+
ownedLeaf.values?.splice(pos, 0, value);
|
|
177
|
+
this.#size++;
|
|
178
|
+
for (const frame of path)
|
|
179
|
+
frame.node.total++;
|
|
180
|
+
this.#refreshSeparators(path);
|
|
181
|
+
this.#splitOverfull(path, ownedLeaf);
|
|
182
|
+
}
|
|
183
|
+
this.#version++;
|
|
184
|
+
return !found;
|
|
185
|
+
}
|
|
186
|
+
/** Whether `key` is present. O(log n). */
|
|
187
|
+
has(key) {
|
|
188
|
+
return this.#locate(key).found;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Remove `key`. O(log n).
|
|
192
|
+
* @returns Whether it was present.
|
|
193
|
+
*/
|
|
194
|
+
delete(key) {
|
|
195
|
+
const { path, leaf, pos, found } = this.#locate(key);
|
|
196
|
+
if (!found)
|
|
197
|
+
return false;
|
|
198
|
+
const ownedLeaf = this.#ownPath(path, leaf);
|
|
199
|
+
ownedLeaf.keys.splice(pos, 1);
|
|
200
|
+
ownedLeaf.values?.splice(pos, 1);
|
|
201
|
+
this.#size--;
|
|
202
|
+
for (const frame of path)
|
|
203
|
+
frame.node.total--;
|
|
204
|
+
this.#refreshSeparators(path);
|
|
205
|
+
this.#mergeUnderfull(path, ownedLeaf);
|
|
206
|
+
this.#version++;
|
|
207
|
+
return true;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* The entry at `index` in key order, with `Array.prototype.at` rules (negative counts from the
|
|
211
|
+
* end), or `undefined` out of range. O(log n), with no comparator calls.
|
|
212
|
+
*/
|
|
213
|
+
at(index) {
|
|
214
|
+
let i = Math.trunc(index) || 0;
|
|
215
|
+
if (i < 0)
|
|
216
|
+
i += this.#size;
|
|
217
|
+
if (i < 0 || i >= this.#size)
|
|
218
|
+
return undefined;
|
|
219
|
+
let node = this.#root;
|
|
220
|
+
while (node.kind === 'branch') {
|
|
221
|
+
let c = 0;
|
|
222
|
+
while (i >= countOf(node.children[c])) {
|
|
223
|
+
i -= countOf(node.children[c]);
|
|
224
|
+
c++;
|
|
225
|
+
}
|
|
226
|
+
node = node.children[c];
|
|
227
|
+
}
|
|
228
|
+
return [node.keys[i], valueAt(node, i)];
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* `[key, value]` pairs in ascending key order.
|
|
232
|
+
* @param lowestKey Start at the first key not below it. Omitted (or `undefined`) means the first
|
|
233
|
+
* key.
|
|
234
|
+
*/
|
|
235
|
+
entries(lowestKey) {
|
|
236
|
+
const start = lowestKey === undefined ? undefined : { key: lowestKey, exclusive: false };
|
|
237
|
+
return this.#walk(ASCENDING, start);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* `[key, value]` pairs in descending key order.
|
|
241
|
+
* @param highestKey Start at the last key not above it. Omitted (or `undefined`) means the last
|
|
242
|
+
* key.
|
|
243
|
+
*/
|
|
244
|
+
entriesDescending(highestKey) {
|
|
245
|
+
const start = highestKey === undefined ? undefined : { key: highestKey, exclusive: false };
|
|
246
|
+
return this.#walk(DESCENDING, start);
|
|
247
|
+
}
|
|
248
|
+
/** The keys, in order. */
|
|
249
|
+
keys() {
|
|
250
|
+
return this.entries().map(([k]) => k);
|
|
251
|
+
}
|
|
252
|
+
/** The values, in key order. */
|
|
253
|
+
values() {
|
|
254
|
+
return this.entries().map(([, v]) => v);
|
|
255
|
+
}
|
|
256
|
+
/** Call `callback(value, key, map)` for each entry, in key order. */
|
|
257
|
+
forEach(callback, thisArg) {
|
|
258
|
+
for (const [k, v] of this.entries())
|
|
259
|
+
Reflect.apply(callback, thisArg, [v, k, this]);
|
|
260
|
+
}
|
|
261
|
+
/** `[key, value]` pairs, in key order. */
|
|
262
|
+
[Symbol.iterator]() {
|
|
263
|
+
return this.entries();
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* How many keys sort before `key`: its index when present, else where it would go. O(log n).
|
|
267
|
+
* @throws {TypeError} when the comparator cannot order `key`.
|
|
268
|
+
*/
|
|
269
|
+
rank(key) {
|
|
270
|
+
let before = 0;
|
|
271
|
+
let node = this.#root;
|
|
272
|
+
while (node.kind === 'branch') {
|
|
273
|
+
const index = this.#lowerBound(node.maxKeys, key);
|
|
274
|
+
if (index >= node.children.length)
|
|
275
|
+
return before + node.total;
|
|
276
|
+
for (let i = 0; i < index; i++)
|
|
277
|
+
before += countOf(node.children[i]);
|
|
278
|
+
node = node.children[index];
|
|
279
|
+
}
|
|
280
|
+
return before + this.#lowerBound(node.keys, key);
|
|
281
|
+
}
|
|
282
|
+
/** The smallest key, or `undefined` when empty. */
|
|
283
|
+
firstKey() {
|
|
284
|
+
let n = this.#root;
|
|
285
|
+
while (n.kind === 'branch')
|
|
286
|
+
n = n.children[0];
|
|
287
|
+
return n.keys[0];
|
|
288
|
+
}
|
|
289
|
+
/** The largest key, or `undefined` when empty. */
|
|
290
|
+
lastKey() {
|
|
291
|
+
let n = this.#root;
|
|
292
|
+
while (n.kind === 'branch')
|
|
293
|
+
n = n.children[n.children.length - 1];
|
|
294
|
+
return n.keys.at(-1);
|
|
295
|
+
}
|
|
296
|
+
/** The entry with the least key above `key`, or `undefined`. */
|
|
297
|
+
higher(key) {
|
|
298
|
+
return this.#entryAt(this.#seek(ASCENDING, { key, exclusive: true }));
|
|
299
|
+
}
|
|
300
|
+
/** The entry with the greatest key below `key`, or `undefined`. */
|
|
301
|
+
lower(key) {
|
|
302
|
+
return this.#entryAt(this.#seek(DESCENDING, { key, exclusive: true }));
|
|
303
|
+
}
|
|
304
|
+
/** The entry with the least key at or above `key`, or `undefined`. */
|
|
305
|
+
ceiling(key) {
|
|
306
|
+
return this.#entryAt(this.#seek(ASCENDING, { key, exclusive: false }));
|
|
307
|
+
}
|
|
308
|
+
/** The entry with the greatest key at or below `key`, or `undefined`. */
|
|
309
|
+
floor(key) {
|
|
310
|
+
return this.#entryAt(this.#seek(DESCENDING, { key, exclusive: false }));
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The entries whose keys lie in `[from, to)`, or `[from, to]` with `inclusiveEnd`, in key order.
|
|
314
|
+
* @param options `inclusiveEnd` includes `to` (default false); `limit` caps how many entries are
|
|
315
|
+
* collected (a non-negative integer or `Infinity`, the default).
|
|
316
|
+
* @throws {TypeError} when `limit` is not a number.
|
|
317
|
+
* @throws {RangeError} when `limit` is negative or not an integer.
|
|
318
|
+
* @example
|
|
319
|
+
* ```ts
|
|
320
|
+
* new SortedMap([[1, 'a'], [2, 'b'], [3, 'c']]).entriesBetween(1, 3) // [[1, 'a'], [2, 'b']]
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
entriesBetween(from, to, options) {
|
|
324
|
+
assertOptions(options);
|
|
325
|
+
const limit = resolveLimit(options?.limit);
|
|
326
|
+
if (limit === 0)
|
|
327
|
+
return [];
|
|
328
|
+
const out = [];
|
|
329
|
+
for (const entry of this.#range(from, to, options?.inclusiveEnd === true)) {
|
|
330
|
+
out.push(entry);
|
|
331
|
+
if (out.length >= limit)
|
|
332
|
+
break;
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Visit the entries whose keys lie in `[from, to)`, or `[from, to]` with `inclusiveEnd`, in key
|
|
338
|
+
* order, and change them in one atomic step: if `update` or the comparator throws, the map is
|
|
339
|
+
* left as it was. `update(value, key, map)` returns `{ replace: value }`, `{ remove: true }`,
|
|
340
|
+
* either with `stop: true` to end after this entry, `{ stop: true }`, or `null`/`undefined` to
|
|
341
|
+
* leave the entry alone.
|
|
342
|
+
* @returns The number of entries visited.
|
|
343
|
+
* @throws {TypeError} when `update` returns a primitive, an object with none of `replace`,
|
|
344
|
+
* `remove` and `stop`, or both `replace` and a truthy `remove`.
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* const stock = new SortedMap([['apple', 3], ['pear', 0]])
|
|
348
|
+
* stock.updateRange('a', 'z', (n) => (n === 0 ? { remove: true } : { replace: n - 1 }))
|
|
349
|
+
* ;[...stock] // [['apple', 2]]
|
|
350
|
+
* ```
|
|
351
|
+
*/
|
|
352
|
+
updateRange(from, to, update, options) {
|
|
353
|
+
assertOptions(options);
|
|
354
|
+
const range = this.#range(from, to, options?.inclusiveEnd === true);
|
|
355
|
+
let next = range.next();
|
|
356
|
+
// An empty range writes nothing, so it keeps the copy-on-write token and later writes in place.
|
|
357
|
+
if (next.done === true)
|
|
358
|
+
return 0;
|
|
359
|
+
return this.#atomic(() => {
|
|
360
|
+
let visited = 0;
|
|
361
|
+
for (; next.done !== true; next = range.next()) {
|
|
362
|
+
const [k, v] = next.value;
|
|
363
|
+
visited++;
|
|
364
|
+
const r = update(v, k, this);
|
|
365
|
+
if (r == null)
|
|
366
|
+
continue;
|
|
367
|
+
if (!isNonPrimitive(r)) {
|
|
368
|
+
throw new TypeError('update must return an object, null or undefined');
|
|
369
|
+
}
|
|
370
|
+
if (!('replace' in r || 'remove' in r || 'stop' in r)) {
|
|
371
|
+
throw new TypeError('update must return { replace }, { remove } or { stop }');
|
|
372
|
+
}
|
|
373
|
+
const remove = 'remove' in r && r.remove;
|
|
374
|
+
if (remove && 'replace' in r) {
|
|
375
|
+
throw new TypeError('an update cannot both replace and remove');
|
|
376
|
+
}
|
|
377
|
+
if (remove)
|
|
378
|
+
this.delete(k);
|
|
379
|
+
else if ('replace' in r)
|
|
380
|
+
this.set(k, r.replace);
|
|
381
|
+
if ('stop' in r && r.stop)
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
return visited;
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Remove the entries whose keys lie in `[from, to)`, or `[from, to]` with `inclusiveEnd`, in one
|
|
389
|
+
* atomic step: a comparator that throws leaves the map unchanged.
|
|
390
|
+
* @returns The number of entries removed.
|
|
391
|
+
* @example
|
|
392
|
+
* ```ts
|
|
393
|
+
* const log = new SortedMap([[1, 'a'], [5, 'b'], [9, 'c']])
|
|
394
|
+
* log.removeRange(0, 5, { inclusiveEnd: true }) // 2
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
397
|
+
removeRange(from, to, options) {
|
|
398
|
+
return this.updateRange(from, to, () => ({ remove: true }), options);
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* A copy in O(1): the two maps share nodes until either one writes, which copies only the path
|
|
402
|
+
* it changes. The copy keeps the class and comparator.
|
|
403
|
+
* @throws {TypeError} when a subclass constructor does not pass `(entries, compareFn)`
|
|
404
|
+
* on to `SortedMap`.
|
|
405
|
+
*/
|
|
406
|
+
clone() {
|
|
407
|
+
const copy = Reflect.construct(this.constructor, [undefined, this.#compareFn]);
|
|
408
|
+
if (copy.#compareFn !== this.#compareFn) {
|
|
409
|
+
throw new TypeError('clone needs the constructor to pass (entries, compareFn) on to SortedMap');
|
|
410
|
+
}
|
|
411
|
+
copy.#root = this.#root;
|
|
412
|
+
copy.#size = this.#size;
|
|
413
|
+
copy.#keyOnly = this.#keyOnly;
|
|
414
|
+
this.#token = {};
|
|
415
|
+
return copy;
|
|
416
|
+
}
|
|
417
|
+
#checkInvariants() {
|
|
418
|
+
const fail = (name) => {
|
|
419
|
+
throw new Error(`SortedMap invariant violated: ${name}`);
|
|
420
|
+
};
|
|
421
|
+
const depths = new Set();
|
|
422
|
+
const walk = (node, depth, isRoot) => {
|
|
423
|
+
const count = itemCount(node);
|
|
424
|
+
if (count > NODE_CAPACITY || (!isRoot && count < MIN_FILL))
|
|
425
|
+
fail('fill');
|
|
426
|
+
if (node.kind === 'leaf') {
|
|
427
|
+
if (this.#keyOnly ? node.values !== undefined : node.keys.length !== node.values?.length)
|
|
428
|
+
fail('leaf-arrays');
|
|
429
|
+
depths.add(depth);
|
|
430
|
+
for (let i = 1; i < node.keys.length; i++) {
|
|
431
|
+
if (!(this.#cmp(node.keys[i - 1], node.keys[i]) < 0))
|
|
432
|
+
fail('order');
|
|
433
|
+
}
|
|
434
|
+
return node.keys.length;
|
|
435
|
+
}
|
|
436
|
+
if (isRoot && count < 2)
|
|
437
|
+
fail('fill');
|
|
438
|
+
if (node.maxKeys.length !== node.children.length)
|
|
439
|
+
fail('separators');
|
|
440
|
+
let sum = 0;
|
|
441
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
442
|
+
const child = node.children[i];
|
|
443
|
+
sum += walk(child, depth + 1, false);
|
|
444
|
+
if (!Object.is(node.maxKeys[i], lastKeyOf(child)))
|
|
445
|
+
fail('separators');
|
|
446
|
+
}
|
|
447
|
+
if (node.total !== sum)
|
|
448
|
+
fail('totals');
|
|
449
|
+
return sum;
|
|
450
|
+
};
|
|
451
|
+
const rootCount = walk(this.#root, 0, true);
|
|
452
|
+
if (rootCount !== this.#size)
|
|
453
|
+
fail('totals');
|
|
454
|
+
if (depths.size > 1)
|
|
455
|
+
fail('depth');
|
|
456
|
+
let height = 0;
|
|
457
|
+
for (let n = this.#root; n.kind === 'branch'; n = n.children[0])
|
|
458
|
+
height++;
|
|
459
|
+
if (height !== (depths.values().next().value ?? 0))
|
|
460
|
+
fail('depth');
|
|
461
|
+
if ((this.#size === 0) !== (this.#root.kind === 'leaf' && this.#root.keys.length === 0)) {
|
|
462
|
+
fail('empty');
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
#cmp(a, b) {
|
|
466
|
+
const r = this.#compareFn(a, b);
|
|
467
|
+
if (typeof r !== 'number' || !Number.isFinite(r)) {
|
|
468
|
+
throw new TypeError('comparator must return a finite number');
|
|
469
|
+
}
|
|
470
|
+
return r;
|
|
471
|
+
}
|
|
472
|
+
#assertReflexive(key) {
|
|
473
|
+
if (this.#cmp(key, key) !== 0) {
|
|
474
|
+
throw new TypeError('comparator must return 0 for a key compared with itself');
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
#lowerBound(keys, key) {
|
|
478
|
+
let lo = 0;
|
|
479
|
+
let hi = keys.length;
|
|
480
|
+
while (lo < hi) {
|
|
481
|
+
const mid = (lo + hi) >>> 1;
|
|
482
|
+
if (this.#cmp(keys[mid], key) < 0)
|
|
483
|
+
lo = mid + 1;
|
|
484
|
+
else
|
|
485
|
+
hi = mid;
|
|
486
|
+
}
|
|
487
|
+
return lo;
|
|
488
|
+
}
|
|
489
|
+
#upperBound(keys, key) {
|
|
490
|
+
let lo = 0;
|
|
491
|
+
let hi = keys.length;
|
|
492
|
+
while (lo < hi) {
|
|
493
|
+
const mid = (lo + hi) >>> 1;
|
|
494
|
+
if (this.#cmp(keys[mid], key) <= 0)
|
|
495
|
+
lo = mid + 1;
|
|
496
|
+
else
|
|
497
|
+
hi = mid;
|
|
498
|
+
}
|
|
499
|
+
return lo;
|
|
500
|
+
}
|
|
501
|
+
/** The leaf where `key` is or would go, the position in it, and the branch frames above it. */
|
|
502
|
+
#locate(key) {
|
|
503
|
+
const path = [];
|
|
504
|
+
let node = this.#root;
|
|
505
|
+
while (node.kind === 'branch') {
|
|
506
|
+
// A key above every separator goes at the end of the last child.
|
|
507
|
+
const index = Math.min(this.#lowerBound(node.maxKeys, key), node.children.length - 1);
|
|
508
|
+
path.push({ node, index });
|
|
509
|
+
node = node.children[index];
|
|
510
|
+
}
|
|
511
|
+
const pos = this.#lowerBound(node.keys, key);
|
|
512
|
+
const found = pos < node.keys.length && this.#cmp(node.keys[pos], key) === 0;
|
|
513
|
+
return { path, leaf: node, pos, found };
|
|
514
|
+
}
|
|
515
|
+
/** `node` itself when this map owns it, else a copy this map owns. */
|
|
516
|
+
#own(node) {
|
|
517
|
+
if (node.owner === this.#token)
|
|
518
|
+
return node;
|
|
519
|
+
const copy = node.kind === 'leaf'
|
|
520
|
+
? {
|
|
521
|
+
kind: 'leaf',
|
|
522
|
+
owner: this.#token,
|
|
523
|
+
keys: node.keys.slice(),
|
|
524
|
+
values: node.values?.slice()
|
|
525
|
+
}
|
|
526
|
+
: {
|
|
527
|
+
kind: 'branch',
|
|
528
|
+
owner: this.#token,
|
|
529
|
+
children: node.children.slice(),
|
|
530
|
+
maxKeys: node.maxKeys.slice(),
|
|
531
|
+
total: node.total
|
|
532
|
+
};
|
|
533
|
+
return copy;
|
|
534
|
+
}
|
|
535
|
+
/** Put `node` where `parent` points, or at the root when there is no parent. */
|
|
536
|
+
#link(parent, node) {
|
|
537
|
+
if (parent === undefined)
|
|
538
|
+
this.#root = node;
|
|
539
|
+
else
|
|
540
|
+
parent.node.children[parent.index] = node;
|
|
541
|
+
return node;
|
|
542
|
+
}
|
|
543
|
+
/** Own every node from the root down to `leaf`, pointing `path` at the owned branches. */
|
|
544
|
+
#ownPath(path, leaf) {
|
|
545
|
+
let parent;
|
|
546
|
+
for (const frame of path) {
|
|
547
|
+
frame.node = this.#link(parent, this.#own(frame.node));
|
|
548
|
+
parent = frame;
|
|
549
|
+
}
|
|
550
|
+
return this.#link(parent, this.#own(leaf));
|
|
551
|
+
}
|
|
552
|
+
/** Set each separator on `path` to the last key of the child it leads to. */
|
|
553
|
+
#refreshSeparators(path) {
|
|
554
|
+
for (let depth = path.length - 1; depth >= 0; depth--) {
|
|
555
|
+
const { node, index } = path[depth];
|
|
556
|
+
node.maxKeys[index] = lastKeyOf(node.children[index]);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/** Move the upper half of an over-full `node` into a new right sibling, and return it. */
|
|
560
|
+
#splitOff(node) {
|
|
561
|
+
const mid = Math.ceil(itemCount(node) / 2);
|
|
562
|
+
if (node.kind === 'leaf') {
|
|
563
|
+
return {
|
|
564
|
+
kind: 'leaf',
|
|
565
|
+
owner: this.#token,
|
|
566
|
+
keys: node.keys.splice(mid),
|
|
567
|
+
values: node.values?.splice(mid)
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const children = node.children.splice(mid);
|
|
571
|
+
let total = 0;
|
|
572
|
+
for (const child of children)
|
|
573
|
+
total += countOf(child);
|
|
574
|
+
node.total -= total;
|
|
575
|
+
return {
|
|
576
|
+
kind: 'branch',
|
|
577
|
+
owner: this.#token,
|
|
578
|
+
children,
|
|
579
|
+
maxKeys: node.maxKeys.splice(mid),
|
|
580
|
+
total
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
/** Split `leaf` and then each ancestor on `path` while it holds more than the capacity. */
|
|
584
|
+
#splitOverfull(path, leaf) {
|
|
585
|
+
let node = leaf;
|
|
586
|
+
for (let depth = path.length - 1; itemCount(node) > NODE_CAPACITY; depth--) {
|
|
587
|
+
const right = this.#splitOff(node);
|
|
588
|
+
if (depth < 0) {
|
|
589
|
+
this.#root = {
|
|
590
|
+
kind: 'branch',
|
|
591
|
+
owner: this.#token,
|
|
592
|
+
children: [node, right],
|
|
593
|
+
maxKeys: [lastKeyOf(node), lastKeyOf(right)],
|
|
594
|
+
total: countOf(node) + countOf(right)
|
|
595
|
+
};
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const { node: parent, index } = path[depth];
|
|
599
|
+
parent.children.splice(index + 1, 0, right);
|
|
600
|
+
parent.maxKeys[index] = lastKeyOf(node);
|
|
601
|
+
parent.maxKeys.splice(index + 1, 0, lastKeyOf(right));
|
|
602
|
+
node = parent;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Refill `leaf` and then each ancestor on `path` while it holds less than the minimum fill, by
|
|
607
|
+
* borrowing one item from a sibling that can spare it or else merging with that sibling.
|
|
608
|
+
*/
|
|
609
|
+
#mergeUnderfull(path, leaf) {
|
|
610
|
+
let node = leaf;
|
|
611
|
+
for (let depth = path.length - 1; depth >= 0 && itemCount(node) < MIN_FILL; depth--) {
|
|
612
|
+
const { node: parent, index: i } = path[depth];
|
|
613
|
+
const j = i > 0 ? i - 1 : i + 1;
|
|
614
|
+
const sibling = this.#own(parent.children[j]);
|
|
615
|
+
parent.children[j] = sibling;
|
|
616
|
+
const leftIndex = Math.min(i, j);
|
|
617
|
+
if (itemCount(sibling) > MIN_FILL) {
|
|
618
|
+
this.#borrow(node, sibling, j < i ? DESCENDING : ASCENDING);
|
|
619
|
+
parent.maxKeys[leftIndex] = lastKeyOf(parent.children[leftIndex]);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
this.#merge(parent.children[leftIndex], parent.children[leftIndex + 1]);
|
|
623
|
+
parent.children.splice(leftIndex + 1, 1);
|
|
624
|
+
parent.maxKeys.splice(leftIndex, 1);
|
|
625
|
+
node = parent;
|
|
626
|
+
}
|
|
627
|
+
if (this.#root.kind === 'branch' && this.#root.children.length === 1) {
|
|
628
|
+
this.#root = this.#root.children[0];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Move one item into `node` from `sibling`, a node at the same depth: the sibling's last item
|
|
633
|
+
* when it sits before `node` (`DESCENDING`), else its first.
|
|
634
|
+
*/
|
|
635
|
+
#borrow(node, sibling, from) {
|
|
636
|
+
const at = from === DESCENDING ? -1 : 0;
|
|
637
|
+
if (node.kind === 'leaf') {
|
|
638
|
+
const donor = sibling;
|
|
639
|
+
const keys = donor.keys.splice(at, 1);
|
|
640
|
+
const values = donor.values?.splice(at, 1);
|
|
641
|
+
if (from === DESCENDING) {
|
|
642
|
+
node.keys.unshift(...keys);
|
|
643
|
+
if (values !== undefined)
|
|
644
|
+
node.values?.unshift(...values);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
node.keys.push(...keys);
|
|
648
|
+
if (values !== undefined)
|
|
649
|
+
node.values?.push(...values);
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const donor = sibling;
|
|
654
|
+
const children = donor.children.splice(at, 1);
|
|
655
|
+
const maxKeys = donor.maxKeys.splice(at, 1);
|
|
656
|
+
const moved = countOf(children[0]);
|
|
657
|
+
donor.total -= moved;
|
|
658
|
+
node.total += moved;
|
|
659
|
+
if (from === DESCENDING) {
|
|
660
|
+
node.children.unshift(...children);
|
|
661
|
+
node.maxKeys.unshift(...maxKeys);
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
node.children.push(...children);
|
|
665
|
+
node.maxKeys.push(...maxKeys);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
/** Append the items of `right` to `left`, its sibling at the same depth. */
|
|
669
|
+
#merge(left, right) {
|
|
670
|
+
if (left.kind === 'leaf') {
|
|
671
|
+
const from = right;
|
|
672
|
+
left.keys.push(...from.keys);
|
|
673
|
+
if (from.values !== undefined)
|
|
674
|
+
left.values?.push(...from.values);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
const from = right;
|
|
678
|
+
left.children.push(...from.children);
|
|
679
|
+
left.maxKeys.push(...from.maxKeys);
|
|
680
|
+
left.total += from.total;
|
|
681
|
+
}
|
|
682
|
+
#entryAt(cursor) {
|
|
683
|
+
if (cursor === undefined)
|
|
684
|
+
return undefined;
|
|
685
|
+
return [cursor.leaf.keys[cursor.index], valueAt(cursor.leaf, cursor.index)];
|
|
686
|
+
}
|
|
687
|
+
/** Descend from `node` to its first key (`ASCENDING`) or its last, pushing frames on `branches`. */
|
|
688
|
+
#edge(node, branches, direction) {
|
|
689
|
+
while (node.kind === 'branch') {
|
|
690
|
+
const index = direction === ASCENDING ? 0 : node.children.length - 1;
|
|
691
|
+
branches.push({ node, index });
|
|
692
|
+
node = node.children[index];
|
|
693
|
+
}
|
|
694
|
+
return { branches, leaf: node, index: direction === ASCENDING ? 0 : node.keys.length - 1 };
|
|
695
|
+
}
|
|
696
|
+
/** The key position after `cursor` in `direction`, or `undefined` past the end. */
|
|
697
|
+
#step(cursor, direction) {
|
|
698
|
+
const index = cursor.index + direction;
|
|
699
|
+
if (index >= 0 && index < cursor.leaf.keys.length)
|
|
700
|
+
return { ...cursor, index };
|
|
701
|
+
const branches = cursor.branches.slice();
|
|
702
|
+
while (branches.length > 0) {
|
|
703
|
+
const top = branches[branches.length - 1];
|
|
704
|
+
const next = top.index + direction;
|
|
705
|
+
if (next >= 0 && next < top.node.children.length) {
|
|
706
|
+
branches[branches.length - 1] = { node: top.node, index: next };
|
|
707
|
+
return this.#edge(top.node.children[next], branches, direction);
|
|
708
|
+
}
|
|
709
|
+
branches.pop();
|
|
710
|
+
}
|
|
711
|
+
return undefined;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* The first key position in `direction` from `start`: ascending, the least key at or above
|
|
715
|
+
* `start.key`; descending, the greatest key at or below it; above or below only when `exclusive`.
|
|
716
|
+
* With no `start`, the first or last key.
|
|
717
|
+
*/
|
|
718
|
+
#seek(direction, start) {
|
|
719
|
+
if (this.#size === 0)
|
|
720
|
+
return undefined;
|
|
721
|
+
const branches = [];
|
|
722
|
+
if (start === undefined)
|
|
723
|
+
return this.#edge(this.#root, branches, direction);
|
|
724
|
+
const { key, exclusive } = start;
|
|
725
|
+
// Ascending-exclusive and descending-inclusive split the keys after those equal to `key`.
|
|
726
|
+
const afterEqual = (direction === ASCENDING) === exclusive;
|
|
727
|
+
const split = (keys) => afterEqual ? this.#upperBound(keys, key) : this.#lowerBound(keys, key);
|
|
728
|
+
let node = this.#root;
|
|
729
|
+
while (node.kind === 'branch') {
|
|
730
|
+
const index = Math.min(split(node.maxKeys), node.children.length - 1);
|
|
731
|
+
branches.push({ node, index });
|
|
732
|
+
node = node.children[index];
|
|
733
|
+
}
|
|
734
|
+
const at = split(node.keys);
|
|
735
|
+
const index = direction === ASCENDING ? at : at - 1;
|
|
736
|
+
if (index >= 0 && index < node.keys.length)
|
|
737
|
+
return { branches, leaf: node, index };
|
|
738
|
+
// The answer, if any, is the nearest key of the next leaf in `direction`.
|
|
739
|
+
const edge = direction === ASCENDING ? node.keys.length - 1 : 0;
|
|
740
|
+
return this.#step({ branches, leaf: node, index: edge }, direction);
|
|
741
|
+
}
|
|
742
|
+
*#walk(direction, start) {
|
|
743
|
+
let cursor = this.#seek(direction, start);
|
|
744
|
+
while (cursor !== undefined) {
|
|
745
|
+
const k = cursor.leaf.keys[cursor.index];
|
|
746
|
+
const v = valueAt(cursor.leaf, cursor.index);
|
|
747
|
+
const seenVersion = this.#version;
|
|
748
|
+
yield [k, v];
|
|
749
|
+
cursor =
|
|
750
|
+
this.#version === seenVersion
|
|
751
|
+
? this.#step(cursor, direction)
|
|
752
|
+
: this.#seek(direction, { key: k, exclusive: true });
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
/** The entries from `from` up to `to`, in key order; it re-seeks after the map changes. */
|
|
756
|
+
*#range(from, to, inclusiveEnd) {
|
|
757
|
+
if (this.#cmp(from, to) > 0)
|
|
758
|
+
return;
|
|
759
|
+
for (const entry of this.#walk(ASCENDING, { key: from, exclusive: false })) {
|
|
760
|
+
const c = this.#cmp(entry[0], to);
|
|
761
|
+
if (inclusiveEnd ? c > 0 : c >= 0)
|
|
762
|
+
return;
|
|
763
|
+
yield entry;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
#atomic(fn) {
|
|
767
|
+
const snapshotRoot = this.#root;
|
|
768
|
+
const snapshotSize = this.#size;
|
|
769
|
+
this.#token = {};
|
|
770
|
+
try {
|
|
771
|
+
return fn();
|
|
772
|
+
}
|
|
773
|
+
catch (err) {
|
|
774
|
+
this.#root = snapshotRoot;
|
|
775
|
+
this.#size = snapshotSize;
|
|
776
|
+
this.#version++;
|
|
777
|
+
throw err;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
#bulkLoad(keys, values) {
|
|
781
|
+
for (const key of keys)
|
|
782
|
+
this.#assertReflexive(key);
|
|
783
|
+
const alreadySorted = keys.every((key, i) => i === 0 || this.#cmp(keys[i - 1], key) < 0);
|
|
784
|
+
const sorted = alreadySorted ? { keys, values } : this.#sortedColumns(keys, values);
|
|
785
|
+
const n = sorted.keys.length;
|
|
786
|
+
const leafCount = Math.max(1, Math.ceil(n / NODE_CAPACITY));
|
|
787
|
+
const leaves = [];
|
|
788
|
+
const base = Math.floor(n / leafCount);
|
|
789
|
+
const extra = n % leafCount;
|
|
790
|
+
let offset = 0;
|
|
791
|
+
for (let i = 0; i < leafCount; i++) {
|
|
792
|
+
const end = offset + base + (i < extra ? 1 : 0);
|
|
793
|
+
leaves.push({
|
|
794
|
+
kind: 'leaf',
|
|
795
|
+
owner: this.#token,
|
|
796
|
+
keys: sorted.keys.slice(offset, end),
|
|
797
|
+
values: sorted.values?.slice(offset, end)
|
|
798
|
+
});
|
|
799
|
+
offset = end;
|
|
800
|
+
}
|
|
801
|
+
let level = leaves;
|
|
802
|
+
while (level.length > 1) {
|
|
803
|
+
const count = Math.ceil(level.length / NODE_CAPACITY);
|
|
804
|
+
const next = [];
|
|
805
|
+
const bbase = Math.floor(level.length / count);
|
|
806
|
+
const bextra = level.length % count;
|
|
807
|
+
let off = 0;
|
|
808
|
+
for (let i = 0; i < count; i++) {
|
|
809
|
+
const size = bbase + (i < bextra ? 1 : 0);
|
|
810
|
+
const children = level.slice(off, off + size);
|
|
811
|
+
off += size;
|
|
812
|
+
let total = 0;
|
|
813
|
+
for (const c of children)
|
|
814
|
+
total += countOf(c);
|
|
815
|
+
next.push({
|
|
816
|
+
kind: 'branch',
|
|
817
|
+
owner: this.#token,
|
|
818
|
+
children,
|
|
819
|
+
maxKeys: children.map(lastKeyOf),
|
|
820
|
+
total
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
level = next;
|
|
824
|
+
}
|
|
825
|
+
this.#root = level[0];
|
|
826
|
+
this.#size = n;
|
|
827
|
+
}
|
|
828
|
+
#sortedColumns(keys, values) {
|
|
829
|
+
const order = keys.map((_, i) => i).sort((a, b) => this.#cmp(keys[a], keys[b]));
|
|
830
|
+
const sorted = {
|
|
831
|
+
keys: [],
|
|
832
|
+
values: values === undefined ? undefined : []
|
|
833
|
+
};
|
|
834
|
+
for (const i of order) {
|
|
835
|
+
const last = sorted.keys.length - 1;
|
|
836
|
+
const slot = last >= 0 && this.#cmp(sorted.keys[last], keys[i]) === 0 ? last : last + 1;
|
|
837
|
+
if (slot === last && values === undefined)
|
|
838
|
+
continue;
|
|
839
|
+
sorted.keys[slot] = keys[i];
|
|
840
|
+
if (sorted.values !== undefined && values !== undefined)
|
|
841
|
+
sorted.values[slot] = values[i];
|
|
842
|
+
}
|
|
843
|
+
return sorted;
|
|
844
|
+
}
|
|
845
|
+
}
|