@zakkster/lite-logn 0.4.0 → 0.6.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/CHANGELOG.md +130 -0
- package/LogN.d.ts +109 -0
- package/LogN.js +1087 -1
- package/README.md +174 -8
- package/llms.txt +99 -2
- package/package.json +10 -3
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.
|
|
42
|
+
export const VERSION = '0.6.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)
|
|
@@ -1410,3 +1410,1089 @@ export class SkipList {
|
|
|
1410
1410
|
throw new RangeError('[lite-logn] SkipList full (capacity ' + this._cap + ')');
|
|
1411
1411
|
}
|
|
1412
1412
|
}
|
|
1413
|
+
|
|
1414
|
+
// Treap (v0.5.0 session) -- an AUGMENTED randomized-balanced ordered map (BELOW).
|
|
1415
|
+
|
|
1416
|
+
/** Treap default PRNG seed when the caller does not supply one. */
|
|
1417
|
+
const TR_DEFAULT_SEED = 0x9E3779B9;
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Max Treap capacity: `0x7FFFFFFF` (2^31 - 1). Every node is addressed by a slot
|
|
1421
|
+
* INDEX stored in `Uint32Array` link columns (`_left` / `_right`), and each subtree
|
|
1422
|
+
* count lives in a `Uint32Array` (`_size`); an index and a count must both fit an
|
|
1423
|
+
* unsigned 32-bit word. `NIL = 0` reserves slot 0 as the empty-subtree sentinel, so
|
|
1424
|
+
* live slots run [1, capacity]. The index / count arithmetic (Uint32 slot indices +
|
|
1425
|
+
* `NIL = 0` + Uint32 subtree sizes), not the byte count, is the hard ceiling -- the
|
|
1426
|
+
* same "the arithmetic caps it" reasoning as the array-embedded members.
|
|
1427
|
+
*/
|
|
1428
|
+
const TR_MAX_CAPACITY = 0x7FFFFFFF; // 2^31 - 1
|
|
1429
|
+
|
|
1430
|
+
/**
|
|
1431
|
+
* A TREAP: a randomized, self-balancing BINARY SEARCH TREE that is ALSO an order-
|
|
1432
|
+
* statistic tree (an AUGMENTED ordered map key -> value). It is the family's second
|
|
1433
|
+
* randomized member and its second pointer-based one; where SkipList threads a
|
|
1434
|
+
* probabilistic tower of forward links, a treap keeps a single BST whose SHAPE is
|
|
1435
|
+
* randomized by a per-node priority, giving EXPECTED O(log n) height. The trick that
|
|
1436
|
+
* keeps it zero-GC is the SkipList one: nodes are slot INDICES in flat typed-array
|
|
1437
|
+
* columns over the same private free-list (NodePool), never heap objects.
|
|
1438
|
+
*
|
|
1439
|
+
* Two orders held at once (the treap invariant):
|
|
1440
|
+
* - BST order on `_key` (an in-order walk is ascending by key); and
|
|
1441
|
+
* - MAX-HEAP order on `_prio` (every parent's priority >= its children's), where
|
|
1442
|
+
* `_prio` is one instance-local NR-LCG draw per inserted node. A random priority
|
|
1443
|
+
* heap over a BST is provably balanced IN EXPECTATION.
|
|
1444
|
+
* The augmentation is a third invariant: `_size[x]` is the number of nodes in x's
|
|
1445
|
+
* subtree, maintained in the SAME pass as every link rewrite, so `rank` (how many
|
|
1446
|
+
* keys are < x) and `select` (the k-th smallest key) are O(log n) via subtree counts.
|
|
1447
|
+
*
|
|
1448
|
+
* Storage (allocated once, sized to capacity + 1; slot 0 is the NIL sentinel whose
|
|
1449
|
+
* `_size` is a permanent 0 -- null is not zero: slot 0 is "no subtree", never data):
|
|
1450
|
+
* - `_key` / `_value` `Float64Array` -- key and value at each slot.
|
|
1451
|
+
* - `_left` / `_right` `Uint32Array` -- child slot indices, `NIL = 0`.
|
|
1452
|
+
* - `_prio` `Uint32Array` -- the random heap priority at each slot.
|
|
1453
|
+
* - `_size` `Uint32Array` -- the subtree node count at each slot.
|
|
1454
|
+
* - `_pool` NodePool -- the free-list handing out slot indices [1, capacity].
|
|
1455
|
+
*
|
|
1456
|
+
* Honesty (randomized member): a hot op is EXPECTED O(log n), not worst-case -- the
|
|
1457
|
+
* same contract as SkipList. An unlucky priority draw can build a tall thin tree and
|
|
1458
|
+
* spike a single op; the MAX single insert (the rotation chain) is DISCLOSED, never
|
|
1459
|
+
* gated (decisions/0007-treap.md, D-06). RECURSION DEPTH: `set` / `delete` / `split`
|
|
1460
|
+
* / `merge` recurse over slot indices; the recursion depth equals the tree height,
|
|
1461
|
+
* which is O(log n) EXPECTED and O(n) worst-case on a pathological priority draw.
|
|
1462
|
+
* Because priorities come from the instance-local LCG (NOT caller-controlled), an
|
|
1463
|
+
* adversary cannot force the worst case with chosen keys, so the expected bound holds
|
|
1464
|
+
* for the fixed public surface -- this matches the EXPECTED contract and is DISCLOSED
|
|
1465
|
+
* here + in the ADR, not silently shipped. The recursion uses the native call stack,
|
|
1466
|
+
* not the GC heap, so every hot op is still 0 B/op.
|
|
1467
|
+
*
|
|
1468
|
+
* Keys and values are FINITE numbers (typeof-guarded BEFORE coercion -- Symbol /
|
|
1469
|
+
* BigInt / NaN / +-Infinity fail closed with a `[lite-logn]` throw). `set` on an
|
|
1470
|
+
* EXISTING key updates its value in place (no new node). A missing / empty query
|
|
1471
|
+
* returns `undefined` (never throws). Fixed capacity: a full pool throws, never
|
|
1472
|
+
* silently drops. `rangeIter` is a VERSION-STAMPED iterator -- any structural OR
|
|
1473
|
+
* value mutation mid-iteration throws `[lite-logn]` rather than yield stale data.
|
|
1474
|
+
*
|
|
1475
|
+
* `split` / `merge` are O(log n) EXPECTED because they REWIRE nodes in place rather
|
|
1476
|
+
* than copy: the two treaps a `split` returns (and the two a `merge` consumes) SHARE
|
|
1477
|
+
* the source's backing arena (columns + free-list). `split` and `merge` therefore
|
|
1478
|
+
* CONSUME their inputs (leaving them empty) and hand back views over the same store
|
|
1479
|
+
* -- the only way to keep the structural ops sub-linear under a pooled allocator.
|
|
1480
|
+
*/
|
|
1481
|
+
export class Treap {
|
|
1482
|
+
/**
|
|
1483
|
+
* @param {number} capacity exact max live entries; integer in [1, 2^31-1].
|
|
1484
|
+
* @param {number} [seed] PRNG seed; unsigned 32-bit integer (default fixed).
|
|
1485
|
+
*/
|
|
1486
|
+
constructor(capacity, seed) {
|
|
1487
|
+
// typeof guard BEFORE coercion (Number.isInteger is Symbol/BigInt-safe).
|
|
1488
|
+
if (typeof capacity !== 'number' || !Number.isInteger(capacity) ||
|
|
1489
|
+
capacity < 1 || capacity > TR_MAX_CAPACITY) {
|
|
1490
|
+
throw new RangeError(
|
|
1491
|
+
'[lite-logn] Treap capacity must be an integer in [1, 2^31-1], got ' +
|
|
1492
|
+
String(capacity));
|
|
1493
|
+
}
|
|
1494
|
+
let s;
|
|
1495
|
+
if (seed === undefined) {
|
|
1496
|
+
s = TR_DEFAULT_SEED;
|
|
1497
|
+
} else if (typeof seed !== 'number' || !Number.isInteger(seed) ||
|
|
1498
|
+
seed < 0 || seed > 0xFFFFFFFF) {
|
|
1499
|
+
throw new RangeError(
|
|
1500
|
+
'[lite-logn] Treap seed must be an unsigned 32-bit integer, got ' +
|
|
1501
|
+
String(seed));
|
|
1502
|
+
} else {
|
|
1503
|
+
s = seed >>> 0;
|
|
1504
|
+
}
|
|
1505
|
+
s = s | 0; // store the LCG state as a SIGNED int32 (an unboxed Smi) -- see _lcgNext
|
|
1506
|
+
this._cap = capacity; // max live entries
|
|
1507
|
+
this._key = new Float64Array(capacity + 1); // key at each slot
|
|
1508
|
+
this._value = new Float64Array(capacity + 1); // value at each slot
|
|
1509
|
+
this._left = new Uint32Array(capacity + 1); // left child slot; NIL = 0
|
|
1510
|
+
this._right = new Uint32Array(capacity + 1); // right child slot; NIL = 0
|
|
1511
|
+
this._prio = new Uint32Array(capacity + 1); // random heap priority
|
|
1512
|
+
this._size = new Uint32Array(capacity + 1); // subtree node count; _size[0] = 0
|
|
1513
|
+
this._pool = new NodePool(capacity); // free-list over slots [1, capacity]
|
|
1514
|
+
this._root = 0; // NIL == empty tree
|
|
1515
|
+
this._seed0 = s; // initial seed (clear resets to it)
|
|
1516
|
+
this._seed = s; // live LCG state
|
|
1517
|
+
this._version = 0; // iterator invalidation stamp
|
|
1518
|
+
this._sr = 0; // split scratch (the "right" root)
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/** Live entry count. O(1) (the root subtree count). */
|
|
1522
|
+
get size() { return this._root === 0 ? 0 : this._size[this._root]; }
|
|
1523
|
+
|
|
1524
|
+
/** The fixed capacity this treap was sized for. O(1). */
|
|
1525
|
+
get capacity() { return this._cap; }
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* The value stored under `key`, or `undefined` if absent (never throws on a
|
|
1529
|
+
* missing / empty query). EXPECTED O(log n): a plain BST descent. Fails closed on
|
|
1530
|
+
* a non-number / non-finite key (typeof-guarded first) with a `[lite-logn]` throw.
|
|
1531
|
+
* @param {number} key a finite number
|
|
1532
|
+
* @returns {number|undefined}
|
|
1533
|
+
*/
|
|
1534
|
+
get(key) {
|
|
1535
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1536
|
+
const L = this._left, R = this._right, K = this._key;
|
|
1537
|
+
let t = this._root;
|
|
1538
|
+
while (t !== 0) {
|
|
1539
|
+
if (key < K[t]) t = L[t];
|
|
1540
|
+
else if (key > K[t]) t = R[t];
|
|
1541
|
+
else return this._value[t];
|
|
1542
|
+
}
|
|
1543
|
+
return undefined;
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
/**
|
|
1547
|
+
* True iff `key` is currently in the treap. EXPECTED O(log n). Fails closed on a
|
|
1548
|
+
* non-finite key (typeof-guarded first).
|
|
1549
|
+
* @param {number} key a finite number
|
|
1550
|
+
* @returns {boolean}
|
|
1551
|
+
*/
|
|
1552
|
+
has(key) {
|
|
1553
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1554
|
+
const L = this._left, R = this._right, K = this._key;
|
|
1555
|
+
let t = this._root;
|
|
1556
|
+
while (t !== 0) {
|
|
1557
|
+
if (key < K[t]) t = L[t];
|
|
1558
|
+
else if (key > K[t]) t = R[t];
|
|
1559
|
+
else return true;
|
|
1560
|
+
}
|
|
1561
|
+
return false;
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
/**
|
|
1565
|
+
* Insert `key -> value`, or UPDATE the value in place if `key` already exists (no
|
|
1566
|
+
* new node). EXPECTED O(log n): a descent to check membership, then (on insert) a
|
|
1567
|
+
* recursive splice that rotates the new node up until heap order is restored,
|
|
1568
|
+
* fixing `_size` on the unwind. Fails closed: a non-finite key or value
|
|
1569
|
+
* (typeof-guarded first), or a full pool, each throw `[lite-logn]` as a no-op.
|
|
1570
|
+
* @param {number} key a finite number
|
|
1571
|
+
* @param {number} value a finite number
|
|
1572
|
+
* @returns {this}
|
|
1573
|
+
*/
|
|
1574
|
+
set(key, value) {
|
|
1575
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1576
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return this._badValue(value);
|
|
1577
|
+
const L = this._left, R = this._right, K = this._key;
|
|
1578
|
+
let t = this._root;
|
|
1579
|
+
while (t !== 0) { // update in place if present -- no new node, no rebalance
|
|
1580
|
+
if (key < K[t]) t = L[t];
|
|
1581
|
+
else if (key > K[t]) t = R[t];
|
|
1582
|
+
else { this._value[t] = value; this._version = (this._version + 1) | 0; return this; }
|
|
1583
|
+
}
|
|
1584
|
+
const slot = this._pool.alloc();
|
|
1585
|
+
if (slot === 0) return this._full();
|
|
1586
|
+
K[slot] = key;
|
|
1587
|
+
this._value[slot] = value;
|
|
1588
|
+
L[slot] = 0; R[slot] = 0;
|
|
1589
|
+
this._size[slot] = 1;
|
|
1590
|
+
this._seed = _lcgNext(this._seed);
|
|
1591
|
+
this._prio[slot] = this._seed; // stored unsigned in the Uint32 column
|
|
1592
|
+
this._root = this._insert(this._root, slot);
|
|
1593
|
+
this._version = (this._version + 1) | 0;
|
|
1594
|
+
return this;
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
/**
|
|
1598
|
+
* Remove `key`. EXPECTED O(log n). Idempotent: returns `false` if `key` is absent
|
|
1599
|
+
* (no throw), `true` if it was present and removed. Deletion MERGES the removed
|
|
1600
|
+
* node's two subtrees (priority-ordered) then frees its slot, fixing `_size` on
|
|
1601
|
+
* the unwind. Fails closed on a non-finite key (typeof-guarded first).
|
|
1602
|
+
* @param {number} key a finite number
|
|
1603
|
+
* @returns {boolean}
|
|
1604
|
+
*/
|
|
1605
|
+
delete(key) {
|
|
1606
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1607
|
+
const L = this._left, R = this._right, K = this._key;
|
|
1608
|
+
let t = this._root, found = false;
|
|
1609
|
+
while (t !== 0) {
|
|
1610
|
+
if (key < K[t]) t = L[t];
|
|
1611
|
+
else if (key > K[t]) t = R[t];
|
|
1612
|
+
else { found = true; break; }
|
|
1613
|
+
}
|
|
1614
|
+
if (!found) return false; // absent (no throw)
|
|
1615
|
+
this._root = this._delete(this._root, key);
|
|
1616
|
+
this._version = (this._version + 1) | 0;
|
|
1617
|
+
return true;
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
/**
|
|
1621
|
+
* The number of stored keys STRICTLY LESS than `x` (its rank / position). EXPECTED
|
|
1622
|
+
* O(log n) via subtree counts: at each node, when the node's key is < `x`, its
|
|
1623
|
+
* whole left subtree plus itself precede `x`. `x` need not be present; `rank` of
|
|
1624
|
+
* the smallest key is 0, of a key past the max is `size`. Fails closed on a
|
|
1625
|
+
* non-finite `x`.
|
|
1626
|
+
* @param {number} x a finite number
|
|
1627
|
+
* @returns {number} count of keys < x, in [0, size]
|
|
1628
|
+
*/
|
|
1629
|
+
rank(x) {
|
|
1630
|
+
if (typeof x !== 'number' || !Number.isFinite(x)) return this._badKey(x);
|
|
1631
|
+
const L = this._left, R = this._right, K = this._key, S = this._size;
|
|
1632
|
+
let t = this._root, r = 0;
|
|
1633
|
+
while (t !== 0) {
|
|
1634
|
+
if (x <= K[t]) t = L[t]; // t (and its right) are >= x
|
|
1635
|
+
else { r += S[L[t]] + 1; t = R[t]; } // t's left subtree + t precede x
|
|
1636
|
+
}
|
|
1637
|
+
return r;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
/**
|
|
1641
|
+
* The k-th smallest KEY (0-based order statistic), or `undefined` if `k` is out of
|
|
1642
|
+
* range [0, size). EXPECTED O(log n) via subtree counts. Fails closed on a
|
|
1643
|
+
* non-integer `k` (typeof-guarded first); an in-type out-of-range `k` returns
|
|
1644
|
+
* `undefined` (matching the soft-miss of `get`).
|
|
1645
|
+
* @param {number} k integer in [0, size)
|
|
1646
|
+
* @returns {number|undefined} the k-th smallest key
|
|
1647
|
+
*/
|
|
1648
|
+
select(k) {
|
|
1649
|
+
if (typeof k !== 'number' || !Number.isInteger(k)) return this._badRank(k);
|
|
1650
|
+
if (k < 0 || k >= this.size) return undefined;
|
|
1651
|
+
const L = this._left, R = this._right, K = this._key, S = this._size;
|
|
1652
|
+
let t = this._root;
|
|
1653
|
+
for (;;) {
|
|
1654
|
+
const ls = S[L[t]];
|
|
1655
|
+
if (k < ls) t = L[t];
|
|
1656
|
+
else if (k > ls) { k -= ls + 1; t = R[t]; }
|
|
1657
|
+
else return K[t];
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/**
|
|
1662
|
+
* The smallest key STRICTLY greater than `key`, or `undefined` if none. EXPECTED
|
|
1663
|
+
* O(log n). `key` itself need not be present. Fails closed on a non-finite key.
|
|
1664
|
+
* @param {number} key a finite number
|
|
1665
|
+
* @returns {number|undefined}
|
|
1666
|
+
*/
|
|
1667
|
+
successor(key) {
|
|
1668
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1669
|
+
const L = this._left, R = this._right, K = this._key;
|
|
1670
|
+
let t = this._root, best;
|
|
1671
|
+
while (t !== 0) {
|
|
1672
|
+
if (K[t] > key) { best = K[t]; t = L[t]; }
|
|
1673
|
+
else t = R[t];
|
|
1674
|
+
}
|
|
1675
|
+
return best;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
/**
|
|
1679
|
+
* The largest key STRICTLY less than `key`, or `undefined` if none. EXPECTED
|
|
1680
|
+
* O(log n). `key` itself need not be present. Fails closed on a non-finite key.
|
|
1681
|
+
* @param {number} key a finite number
|
|
1682
|
+
* @returns {number|undefined}
|
|
1683
|
+
*/
|
|
1684
|
+
predecessor(key) {
|
|
1685
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1686
|
+
const L = this._left, R = this._right, K = this._key;
|
|
1687
|
+
let t = this._root, best;
|
|
1688
|
+
while (t !== 0) {
|
|
1689
|
+
if (K[t] < key) { best = K[t]; t = R[t]; }
|
|
1690
|
+
else t = L[t];
|
|
1691
|
+
}
|
|
1692
|
+
return best;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
/**
|
|
1696
|
+
* A VERSION-STAMPED iterator over the keys in `[lo, hi]` INCLUSIVE, ascending.
|
|
1697
|
+
* Bounds may be any number INCLUDING +-Infinity (an unbounded end); `NaN` fails
|
|
1698
|
+
* closed, as does `lo > hi`. The generator captures the treap's version and throws
|
|
1699
|
+
* `[lite-logn]` if any STRUCTURAL or VALUE mutation happens mid-iteration, rather
|
|
1700
|
+
* than yield stale data. It walks by repeated `successor` (each step a fresh
|
|
1701
|
+
* O(log n) descent, so NO scratch stack is allocated); the one documented per-
|
|
1702
|
+
* protocol allocator is the {value, done} per step.
|
|
1703
|
+
* @param {number} lo lower bound (inclusive); may be -Infinity
|
|
1704
|
+
* @param {number} hi upper bound (inclusive); may be +Infinity
|
|
1705
|
+
* @returns {IterableIterator<number>} the keys in [lo, hi], ascending
|
|
1706
|
+
*/
|
|
1707
|
+
rangeIter(lo, hi) {
|
|
1708
|
+
if (typeof lo !== 'number' || Number.isNaN(lo)) return this._badBound(lo);
|
|
1709
|
+
if (typeof hi !== 'number' || Number.isNaN(hi)) return this._badBound(hi);
|
|
1710
|
+
if (lo > hi) return this._badRange(lo, hi);
|
|
1711
|
+
return this._rangeGen(lo, hi);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/** @private version-stamped range generator (see rangeIter). */
|
|
1715
|
+
*_rangeGen(lo, hi) {
|
|
1716
|
+
const ver = this._version;
|
|
1717
|
+
let cur = this._ceil(lo); // smallest key >= lo, or undefined
|
|
1718
|
+
while (cur !== undefined && cur <= hi) {
|
|
1719
|
+
if (this._version !== ver) {
|
|
1720
|
+
throw new Error('[lite-logn] Treap mutated during iteration');
|
|
1721
|
+
}
|
|
1722
|
+
yield cur;
|
|
1723
|
+
cur = this.successor(cur);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
/**
|
|
1728
|
+
* Visit every live `(key, value)` pair in ASCENDING key order. O(n) cold in-order
|
|
1729
|
+
* walk (recursion depth = tree height), allocation-free in the loop body (pass a
|
|
1730
|
+
* hoisted callback). Unlike rangeIter this is NOT version-stamped -- mutating from
|
|
1731
|
+
* within the callback is the caller's responsibility (matching the other members).
|
|
1732
|
+
* @param {(key:number, value:number, treap:Treap)=>void} fn
|
|
1733
|
+
*/
|
|
1734
|
+
forEach(fn) {
|
|
1735
|
+
this._forEach(this._root, fn);
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
/** @private recursive in-order walk. */
|
|
1739
|
+
_forEach(t, fn) {
|
|
1740
|
+
if (t === 0) return;
|
|
1741
|
+
this._forEach(this._left[t], fn);
|
|
1742
|
+
fn(this._key[t], this._value[t], this);
|
|
1743
|
+
this._forEach(this._right[t], fn);
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/**
|
|
1747
|
+
* Empty the treap, keeping the fixed capacity. O(capacity) cold path: returns
|
|
1748
|
+
* every node to the pool, points the root at NIL, and restores the PRNG to its
|
|
1749
|
+
* initial seed (a cleared treap replays a fresh one). @returns {this}
|
|
1750
|
+
*/
|
|
1751
|
+
clear() {
|
|
1752
|
+
this._pool.clear();
|
|
1753
|
+
this._root = 0;
|
|
1754
|
+
this._seed = this._seed0;
|
|
1755
|
+
this._version = (this._version + 1) | 0;
|
|
1756
|
+
return this;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
/**
|
|
1760
|
+
* SPLIT `this` at `key` into two treaps: `[left, right]` where `left` holds every
|
|
1761
|
+
* key STRICTLY LESS than `key` and `right` holds every key >= `key`. EXPECTED
|
|
1762
|
+
* O(log n) -- it REWIRES nodes in place (no copy), so the returned treaps SHARE
|
|
1763
|
+
* this treap's backing arena, and `this` is CONSUMED (left empty). Fails closed on
|
|
1764
|
+
* a non-finite key.
|
|
1765
|
+
* @param {number} key a finite number
|
|
1766
|
+
* @returns {[Treap, Treap]} [keys < key, keys >= key]
|
|
1767
|
+
*/
|
|
1768
|
+
split(key) {
|
|
1769
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1770
|
+
const l = this._split(this._root, key);
|
|
1771
|
+
const r = this._sr;
|
|
1772
|
+
const left = Treap._view(this, l);
|
|
1773
|
+
const right = Treap._view(this, r);
|
|
1774
|
+
this._root = 0; // consumed: its nodes now belong to left / right
|
|
1775
|
+
this._version = (this._version + 1) | 0;
|
|
1776
|
+
return [left, right];
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* MERGE two treaps `a` and `b` -- where EVERY key of `a` is STRICTLY LESS than
|
|
1781
|
+
* every key of `b` -- into one, returning it. EXPECTED O(log n): it rewires nodes
|
|
1782
|
+
* in place, so `a` and `b` MUST share a backing arena (i.e. both came from a prior
|
|
1783
|
+
* `split`), and BOTH are CONSUMED. Fails closed: non-Treap inputs, treaps from
|
|
1784
|
+
* different arenas, or an overlapping key range each throw `[lite-logn]`.
|
|
1785
|
+
* @param {Treap} a all keys strictly less than every key of b
|
|
1786
|
+
* @param {Treap} b all keys strictly greater than every key of a
|
|
1787
|
+
* @returns {Treap}
|
|
1788
|
+
*/
|
|
1789
|
+
static merge(a, b) {
|
|
1790
|
+
if (!(a instanceof Treap) || !(b instanceof Treap)) {
|
|
1791
|
+
throw new TypeError('[lite-logn] Treap.merge needs two Treap instances');
|
|
1792
|
+
}
|
|
1793
|
+
if (a._key !== b._key) {
|
|
1794
|
+
throw new Error(
|
|
1795
|
+
'[lite-logn] Treap.merge requires two treaps sharing an arena (from the same split)');
|
|
1796
|
+
}
|
|
1797
|
+
if (a._root !== 0 && b._root !== 0) {
|
|
1798
|
+
let m = a._root; while (a._right[m] !== 0) m = a._right[m]; // max key of a
|
|
1799
|
+
let n = b._root; while (b._left[n] !== 0) n = b._left[n]; // min key of b
|
|
1800
|
+
if (a._key[m] >= b._key[n]) {
|
|
1801
|
+
throw new Error(
|
|
1802
|
+
'[lite-logn] Treap.merge requires all keys of a < all keys of b');
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
const root = a._merge(a._root, b._root);
|
|
1806
|
+
const out = Treap._view(a, root);
|
|
1807
|
+
a._root = 0; b._root = 0; // both consumed
|
|
1808
|
+
a._version = (a._version + 1) | 0;
|
|
1809
|
+
b._version = (b._version + 1) | 0;
|
|
1810
|
+
return out;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
// ---- private rotations + recursive structure (hot bodies) ---------------
|
|
1814
|
+
|
|
1815
|
+
/**
|
|
1816
|
+
* @private true iff node `a` outranks node `b` in the priority MAX-heap. Priority
|
|
1817
|
+
* ties (astronomically rare across 32-bit LCG draws) break by key, so the tree
|
|
1818
|
+
* shape is a deterministic function of the (key, priority) set -- never ambiguous.
|
|
1819
|
+
*/
|
|
1820
|
+
_higher(a, b) {
|
|
1821
|
+
const pa = this._prio[a], pb = this._prio[b];
|
|
1822
|
+
return pa > pb || (pa === pb && this._key[a] < this._key[b]);
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
/**
|
|
1826
|
+
* @private right rotation: `y`'s left child `x` becomes the subtree root. Rewrites
|
|
1827
|
+
* two child links and recomputes the two affected `_size` cells. Returns `x`.
|
|
1828
|
+
*/
|
|
1829
|
+
_rotR(y) {
|
|
1830
|
+
const L = this._left, R = this._right, S = this._size;
|
|
1831
|
+
const x = L[y];
|
|
1832
|
+
L[y] = R[x];
|
|
1833
|
+
R[x] = y;
|
|
1834
|
+
S[y] = S[L[y]] + S[R[y]] + 1;
|
|
1835
|
+
S[x] = S[L[x]] + S[R[x]] + 1;
|
|
1836
|
+
return x;
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
/**
|
|
1840
|
+
* @private left rotation: `y`'s right child `x` becomes the subtree root. Rewrites
|
|
1841
|
+
* two child links and recomputes the two affected `_size` cells. Returns `x`.
|
|
1842
|
+
*/
|
|
1843
|
+
_rotL(y) {
|
|
1844
|
+
const L = this._left, R = this._right, S = this._size;
|
|
1845
|
+
const x = R[y];
|
|
1846
|
+
R[y] = L[x];
|
|
1847
|
+
L[x] = y;
|
|
1848
|
+
S[y] = S[L[y]] + S[R[y]] + 1;
|
|
1849
|
+
S[x] = S[L[x]] + S[R[x]] + 1;
|
|
1850
|
+
return x;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
/** @private recursive BST insert of leaf slot `s`, rotating up to fix heap order. */
|
|
1854
|
+
_insert(t, s) {
|
|
1855
|
+
if (t === 0) return s; // s already has size 1, NIL children, its priority set
|
|
1856
|
+
const L = this._left, R = this._right, S = this._size, K = this._key;
|
|
1857
|
+
if (K[s] < K[t]) {
|
|
1858
|
+
L[t] = this._insert(L[t], s);
|
|
1859
|
+
S[t] = S[L[t]] + S[R[t]] + 1;
|
|
1860
|
+
if (this._higher(L[t], t)) return this._rotR(t);
|
|
1861
|
+
} else {
|
|
1862
|
+
R[t] = this._insert(R[t], s);
|
|
1863
|
+
S[t] = S[L[t]] + S[R[t]] + 1;
|
|
1864
|
+
if (this._higher(R[t], t)) return this._rotL(t);
|
|
1865
|
+
}
|
|
1866
|
+
return t;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
/** @private recursive delete of `key` from subtree `t`; frees the removed slot. */
|
|
1870
|
+
_delete(t, key) {
|
|
1871
|
+
const L = this._left, R = this._right, S = this._size, K = this._key;
|
|
1872
|
+
if (key < K[t]) {
|
|
1873
|
+
L[t] = this._delete(L[t], key);
|
|
1874
|
+
S[t] = S[L[t]] + S[R[t]] + 1;
|
|
1875
|
+
return t;
|
|
1876
|
+
}
|
|
1877
|
+
if (key > K[t]) {
|
|
1878
|
+
R[t] = this._delete(R[t], key);
|
|
1879
|
+
S[t] = S[L[t]] + S[R[t]] + 1;
|
|
1880
|
+
return t;
|
|
1881
|
+
}
|
|
1882
|
+
const merged = this._merge(L[t], R[t]); // t removed: fuse its two subtrees
|
|
1883
|
+
this._pool.free(t);
|
|
1884
|
+
return merged;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
/** @private recursive priority merge of two subtrees (all keys in a < all in b). */
|
|
1888
|
+
_merge(a, b) {
|
|
1889
|
+
if (a === 0) return b;
|
|
1890
|
+
if (b === 0) return a;
|
|
1891
|
+
const L = this._left, R = this._right, S = this._size;
|
|
1892
|
+
if (this._higher(a, b)) {
|
|
1893
|
+
R[a] = this._merge(R[a], b);
|
|
1894
|
+
S[a] = S[L[a]] + S[R[a]] + 1;
|
|
1895
|
+
return a;
|
|
1896
|
+
}
|
|
1897
|
+
L[b] = this._merge(a, L[b]);
|
|
1898
|
+
S[b] = S[L[b]] + S[R[b]] + 1;
|
|
1899
|
+
return b;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
/**
|
|
1903
|
+
* @private recursive split of subtree `t` by `key`. Returns the LEFT root (keys <
|
|
1904
|
+
* key); the RIGHT root (keys >= key) is left in `this._sr` (read by the caller
|
|
1905
|
+
* immediately, before any sibling recursion, so a single scratch field suffices).
|
|
1906
|
+
*/
|
|
1907
|
+
_split(t, key) {
|
|
1908
|
+
if (t === 0) { this._sr = 0; return 0; }
|
|
1909
|
+
const L = this._left, R = this._right, K = this._key, S = this._size;
|
|
1910
|
+
if (K[t] < key) {
|
|
1911
|
+
const l1 = this._split(R[t], key); // this._sr := the right part
|
|
1912
|
+
R[t] = l1;
|
|
1913
|
+
S[t] = S[L[t]] + S[R[t]] + 1;
|
|
1914
|
+
return t; // pair (t, this._sr)
|
|
1915
|
+
}
|
|
1916
|
+
const l1 = this._split(L[t], key); // this._sr := R1 ; l1 := L1
|
|
1917
|
+
L[t] = this._sr;
|
|
1918
|
+
S[t] = S[L[t]] + S[R[t]] + 1;
|
|
1919
|
+
this._sr = t;
|
|
1920
|
+
return l1; // pair (l1, t)
|
|
1921
|
+
}
|
|
1922
|
+
|
|
1923
|
+
/** @private smallest key >= `lo`, or undefined (the range-iter start). */
|
|
1924
|
+
_ceil(lo) {
|
|
1925
|
+
const L = this._left, R = this._right, K = this._key;
|
|
1926
|
+
let t = this._root, best;
|
|
1927
|
+
while (t !== 0) {
|
|
1928
|
+
if (K[t] >= lo) { best = K[t]; t = L[t]; }
|
|
1929
|
+
else t = R[t];
|
|
1930
|
+
}
|
|
1931
|
+
return best;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
/** @private build a Treap VIEW sharing `src`'s arena with a given root (split/merge). */
|
|
1935
|
+
static _view(src, root) {
|
|
1936
|
+
const t = Object.create(Treap.prototype);
|
|
1937
|
+
t._cap = src._cap;
|
|
1938
|
+
t._key = src._key; t._value = src._value;
|
|
1939
|
+
t._left = src._left; t._right = src._right;
|
|
1940
|
+
t._prio = src._prio; t._size = src._size;
|
|
1941
|
+
t._pool = src._pool;
|
|
1942
|
+
t._root = root;
|
|
1943
|
+
t._seed0 = src._seed0; t._seed = src._seed;
|
|
1944
|
+
t._version = 0; t._sr = 0;
|
|
1945
|
+
return t;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
// ---- cold path only: throw builders (string concat off the hot body) ----
|
|
1949
|
+
|
|
1950
|
+
/** @private */
|
|
1951
|
+
_badKey(key) {
|
|
1952
|
+
throw new TypeError(
|
|
1953
|
+
'[lite-logn] Treap key must be a finite number, got ' + String(key));
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
/** @private */
|
|
1957
|
+
_badValue(value) {
|
|
1958
|
+
throw new TypeError(
|
|
1959
|
+
'[lite-logn] Treap value must be a finite number, got ' + String(value));
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
/** @private */
|
|
1963
|
+
_badRank(k) {
|
|
1964
|
+
throw new TypeError(
|
|
1965
|
+
'[lite-logn] Treap select index must be an integer, got ' + String(k));
|
|
1966
|
+
}
|
|
1967
|
+
|
|
1968
|
+
/** @private */
|
|
1969
|
+
_badBound(b) {
|
|
1970
|
+
throw new TypeError(
|
|
1971
|
+
'[lite-logn] Treap rangeIter bound must be a number (not NaN), got ' + String(b));
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
/** @private */
|
|
1975
|
+
_badRange(lo, hi) {
|
|
1976
|
+
throw new RangeError(
|
|
1977
|
+
'[lite-logn] Treap rangeIter needs lo <= hi, got lo=' + String(lo) +
|
|
1978
|
+
' hi=' + String(hi));
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
/** @private */
|
|
1982
|
+
_full() {
|
|
1983
|
+
throw new RangeError('[lite-logn] Treap full (capacity ' + this._cap + ')');
|
|
1984
|
+
}
|
|
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
|
+
}
|