@zakkster/lite-logn 0.5.0 → 0.7.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/LogN.js CHANGED
@@ -39,7 +39,7 @@
39
39
  */
40
40
 
41
41
  /** Package version. One of the three version sites (package.json / VERSION / llms.txt). */
42
- export const VERSION = '0.5.0';
42
+ export const VERSION = '0.7.0';
43
43
 
44
44
  // --- members land here, append-only, one tree-shakeable class each -----------
45
45
  // BinaryHeap (v0.1.0 session) -- indexed O(log n) min|max heap (BELOW)
@@ -1983,3 +1983,892 @@ export class Treap {
1983
1983
  throw new RangeError('[lite-logn] Treap full (capacity ' + this._cap + ')');
1984
1984
  }
1985
1985
  }
1986
+
1987
+ // Scapegoat (v0.6.0 session) -- a DETERMINISTIC weight-balanced augmented ordered map (BELOW).
1988
+
1989
+ /**
1990
+ * Max Scapegoat capacity: `0x7FFFFFFF` (2^31 - 1). Every node is addressed by a slot
1991
+ * INDEX stored in `Uint32Array` link columns (`_left` / `_right`), each subtree count
1992
+ * lives in a `Uint32Array` (`_size`), and the rebuild scratch (`_flat`) plus the
1993
+ * flatten index-stack (`_stack`) are `Uint32Array` slot buffers; an index and a count
1994
+ * must both fit an unsigned 32-bit word. `NIL = 0` reserves slot 0 as the empty-subtree
1995
+ * sentinel, so live slots run [1, capacity]. The index / count arithmetic (Uint32 slot
1996
+ * indices + `NIL = 0` + Uint32 subtree sizes), not the byte count, is the hard ceiling
1997
+ * -- the same "the arithmetic caps it" reasoning as the array-embedded members.
1998
+ */
1999
+ const SG_MAX_CAPACITY = 0x7FFFFFFF; // 2^31 - 1
2000
+
2001
+ /**
2002
+ * A SCAPEGOAT TREE: a DETERMINISTIC, weight-balanced BINARY SEARCH TREE that is ALSO an
2003
+ * order-statistic tree (an AUGMENTED ordered map key -> value). It is the honest PAIR to
2004
+ * Treap: where a treap randomizes its shape to be balanced IN EXPECTATION, a scapegoat
2005
+ * keeps a hard WORST-CASE height bound (`get` is O(log n) worst-case, never merely
2006
+ * expected) by paying for it with AMORTIZED O(log n) `set` / `delete` -- an occasional
2007
+ * subtree rebuild absorbs the imbalance. No priorities, no RNG anywhere: the tree shape
2008
+ * is a deterministic function of the insert / delete order. The trick that keeps it
2009
+ * zero-GC is the SkipList / Treap one: nodes are slot INDICES in flat typed-array columns
2010
+ * over the same private free-list (NodePool), never heap objects; AND the rebuild reuses
2011
+ * ONE preallocated scratch buffer (`_flat`) + ONE preallocated index-stack (`_stack`),
2012
+ * so even a rebuild-heavy trace allocates ZERO bytes after construction.
2013
+ *
2014
+ * Two invariants held at once:
2015
+ * - BST order on `_key` (an in-order walk is ascending by key); and
2016
+ * - alpha-WEIGHT-BALANCE: after every mutation the height stays <= log_{1/alpha}(n) + 1
2017
+ * (h_alpha), enforced by the dual trigger below. The augmentation is a third
2018
+ * invariant: `_size[x]` is the number of nodes in x's subtree, maintained in the SAME
2019
+ * pass as every link rewrite, so `rank` (keys < x) and `select` (k-th smallest key)
2020
+ * are O(log n) via subtree counts.
2021
+ *
2022
+ * The DUAL trigger (alpha frozen at construction, `alpha` in the OPEN interval
2023
+ * (0.55, 0.75); default 2/3):
2024
+ * - `set`: descend recording the path, link the new leaf at depth d, then if the node
2025
+ * is "too deep" (d > h_alpha(size)) walk the recorded path back up to the SCAPEGOAT
2026
+ * -- the lowest ancestor whose child subtree exceeds `alpha` of its own size -- and
2027
+ * rebuild THAT subtree perfectly balanced. `_maxCount` tracks the high-water size.
2028
+ * - `delete`: remove the node (standard BST delete, `_size` fixed on the unwind), then
2029
+ * when `size < alpha * _maxCount` rebuild the WHOLE tree and reset `_maxCount = size`.
2030
+ * The depth test uses NO per-op `Math.log`: since `_invAlpha = 1/alpha` is ctor-cached,
2031
+ * `d > h_alpha(n)` (= `d > floor(_invLog * log2(n))`, `_invLog = 1/log2(1/alpha)`) is
2032
+ * tested EXACTLY as `_invAlpha^d > n` (for integer d the strict `>` matches the floor),
2033
+ * accumulated with one float multiply per path level -- only on the `set` path.
2034
+ *
2035
+ * ZERO-GC rebuild (the load-bearing design call, decisions/0008-scapegoat.md): NO fresh
2036
+ * array per rebuild. `_flatten` walks the target subtree in-order ITERATIVELY (Morris-
2037
+ * free) using the preallocated `_stack` index-column, writing sorted slot indices into
2038
+ * the preallocated `_flat` buffer; `_buildBalanced` reads that sorted range and re-links
2039
+ * `_left` / `_right` / `_size` via bounded native recursion whose depth is O(log
2040
+ * subtree) <= ~31 (it produces a perfectly balanced subtree), so it runs on the native
2041
+ * call stack, never the GC heap. Both are 0 B/op -- proven by the torture gate's rebuild-
2042
+ * heavy ascending-insert lane. RECURSION: `delete` and `forEach` also recurse to a depth
2043
+ * equal to the tree height, which is O(log n) worst-case here (the weight balance bounds
2044
+ * it) -- strictly safer than Treap's expected bound, disclosed here + in the ADR, on the
2045
+ * native stack, so still 0 B/op.
2046
+ *
2047
+ * Keys and values are FINITE numbers (typeof-guarded BEFORE coercion -- Symbol / BigInt /
2048
+ * NaN / +-Infinity fail closed with a `[lite-logn]` throw). `set` on an EXISTING key
2049
+ * updates its value in place (no new node, no rebuild). A missing / empty query returns
2050
+ * `undefined` (never throws). Fixed capacity: a full pool throws, never silently drops.
2051
+ * `rangeIter` is a VERSION-STAMPED iterator -- any structural OR value mutation mid-
2052
+ * iteration throws `[lite-logn]` rather than yield stale data.
2053
+ *
2054
+ * Unlike Treap there is NO `split` / `merge`: those are the treap's arena-sharing set
2055
+ * surgery (they rewire a randomized heap in place); a scapegoat has no priority heap to
2056
+ * merge by, and an honest deterministic split/merge would be O(n) rebuilds, forfeiting
2057
+ * the sub-linear headline -- so the surface is deliberately the ordered-map + order-
2058
+ * statistic core (get / has / set / delete / rank / select / successor / predecessor /
2059
+ * rangeIter / forEach / clear), documented in the ADR as the asymmetry vs Treap.
2060
+ */
2061
+ export class Scapegoat {
2062
+ /**
2063
+ * @param {number} capacity exact max live entries; integer in [1, 2^31-1].
2064
+ * @param {number} [alpha] weight-balance factor in the OPEN interval (0.55, 0.75)
2065
+ * (both ends throw); default 2/3. Frozen after construction.
2066
+ */
2067
+ constructor(capacity, alpha = 2 / 3) {
2068
+ // typeof guard BEFORE coercion (Number.isInteger is Symbol/BigInt-safe).
2069
+ if (typeof capacity !== 'number' || !Number.isInteger(capacity) ||
2070
+ capacity < 1 || capacity > SG_MAX_CAPACITY) {
2071
+ throw new RangeError(
2072
+ '[lite-logn] Scapegoat capacity must be an integer in [1, 2^31-1], got ' +
2073
+ String(capacity));
2074
+ }
2075
+ // typeof guard BEFORE the range check; the interval is OPEN (both 0.55 and 0.75 throw).
2076
+ if (typeof alpha !== 'number' || !Number.isFinite(alpha) || alpha <= 0.55 || alpha >= 0.75) {
2077
+ throw new RangeError(
2078
+ '[lite-logn] Scapegoat alpha must be a number in the open interval (0.55, 0.75), got ' +
2079
+ String(alpha));
2080
+ }
2081
+ this._cap = capacity; // max live entries
2082
+ this._key = new Float64Array(capacity + 1); // key at each slot
2083
+ this._value = new Float64Array(capacity + 1); // value at each slot
2084
+ this._left = new Uint32Array(capacity + 1); // left child slot; NIL = 0
2085
+ this._right = new Uint32Array(capacity + 1); // right child slot; NIL = 0
2086
+ this._size = new Uint32Array(capacity + 1); // subtree node count; _size[0] = 0
2087
+ this._pool = new NodePool(capacity); // free-list over slots [1, capacity]
2088
+ this._flat = new Uint32Array(capacity); // rebuild scratch: sorted slot indices
2089
+ this._stack = new Uint32Array(capacity + 1); // flatten / descent index-stack (reused)
2090
+ this._root = 0; // NIL == empty tree
2091
+ this._maxCount = 0; // high-water size since the last full rebuild
2092
+ this._version = 0; // iterator invalidation stamp
2093
+ this._alpha = alpha; // ctor-frozen weight-balance factor
2094
+ this._invAlpha = 1 / alpha; // ctor-cached: no per-op Math.log
2095
+ }
2096
+
2097
+ /** Live entry count. O(1) (the root subtree count). */
2098
+ get size() { return this._root === 0 ? 0 : this._size[this._root]; }
2099
+
2100
+ /** The fixed capacity this tree was sized for. O(1). */
2101
+ get capacity() { return this._cap; }
2102
+
2103
+ /** The frozen weight-balance factor. O(1). */
2104
+ get alpha() { return this._alpha; }
2105
+
2106
+ /**
2107
+ * The value stored under `key`, or `undefined` if absent (never throws on a missing /
2108
+ * empty query). O(log n) WORST-case: a plain BST descent over a weight-balanced tree.
2109
+ * Fails closed on a non-number / non-finite key (typeof-guarded first).
2110
+ * @param {number} key a finite number
2111
+ * @returns {number|undefined}
2112
+ */
2113
+ get(key) {
2114
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
2115
+ const L = this._left, R = this._right, K = this._key;
2116
+ let t = this._root;
2117
+ while (t !== 0) {
2118
+ if (key < K[t]) t = L[t];
2119
+ else if (key > K[t]) t = R[t];
2120
+ else return this._value[t];
2121
+ }
2122
+ return undefined;
2123
+ }
2124
+
2125
+ /**
2126
+ * True iff `key` is currently in the tree. O(log n) worst-case. Fails closed on a
2127
+ * non-finite key (typeof-guarded first).
2128
+ * @param {number} key a finite number
2129
+ * @returns {boolean}
2130
+ */
2131
+ has(key) {
2132
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
2133
+ const L = this._left, R = this._right, K = this._key;
2134
+ let t = this._root;
2135
+ while (t !== 0) {
2136
+ if (key < K[t]) t = L[t];
2137
+ else if (key > K[t]) t = R[t];
2138
+ else return true;
2139
+ }
2140
+ return false;
2141
+ }
2142
+
2143
+ /**
2144
+ * Insert `key -> value`, or UPDATE the value in place if `key` already exists (no new
2145
+ * node, no rebuild). AMORTIZED O(log n): a descent recording the path, then (on
2146
+ * insert) an amortized-cheap weight-balance check that occasionally rebuilds the
2147
+ * scapegoat subtree. Fails closed: a non-finite key or value (typeof-guarded first),
2148
+ * or a full pool, each throw `[lite-logn]` as a no-op.
2149
+ * @param {number} key a finite number
2150
+ * @param {number} value a finite number
2151
+ * @returns {this}
2152
+ */
2153
+ set(key, value) {
2154
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
2155
+ if (typeof value !== 'number' || !Number.isFinite(value)) return this._badValue(value);
2156
+ const K = this._key, L = this._left, R = this._right, S = this._size, stk = this._stack;
2157
+ let t = this._root, sp = 0;
2158
+ while (t !== 0) { // descend, recording the path; update in place if present
2159
+ stk[sp++] = t;
2160
+ if (key < K[t]) t = L[t];
2161
+ else if (key > K[t]) t = R[t];
2162
+ else { this._value[t] = value; this._version = (this._version + 1) | 0; return this; }
2163
+ }
2164
+ const slot = this._pool.alloc();
2165
+ if (slot === 0) return this._full();
2166
+ K[slot] = key; this._value[slot] = value; L[slot] = 0; R[slot] = 0; S[slot] = 1;
2167
+ if (sp === 0) this._root = slot; // first node
2168
+ else { const p = stk[sp - 1]; if (key < K[p]) L[p] = slot; else R[p] = slot; }
2169
+ for (let i = 0; i < sp; i++) S[stk[i]]++; // every ancestor gained one node
2170
+ const newSize = S[this._root]; // == old size + 1
2171
+ if (newSize > this._maxCount) this._maxCount = newSize;
2172
+ this._version = (this._version + 1) | 0;
2173
+ // Depth test with NO Math.log: the new node sits at depth d == sp; it is too deep
2174
+ // iff d > h_alpha(newSize) == floor(_invLog * log2(newSize)), tested EXACTLY as
2175
+ // _invAlpha^d > newSize (integer d, so strict > matches the floor). One float
2176
+ // multiply per level -- only on this insert path, never on get.
2177
+ let bound = 1;
2178
+ for (let i = 0; i < sp; i++) bound *= this._invAlpha;
2179
+ if (bound > newSize) {
2180
+ // Walk the recorded path up to the SCAPEGOAT: the lowest ancestor whose
2181
+ // path-child subtree exceeds alpha of its own (post-insert) size.
2182
+ let g = -1;
2183
+ for (let i = sp - 1; i >= 0; i--) {
2184
+ const node = stk[i];
2185
+ const child = i === sp - 1 ? slot : stk[i + 1];
2186
+ if (S[child] > this._alpha * S[node]) { g = i; break; }
2187
+ }
2188
+ if (g === -1) {
2189
+ this._rebuildSubtree(this._root, 0, false); // defensive: rebuild whole tree
2190
+ } else {
2191
+ const node = stk[g];
2192
+ const parent = g > 0 ? stk[g - 1] : 0;
2193
+ const wasLeft = parent !== 0 && L[parent] === node;
2194
+ this._rebuildSubtree(node, parent, wasLeft);
2195
+ }
2196
+ }
2197
+ return this;
2198
+ }
2199
+
2200
+ /**
2201
+ * Remove `key`. AMORTIZED O(log n). Idempotent: returns `false` if `key` is absent (no
2202
+ * throw), `true` if it was present and removed. A standard BST delete fixes `_size` on
2203
+ * the unwind; when the tree has shrunk below `alpha * _maxCount` the WHOLE tree is
2204
+ * rebuilt perfectly balanced and `_maxCount` reset. Fails closed on a non-finite key.
2205
+ * @param {number} key a finite number
2206
+ * @returns {boolean}
2207
+ */
2208
+ delete(key) {
2209
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
2210
+ const L = this._left, R = this._right, K = this._key;
2211
+ let t = this._root, found = false;
2212
+ while (t !== 0) {
2213
+ if (key < K[t]) t = L[t];
2214
+ else if (key > K[t]) t = R[t];
2215
+ else { found = true; break; }
2216
+ }
2217
+ if (!found) return false; // absent (no throw)
2218
+ this._root = this._delete(this._root, key);
2219
+ this._version = (this._version + 1) | 0;
2220
+ const newSize = this._root === 0 ? 0 : this._size[this._root];
2221
+ if (newSize < this._alpha * this._maxCount) {
2222
+ this._rebuildSubtree(this._root, 0, false); // global rebuild
2223
+ this._maxCount = newSize;
2224
+ }
2225
+ return true;
2226
+ }
2227
+
2228
+ /**
2229
+ * The number of stored keys STRICTLY LESS than `x` (its rank / position). O(log n) via
2230
+ * subtree counts. `x` need not be present; `rank` of the smallest key is 0, of a key
2231
+ * past the max is `size`. Fails closed on a non-finite `x`.
2232
+ * @param {number} x a finite number
2233
+ * @returns {number} count of keys < x, in [0, size]
2234
+ */
2235
+ rank(x) {
2236
+ if (typeof x !== 'number' || !Number.isFinite(x)) return this._badKey(x);
2237
+ const L = this._left, R = this._right, K = this._key, S = this._size;
2238
+ let t = this._root, r = 0;
2239
+ while (t !== 0) {
2240
+ if (x <= K[t]) t = L[t]; // t (and its right) are >= x
2241
+ else { r += S[L[t]] + 1; t = R[t]; } // t's left subtree + t precede x
2242
+ }
2243
+ return r;
2244
+ }
2245
+
2246
+ /**
2247
+ * The k-th smallest KEY (0-based order statistic), or `undefined` if `k` is out of
2248
+ * range [0, size). O(log n) via subtree counts. Fails closed on a non-integer `k`
2249
+ * (typeof-guarded first); an in-type out-of-range `k` returns `undefined`.
2250
+ * @param {number} k integer in [0, size)
2251
+ * @returns {number|undefined} the k-th smallest key
2252
+ */
2253
+ select(k) {
2254
+ if (typeof k !== 'number' || !Number.isInteger(k)) return this._badRank(k);
2255
+ const sz = this._root === 0 ? 0 : this._size[this._root];
2256
+ if (k < 0 || k >= sz) return undefined;
2257
+ const L = this._left, R = this._right, K = this._key, S = this._size;
2258
+ let t = this._root;
2259
+ for (;;) {
2260
+ const ls = S[L[t]];
2261
+ if (k < ls) t = L[t];
2262
+ else if (k > ls) { k -= ls + 1; t = R[t]; }
2263
+ else return K[t];
2264
+ }
2265
+ }
2266
+
2267
+ /**
2268
+ * The smallest key STRICTLY greater than `key`, or `undefined` if none. O(log n).
2269
+ * `key` itself need not be present. Fails closed on a non-finite key.
2270
+ * @param {number} key a finite number
2271
+ * @returns {number|undefined}
2272
+ */
2273
+ successor(key) {
2274
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
2275
+ const L = this._left, R = this._right, K = this._key;
2276
+ let t = this._root, best;
2277
+ while (t !== 0) {
2278
+ if (K[t] > key) { best = K[t]; t = L[t]; }
2279
+ else t = R[t];
2280
+ }
2281
+ return best;
2282
+ }
2283
+
2284
+ /**
2285
+ * The largest key STRICTLY less than `key`, or `undefined` if none. O(log n). `key`
2286
+ * itself need not be present. Fails closed on a non-finite key.
2287
+ * @param {number} key a finite number
2288
+ * @returns {number|undefined}
2289
+ */
2290
+ predecessor(key) {
2291
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
2292
+ const L = this._left, R = this._right, K = this._key;
2293
+ let t = this._root, best;
2294
+ while (t !== 0) {
2295
+ if (K[t] < key) { best = K[t]; t = R[t]; }
2296
+ else t = L[t];
2297
+ }
2298
+ return best;
2299
+ }
2300
+
2301
+ /**
2302
+ * A VERSION-STAMPED iterator over the keys in `[lo, hi]` INCLUSIVE, ascending. Bounds
2303
+ * may be any number INCLUDING +-Infinity (an unbounded end); `NaN` fails closed, as
2304
+ * does `lo > hi`. The generator captures the tree's version and throws `[lite-logn]`
2305
+ * if any STRUCTURAL or VALUE mutation happens mid-iteration, rather than yield stale
2306
+ * data. It walks by repeated `successor` (each step a fresh O(log n) descent, so NO
2307
+ * scratch stack is allocated); the one documented per-protocol allocator is the
2308
+ * {value, done} per step.
2309
+ * @param {number} lo lower bound (inclusive); may be -Infinity
2310
+ * @param {number} hi upper bound (inclusive); may be +Infinity
2311
+ * @returns {IterableIterator<number>} the keys in [lo, hi], ascending
2312
+ */
2313
+ rangeIter(lo, hi) {
2314
+ if (typeof lo !== 'number' || Number.isNaN(lo)) return this._badBound(lo);
2315
+ if (typeof hi !== 'number' || Number.isNaN(hi)) return this._badBound(hi);
2316
+ if (lo > hi) return this._badRange(lo, hi);
2317
+ return this._rangeGen(lo, hi);
2318
+ }
2319
+
2320
+ /** @private version-stamped range generator (see rangeIter). */
2321
+ *_rangeGen(lo, hi) {
2322
+ const ver = this._version;
2323
+ let cur = this._ceil(lo); // smallest key >= lo, or undefined
2324
+ while (cur !== undefined && cur <= hi) {
2325
+ if (this._version !== ver) {
2326
+ throw new Error('[lite-logn] Scapegoat mutated during iteration');
2327
+ }
2328
+ yield cur;
2329
+ cur = this.successor(cur);
2330
+ }
2331
+ }
2332
+
2333
+ /**
2334
+ * Visit every live `(key, value)` pair in ASCENDING key order. O(n) cold in-order walk
2335
+ * (recursion depth = tree height, O(log n)), allocation-free in the loop body (pass a
2336
+ * hoisted callback). Unlike rangeIter this is NOT version-stamped -- mutating from
2337
+ * within the callback is the caller's responsibility (matching the other members).
2338
+ * @param {(key:number, value:number, tree:Scapegoat)=>void} fn
2339
+ */
2340
+ forEach(fn) {
2341
+ this._forEach(this._root, fn);
2342
+ }
2343
+
2344
+ /** @private recursive in-order walk. */
2345
+ _forEach(t, fn) {
2346
+ if (t === 0) return;
2347
+ this._forEach(this._left[t], fn);
2348
+ fn(this._key[t], this._value[t], this);
2349
+ this._forEach(this._right[t], fn);
2350
+ }
2351
+
2352
+ /**
2353
+ * Empty the tree, keeping the fixed capacity. O(capacity) cold path: returns every
2354
+ * node to the pool and points the root at NIL. @returns {this}
2355
+ */
2356
+ clear() {
2357
+ this._pool.clear();
2358
+ this._root = 0;
2359
+ this._maxCount = 0;
2360
+ this._version = (this._version + 1) | 0;
2361
+ return this;
2362
+ }
2363
+
2364
+ // ---- private structure (rebuild + recursive delete; hot bodies elsewhere) ----
2365
+
2366
+ /**
2367
+ * @private In-order flatten of subtree `root` into `_flat` using the preallocated
2368
+ * `_stack` index-column (Morris-free, ITERATIVE -- so a degenerate pre-rebuild chain
2369
+ * cannot overflow the native stack). Returns the node count written. 0 B/op.
2370
+ */
2371
+ _flatten(root) {
2372
+ const L = this._left, R = this._right, stk = this._stack, flat = this._flat;
2373
+ let node = root, sp = 0, c = 0;
2374
+ while (node !== 0 || sp > 0) {
2375
+ while (node !== 0) { stk[sp++] = node; node = L[node]; }
2376
+ node = stk[--sp];
2377
+ flat[c++] = node;
2378
+ node = R[node];
2379
+ }
2380
+ return c;
2381
+ }
2382
+
2383
+ /**
2384
+ * @private Build a perfectly balanced BST from the sorted slot range `_flat[lo..hi]`,
2385
+ * re-linking `_left` / `_right` / `_size`. Returns the subtree root (NIL if empty).
2386
+ * Bounded native recursion: depth is O(log(hi-lo+1)) <= ~31 (it produces a balanced
2387
+ * subtree), so it runs on the native call stack, never the GC heap. 0 B/op.
2388
+ */
2389
+ _buildBalanced(lo, hi) {
2390
+ if (lo > hi) return 0;
2391
+ const mid = (lo + hi) >> 1;
2392
+ const s = this._flat[mid];
2393
+ const l = this._buildBalanced(lo, mid - 1);
2394
+ const r = this._buildBalanced(mid + 1, hi);
2395
+ this._left[s] = l;
2396
+ this._right[s] = r;
2397
+ this._size[s] = this._size[l] + this._size[r] + 1; // _size[0] is a permanent 0
2398
+ return s;
2399
+ }
2400
+
2401
+ /**
2402
+ * @private Rebuild subtree `root` perfectly balanced and re-link it under `parent`
2403
+ * (or the tree root when `parent === 0`). `wasLeft` records which child link to
2404
+ * rewrite. Reuses the preallocated `_flat` + `_stack` scratch: 0 B/op.
2405
+ */
2406
+ _rebuildSubtree(root, parent, wasLeft) {
2407
+ const count = this._flatten(root);
2408
+ const nr = this._buildBalanced(0, count - 1);
2409
+ if (parent === 0) this._root = nr;
2410
+ else if (wasLeft) this._left[parent] = nr;
2411
+ else this._right[parent] = nr;
2412
+ }
2413
+
2414
+ /** @private recursive BST delete of `key` from subtree `t`; frees the removed slot,
2415
+ * fixing `_size` on the unwind. Depth = tree height = O(log n). */
2416
+ _delete(t, key) {
2417
+ const L = this._left, R = this._right, S = this._size, K = this._key;
2418
+ if (key < K[t]) {
2419
+ L[t] = this._delete(L[t], key);
2420
+ S[t] = S[L[t]] + S[R[t]] + 1;
2421
+ return t;
2422
+ }
2423
+ if (key > K[t]) {
2424
+ R[t] = this._delete(R[t], key);
2425
+ S[t] = S[L[t]] + S[R[t]] + 1;
2426
+ return t;
2427
+ }
2428
+ // found t
2429
+ const l = L[t], r = R[t];
2430
+ if (l === 0) { this._pool.free(t); return r; }
2431
+ if (r === 0) { this._pool.free(t); return l; }
2432
+ // two children: copy the in-order successor (min of the right subtree) into t,
2433
+ // then delete that successor from the right subtree (frees ITS slot).
2434
+ let m = r; while (L[m] !== 0) m = L[m];
2435
+ K[t] = K[m]; this._value[t] = this._value[m];
2436
+ R[t] = this._deleteMin(R[t]);
2437
+ S[t] = S[L[t]] + S[R[t]] + 1;
2438
+ return t;
2439
+ }
2440
+
2441
+ /** @private remove the minimum of subtree `t`, freeing its slot; return the new root. */
2442
+ _deleteMin(t) {
2443
+ const L = this._left, R = this._right, S = this._size;
2444
+ if (L[t] === 0) { const r = R[t]; this._pool.free(t); return r; }
2445
+ L[t] = this._deleteMin(L[t]);
2446
+ S[t] = S[L[t]] + S[R[t]] + 1;
2447
+ return t;
2448
+ }
2449
+
2450
+ /** @private smallest key >= `lo`, or undefined (the range-iter start). */
2451
+ _ceil(lo) {
2452
+ const L = this._left, R = this._right, K = this._key;
2453
+ let t = this._root, best;
2454
+ while (t !== 0) {
2455
+ if (K[t] >= lo) { best = K[t]; t = L[t]; }
2456
+ else t = R[t];
2457
+ }
2458
+ return best;
2459
+ }
2460
+
2461
+ // ---- cold path only: throw builders (string concat off the hot body) ----
2462
+
2463
+ /** @private */
2464
+ _badKey(key) {
2465
+ throw new TypeError(
2466
+ '[lite-logn] Scapegoat key must be a finite number, got ' + String(key));
2467
+ }
2468
+
2469
+ /** @private */
2470
+ _badValue(value) {
2471
+ throw new TypeError(
2472
+ '[lite-logn] Scapegoat value must be a finite number, got ' + String(value));
2473
+ }
2474
+
2475
+ /** @private */
2476
+ _badRank(k) {
2477
+ throw new TypeError(
2478
+ '[lite-logn] Scapegoat select index must be an integer, got ' + String(k));
2479
+ }
2480
+
2481
+ /** @private */
2482
+ _badBound(b) {
2483
+ throw new TypeError(
2484
+ '[lite-logn] Scapegoat rangeIter bound must be a number (not NaN), got ' + String(b));
2485
+ }
2486
+
2487
+ /** @private */
2488
+ _badRange(lo, hi) {
2489
+ throw new RangeError(
2490
+ '[lite-logn] Scapegoat rangeIter needs lo <= hi, got lo=' + String(lo) +
2491
+ ' hi=' + String(hi));
2492
+ }
2493
+
2494
+ /** @private */
2495
+ _full() {
2496
+ throw new RangeError('[lite-logn] Scapegoat full (capacity ' + this._cap + ')');
2497
+ }
2498
+ }
2499
+
2500
+ // MinMaxHeap (v0.7.0 session) -- a DEPQ (double-ended priority queue), array-embedded min-max heap (BELOW).
2501
+
2502
+ /**
2503
+ * Max MinMaxHeap capacity: slot indices 0..cap-1 index the two parallel, pointer-free
2504
+ * typed-array columns (`_key` Float64Array, `_id` Uint32Array). Children of slot i are
2505
+ * `2i+1` / `2i+2` and grandchildren `4i+3 .. 4i+6`, so the deepest index arithmetic a
2506
+ * hot op performs is `4*i + 6`; capping capacity at 2^31-1 keeps every derived index a
2507
+ * positive int32. The index arithmetic, not the byte count, is the hard ceiling -- the
2508
+ * same "the arithmetic caps it" reasoning as BinaryHeap's BH_MAX_CAPACITY.
2509
+ */
2510
+ const MMH_MAX_CAPACITY = 0x7FFFFFFF; // 2^31 - 1
2511
+
2512
+ /**
2513
+ * A MIN-MAX HEAP: a DOUBLE-ENDED priority queue (DEPQ) held in ONE array-embedded binary
2514
+ * heap whose levels ALTERNATE min / max (Atkinson, Sack, Santoro & Strothotte 1986). Even
2515
+ * depth (the root is depth 0) is a MIN level, odd depth is a MAX level, so the global
2516
+ * minimum sits at the root and the global maximum is the LARGER of the root's up-to-two
2517
+ * children. That single alternating heap answers BOTH ends: `peekMin` / `peekMax` are
2518
+ * O(1); `push` / `popMin` / `popMax` are O(log n) WORST-case. It is the family's DEPQ --
2519
+ * where BinaryHeap fixes one extreme at construction, a min-max heap serves both from one
2520
+ * structure without a second heap or a paired-heap correspondence to maintain.
2521
+ *
2522
+ * Storage (allocated once, sized to capacity), the BinaryHeap id+key idiom:
2523
+ * - `_key` Float64Array -- the priority key at each heap SLOT (min-max-ordered).
2524
+ * - `_id` Uint32Array -- the opaque payload id at each heap SLOT (moves with its key).
2525
+ * There is NO reverse-index map (`_pos`) and so NO addressable `changeKey` / `remove`: the
2526
+ * id is an OPAQUE Uint32 payload, NOT a unique handle -- duplicate ids are allowed, and the
2527
+ * id domain is the full Uint32 range [0, 2^32) (a wider domain than BinaryHeap's [0,
2528
+ * capacity), which only that member's `_pos` map constrains). See decisions/0009.
2529
+ *
2530
+ * Level parity is computed zero-alloc: slot i (0-based) is a MIN level iff
2531
+ * `((31 - Math.clz32(i + 1)) & 1) === 0` (the depth `31 - clz32(i+1)` is even). The sift
2532
+ * is HOLE-PUNCHING (not a 3-write swap chain): the moving element is cached in locals once
2533
+ * and the hole walks writing ONE slot per level.
2534
+ * - push: append at the tail, compare the new element to its PARENT to pick the own-level
2535
+ * vs other-level chain, then bubble by GRANDPARENT comparisons up the min-or-max chain.
2536
+ * - popMin / popMax: open a hole at the root (min) or at the max-of-{slot1,slot2} (max),
2537
+ * move the last element into it, and trickle DOWN over CHILDREN + GRANDCHILDREN (a min
2538
+ * level sinks toward the smallest of the up-to-six descendants, a max level toward the
2539
+ * largest); a GRANDCHILD move does the extra parent re-check that keeps the alternating
2540
+ * order intact. Every one of the four grandchild indices is bound-checked against the
2541
+ * live size (the classic min-max off-by-one). Zero bytes allocated after construction.
2542
+ *
2543
+ * Keys are FINITE numbers (typeof-guarded BEFORE coercion -- Symbol / BigInt / NaN /
2544
+ * +-Infinity fail closed with a `[lite-logn]` throw); ids are integers in [0, 2^32). A key
2545
+ * guard fires FIRST, then the id guard, then the full-heap guard -- any throw leaves `size`
2546
+ * unchanged. Every peek / pop on an EMPTY heap returns `undefined` and NEVER throws. Fixed
2547
+ * capacity: overflow throws, never silently drops.
2548
+ *
2549
+ * This is the classic ONE-element-per-node min-max heap ONLY; the interval-heap DEPQ (two
2550
+ * elements per node) is a deliberately deferred alternative -- see decisions/0009 + NOT FOR.
2551
+ */
2552
+ export class MinMaxHeap {
2553
+ /**
2554
+ * @param {number} capacity exact max live entries; integer in [1, 2^31-1].
2555
+ */
2556
+ constructor(capacity) {
2557
+ // typeof guard BEFORE coercion (Number.isInteger is Symbol/BigInt-safe).
2558
+ if (typeof capacity !== 'number' || !Number.isInteger(capacity) ||
2559
+ capacity < 1 || capacity > MMH_MAX_CAPACITY) {
2560
+ throw new RangeError(
2561
+ '[lite-logn] MinMaxHeap capacity must be an integer in [1, 2^31-1], got ' +
2562
+ String(capacity));
2563
+ }
2564
+ this._key = new Float64Array(capacity); // key at each heap slot
2565
+ this._id = new Uint32Array(capacity); // opaque payload id at each heap slot
2566
+ this._cap = capacity; // exact fixed capacity
2567
+ this._n = 0; // live entries (heap size)
2568
+ }
2569
+
2570
+ /** Live entry count. O(1). */
2571
+ get size() { return this._n; }
2572
+
2573
+ /** The fixed capacity this heap was sized for. O(1). */
2574
+ get capacity() { return this._cap; }
2575
+
2576
+ /**
2577
+ * Insert entity `id` with priority `key`. O(log n). Fails closed: a non-finite /
2578
+ * non-number key (checked FIRST), a non-integer / out-of-range id, or a full heap each
2579
+ * throw `[lite-logn]` as a no-op (size unchanged).
2580
+ * @param {number} id integer in [0, 2^32), an OPAQUE payload (not required unique)
2581
+ * @param {number} key a finite number
2582
+ */
2583
+ push(id, key) {
2584
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
2585
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) {
2586
+ return this._badId(id);
2587
+ }
2588
+ const n = this._n;
2589
+ if (n === this._cap) return this._full();
2590
+ this._n = n + 1;
2591
+ this._siftUp(n, key, id);
2592
+ }
2593
+
2594
+ /**
2595
+ * Remove and return the id at the MINIMUM key (the root), or `undefined` if empty
2596
+ * (never throws on empty). O(log n).
2597
+ * @returns {number|undefined}
2598
+ */
2599
+ popMin() {
2600
+ const n = this._n;
2601
+ if (n === 0) return undefined;
2602
+ const top = this._id[0];
2603
+ const last = n - 1;
2604
+ this._n = last;
2605
+ if (last > 0) this._siftDownMin(0, this._key[last], this._id[last]);
2606
+ return top;
2607
+ }
2608
+
2609
+ /**
2610
+ * Remove and return the id at the MAXIMUM key (the larger of the root's up-to-two
2611
+ * children, or the root itself when the heap holds one element), or `undefined` if
2612
+ * empty (never throws on empty). O(log n).
2613
+ * @returns {number|undefined}
2614
+ */
2615
+ popMax() {
2616
+ const n = this._n;
2617
+ if (n === 0) return undefined;
2618
+ if (n === 1) { this._n = 0; return this._id[0]; }
2619
+ const K = this._key;
2620
+ // Max is at slot 1, unless slot 2 exists (n > 2) and holds a larger key.
2621
+ let mi = 1;
2622
+ if (n > 2 && K[2] > K[1]) mi = 2;
2623
+ const top = this._id[mi];
2624
+ const last = n - 1;
2625
+ this._n = last;
2626
+ // If the max WAS the last element, dropping the tail already removed it.
2627
+ if (mi !== last) this._siftDownMax(mi, K[last], this._id[last]);
2628
+ return top;
2629
+ }
2630
+
2631
+ /** The id at the minimum key (root), or `undefined` if empty. Read-only. O(1). */
2632
+ peekMin() { return this._n === 0 ? undefined : this._id[0]; }
2633
+
2634
+ /** The minimum key (root), or `undefined` if empty. O(1). */
2635
+ peekMinKey() { return this._n === 0 ? undefined : this._key[0]; }
2636
+
2637
+ /** The id at the maximum key, or `undefined` if empty. Read-only. O(1). */
2638
+ peekMax() {
2639
+ const n = this._n;
2640
+ if (n === 0) return undefined;
2641
+ if (n === 1) return this._id[0];
2642
+ const K = this._key;
2643
+ return (n > 2 && K[2] > K[1]) ? this._id[2] : this._id[1];
2644
+ }
2645
+
2646
+ /** The maximum key, or `undefined` if empty. O(1). */
2647
+ peekMaxKey() {
2648
+ const n = this._n;
2649
+ if (n === 0) return undefined;
2650
+ if (n === 1) return this._key[0];
2651
+ const K = this._key;
2652
+ return (n > 2 && K[2] > K[1]) ? K[2] : K[1];
2653
+ }
2654
+
2655
+ /** Empty the heap. O(1) (resets size; the columns are kept). */
2656
+ clear() { this._n = 0; }
2657
+
2658
+ /**
2659
+ * Visit every live (id, key) pair in UNSPECIFIED (heap-array) order -- NOT sorted /
2660
+ * not pop order. O(n) cold scan, allocation-free (pass a hoisted callback).
2661
+ * @param {(id:number, key:number, heap:MinMaxHeap)=>void} fn
2662
+ */
2663
+ forEach(fn) {
2664
+ const id = this._id, key = this._key, n = this._n;
2665
+ for (let i = 0; i < n; i++) fn(id[i], key[i], this);
2666
+ }
2667
+
2668
+ /**
2669
+ * Iterate live entity ids in UNSPECIFIED (heap-array) order -- NOT sorted. The one
2670
+ * documented per-protocol allocator (a {value, done} per step); use forEach for the
2671
+ * alloc-free scan.
2672
+ */
2673
+ *[Symbol.iterator]() {
2674
+ const id = this._id, n = this._n;
2675
+ for (let i = 0; i < n; i++) yield id[i];
2676
+ }
2677
+
2678
+ /**
2679
+ * Floyd O(n) bulk build: load every (ids[i], keys[i]) pair then heapify bottom-up in
2680
+ * O(n) (deepest-first, level-aware sift-down), rather than n individual O(log n)
2681
+ * pushes. COLD path; fails closed on any violation (non-array-like / length mismatch,
2682
+ * count > capacity, non-integer / out-of-range id, non-finite key) before use.
2683
+ * @param {ArrayLike<number>} ids integers in [0, 2^32) (not required unique)
2684
+ * @param {ArrayLike<number>} keys finite numbers, keys.length === ids.length
2685
+ * @param {number} capacity
2686
+ * @returns {MinMaxHeap}
2687
+ */
2688
+ static build(ids, keys, capacity) {
2689
+ const heap = new MinMaxHeap(capacity);
2690
+ if (ids == null || keys == null ||
2691
+ typeof ids.length !== 'number' || typeof keys.length !== 'number') {
2692
+ throw new TypeError('[lite-logn] MinMaxHeap.build needs array-like ids and keys');
2693
+ }
2694
+ const count = ids.length;
2695
+ if (keys.length !== count) {
2696
+ throw new RangeError(
2697
+ '[lite-logn] MinMaxHeap.build ids/keys length mismatch (' +
2698
+ count + ' vs ' + keys.length + ')');
2699
+ }
2700
+ if (count > capacity) {
2701
+ throw new RangeError(
2702
+ '[lite-logn] MinMaxHeap.build count ' + count + ' exceeds capacity ' + capacity);
2703
+ }
2704
+ const K = heap._key, I = heap._id;
2705
+ for (let i = 0; i < count; i++) {
2706
+ const id = ids[i];
2707
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id > 0xFFFFFFFF) {
2708
+ throw new RangeError(
2709
+ '[lite-logn] MinMaxHeap.build id must be an integer in [0, 2^32), got ' +
2710
+ String(id));
2711
+ }
2712
+ const key = keys[i];
2713
+ if (typeof key !== 'number' || !Number.isFinite(key)) {
2714
+ throw new TypeError(
2715
+ '[lite-logn] MinMaxHeap.build key must be a finite number, got ' + String(key));
2716
+ }
2717
+ K[i] = key;
2718
+ I[i] = id;
2719
+ }
2720
+ heap._n = count;
2721
+ // Floyd: sift down every internal node, deepest-first, level-aware. O(n).
2722
+ for (let i = (count >> 1) - 1; i >= 0; i--) {
2723
+ if (((31 - Math.clz32(i + 1)) & 1) === 0) heap._siftDownMin(i, K[i], I[i]);
2724
+ else heap._siftDownMax(i, K[i], I[i]);
2725
+ }
2726
+ return heap;
2727
+ }
2728
+
2729
+ // ---- private hole-punching sifts (hot bodies) --------------------------
2730
+
2731
+ /**
2732
+ * Sift UP from `hole` after an append: compare to the PARENT to decide whether the new
2733
+ * element belongs on its own level's chain or the other level's, then bubble it up by
2734
+ * GRANDPARENT comparisons. One write per level; zero temporaries.
2735
+ * @private
2736
+ */
2737
+ _siftUp(hole, key, id) {
2738
+ const K = this._key, I = this._id;
2739
+ if (hole === 0) { K[0] = key; I[0] = id; return; }
2740
+ const parent = (hole - 1) >> 1;
2741
+ // Depth parity of `hole`: MIN level iff (31 - clz32(hole+1)) is even.
2742
+ if (((31 - Math.clz32(hole + 1)) & 1) === 0) { // hole is on a MIN level
2743
+ if (key > K[parent]) { // parent is a MAX node: element belongs above it
2744
+ K[hole] = K[parent]; I[hole] = I[parent];
2745
+ this._bubbleUpMax(parent, key, id);
2746
+ } else {
2747
+ this._bubbleUpMin(hole, key, id);
2748
+ }
2749
+ } else { // hole is on a MAX level
2750
+ if (key < K[parent]) { // parent is a MIN node: element belongs above it
2751
+ K[hole] = K[parent]; I[hole] = I[parent];
2752
+ this._bubbleUpMin(parent, key, id);
2753
+ } else {
2754
+ this._bubbleUpMax(hole, key, id);
2755
+ }
2756
+ }
2757
+ }
2758
+
2759
+ /** Bubble a MIN-level hole up by grandparents while the element is smaller. @private */
2760
+ _bubbleUpMin(hole, key, id) {
2761
+ const K = this._key, I = this._id;
2762
+ while (hole > 2) { // has a grandparent (hole >= 3)
2763
+ const gp = (hole - 3) >> 2; // (((hole-1)>>1)-1)>>1
2764
+ if (key < K[gp]) { K[hole] = K[gp]; I[hole] = I[gp]; hole = gp; }
2765
+ else break;
2766
+ }
2767
+ K[hole] = key; I[hole] = id;
2768
+ }
2769
+
2770
+ /** Bubble a MAX-level hole up by grandparents while the element is larger. @private */
2771
+ _bubbleUpMax(hole, key, id) {
2772
+ const K = this._key, I = this._id;
2773
+ while (hole > 2) { // has a grandparent (hole >= 3)
2774
+ const gp = (hole - 3) >> 2; // (((hole-1)>>1)-1)>>1
2775
+ if (key > K[gp]) { K[hole] = K[gp]; I[hole] = I[gp]; hole = gp; }
2776
+ else break;
2777
+ }
2778
+ K[hole] = key; I[hole] = id;
2779
+ }
2780
+
2781
+ /**
2782
+ * Trickle a MIN-level hole DOWN toward the SMALLEST of its up-to-six descendants
2783
+ * (children `2h+1`/`2h+2`, grandchildren `4h+3..4h+6`). A grandchild move does the
2784
+ * extra max-parent re-check that keeps the alternating order. Every grandchild index
2785
+ * is bound-checked against the live size. One write per level; zero temporaries.
2786
+ * @private
2787
+ */
2788
+ _siftDownMin(hole, key, id) {
2789
+ const K = this._key, I = this._id, n = this._n;
2790
+ for (;;) {
2791
+ const c1 = (hole << 1) + 1;
2792
+ if (c1 >= n) break; // leaf: no children -> settle here
2793
+ const c2 = c1 + 1;
2794
+ let m = c1, mGrand = false; // smallest descendant so far
2795
+ if (c2 < n && K[c2] < K[m]) m = c2;
2796
+ const gEnd = (c2 << 1) + 2; // 4h+6, the last grandchild index
2797
+ for (let g = (c1 << 1) + 1; g < n && g <= gEnd; g++) { // grandchildren 4h+3..4h+6
2798
+ if (K[g] < K[m]) { m = g; mGrand = true; }
2799
+ }
2800
+ if (K[m] >= key) break; // element is <= every descendant -> settle
2801
+ if (!mGrand) { // smallest is a direct child (a leaf) -> place, done
2802
+ K[hole] = K[m]; I[hole] = I[m];
2803
+ hole = m;
2804
+ break;
2805
+ }
2806
+ // smallest is a grandchild: pull it up, then reconcile with its MAX-level parent.
2807
+ K[hole] = K[m]; I[hole] = I[m];
2808
+ const p = (m - 1) >> 1;
2809
+ if (key > K[p]) { // element too big under max parent p: settle it at p,
2810
+ const pk = K[p], pid = I[p]; // carry p's (smaller) value on down from m.
2811
+ K[p] = key; I[p] = id;
2812
+ key = pk; id = pid;
2813
+ }
2814
+ hole = m;
2815
+ }
2816
+ K[hole] = key; I[hole] = id;
2817
+ }
2818
+
2819
+ /**
2820
+ * Trickle a MAX-level hole DOWN toward the LARGEST of its up-to-six descendants. Mirror
2821
+ * of `_siftDownMin`: a grandchild move re-checks the MIN-level parent. Every grandchild
2822
+ * index is bound-checked. One write per level; zero temporaries.
2823
+ * @private
2824
+ */
2825
+ _siftDownMax(hole, key, id) {
2826
+ const K = this._key, I = this._id, n = this._n;
2827
+ for (;;) {
2828
+ const c1 = (hole << 1) + 1;
2829
+ if (c1 >= n) break; // leaf: no children -> settle here
2830
+ const c2 = c1 + 1;
2831
+ let m = c1, mGrand = false; // largest descendant so far
2832
+ if (c2 < n && K[c2] > K[m]) m = c2;
2833
+ const gEnd = (c2 << 1) + 2; // 4h+6, the last grandchild index
2834
+ for (let g = (c1 << 1) + 1; g < n && g <= gEnd; g++) { // grandchildren 4h+3..4h+6
2835
+ if (K[g] > K[m]) { m = g; mGrand = true; }
2836
+ }
2837
+ if (K[m] <= key) break; // element is >= every descendant -> settle
2838
+ if (!mGrand) { // largest is a direct child (a leaf) -> place, done
2839
+ K[hole] = K[m]; I[hole] = I[m];
2840
+ hole = m;
2841
+ break;
2842
+ }
2843
+ // largest is a grandchild: pull it up, then reconcile with its MIN-level parent.
2844
+ K[hole] = K[m]; I[hole] = I[m];
2845
+ const p = (m - 1) >> 1;
2846
+ if (key < K[p]) { // element too small under min parent p: settle it at p,
2847
+ const pk = K[p], pid = I[p]; // carry p's (larger) value on down from m.
2848
+ K[p] = key; I[p] = id;
2849
+ key = pk; id = pid;
2850
+ }
2851
+ hole = m;
2852
+ }
2853
+ K[hole] = key; I[hole] = id;
2854
+ }
2855
+
2856
+ // ---- cold path only: throw builders (string concat off the hot body) ---
2857
+
2858
+ /** @private */
2859
+ _badId(id) {
2860
+ throw new RangeError(
2861
+ '[lite-logn] MinMaxHeap id must be an integer in [0, 2^32), got ' + String(id));
2862
+ }
2863
+
2864
+ /** @private */
2865
+ _badKey(key) {
2866
+ throw new TypeError(
2867
+ '[lite-logn] MinMaxHeap key must be a finite number, got ' + String(key));
2868
+ }
2869
+
2870
+ /** @private */
2871
+ _full() {
2872
+ throw new RangeError('[lite-logn] MinMaxHeap full (capacity ' + this._cap + ')');
2873
+ }
2874
+ }