@zakkster/lite-logn 0.3.0 → 0.5.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 +127 -1
- package/LogN.d.ts +96 -0
- package/LogN.js +1062 -11
- package/README.md +169 -9
- package/llms.txt +83 -6
- package/package.json +7 -3
package/LogN.js
CHANGED
|
@@ -12,15 +12,19 @@
|
|
|
12
12
|
* the lowest-set-bit walk (`i & -i`). v0.3.0 adds the THIRD member: SegmentTree,
|
|
13
13
|
* a flat `Float64Array(2n)` (leaves at n..2n-1) whose range-query AND point-update
|
|
14
14
|
* are BOTH O(log n) via iterative bottom-up walks, with the associative fold
|
|
15
|
-
* (min / max / sum / gcd) chosen ONCE at construction.
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
15
|
+
* (min / max / sum / gcd) chosen ONCE at construction. v0.4.0 adds the FOURTH
|
|
16
|
+
* member: SkipList, a pointer-free ordered map (get / set / delete / successor /
|
|
17
|
+
* predecessor / rangeIter) whose links are slot INDICES in flat `Uint32Array`
|
|
18
|
+
* columns over a private free-list (NodePool), giving EXPECTED O(log n) with zero
|
|
19
|
+
* per-op allocation and a deterministic instance-local PRNG. Members land
|
|
20
|
+
* append-only, leaving this header and the `VERSION` const the only prior lines
|
|
21
|
+
* that ever change. Roster: BinaryHeap (v0.1.0, array-embedded O(log n) push / pop
|
|
22
|
+
* min|max heap), Fenwick / BIT (v0.2.0, O(log n) point-update AND prefix-sum via
|
|
23
|
+
* the `i & -i` walk), SegmentTree (v0.3.0, O(log n) associative range-query +
|
|
24
|
+
* point-update over a flat 2n array, fold chosen at construction), and SkipList
|
|
25
|
+
* (v0.4.0, pointer-free expected-O(log n) ordered map over a private free-list
|
|
26
|
+
* node pool). Members are independent (no shared mutable module state), so a
|
|
27
|
+
* bundler that imports one drops the others (`sideEffects: false`).
|
|
24
28
|
*
|
|
25
29
|
* The family delta: lite-o1 proves a FLAT ops/ms line on a log-x axis (the
|
|
26
30
|
* constant, slope ~ 0); lite-logn proves a STRAIGHT line on that same axis (one
|
|
@@ -35,13 +39,13 @@
|
|
|
35
39
|
*/
|
|
36
40
|
|
|
37
41
|
/** Package version. One of the three version sites (package.json / VERSION / llms.txt). */
|
|
38
|
-
export const VERSION = '0.
|
|
42
|
+
export const VERSION = '0.5.0';
|
|
39
43
|
|
|
40
44
|
// --- members land here, append-only, one tree-shakeable class each -----------
|
|
41
45
|
// BinaryHeap (v0.1.0 session) -- indexed O(log n) min|max heap (BELOW)
|
|
42
46
|
// Fenwick (v0.2.0 session) -- O(log n) point-update + prefix-sum (BELOW)
|
|
43
47
|
// SegmentTree (v0.3.0 session) -- O(log n) associative range-query + point-update (BELOW)
|
|
44
|
-
// SkipList (v0.4.0)
|
|
48
|
+
// SkipList (v0.4.0 session) -- pointer-free expected-O(log n) ordered map (BELOW)
|
|
45
49
|
|
|
46
50
|
/** Max heap capacity: slot indices 0..cap-1 must fit the Int32Array _pos map. */
|
|
47
51
|
const BH_MAX_CAPACITY = 0x7FFFFFFF; // 2^31 - 1
|
|
@@ -932,3 +936,1050 @@ export class SegmentTree {
|
|
|
932
936
|
'[lite-logn] SegmentTree gcd value must be a nonnegative integer, got ' + String(value));
|
|
933
937
|
}
|
|
934
938
|
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Advance a 32-bit Numerical-Recipes LCG one step: `s' = (s*1664525 + 1013904223)
|
|
942
|
+
* mod 2^32`, kept as a SIGNED int32. The repo's single PRNG -- deterministic,
|
|
943
|
+
* instance-local, no Math.random, no module state, no new generator. Two integer
|
|
944
|
+
* disciplines keep it zero-alloc:
|
|
945
|
+
* - `Math.imul` does the multiply as a 32-bit integer op (the low 32 bits of the
|
|
946
|
+
* product), so no large intermediate double is ever formed (`s * 1664525` would
|
|
947
|
+
* reach ~7.1e15); and
|
|
948
|
+
* - the result is folded with `| 0` (a SIGNED int32), NOT `>>> 0`: a `>>> 0`
|
|
949
|
+
* Uint32 exceeds the Smi range (2^31) about half the time and would box as a
|
|
950
|
+
* transient HeapNumber every step (the perf-gate scavenge counter catches it),
|
|
951
|
+
* whereas an `| 0` signed int32 always stays an unboxed Smi.
|
|
952
|
+
* The 32-BIT WORD is identical either way, so the sequence is unchanged: `Math.imul`
|
|
953
|
+
* gives the same low 32 bits as the plain multiply, and mod-2^32 addition is
|
|
954
|
+
* sign-agnostic. SkipList draws a node level from the HIGH bits via `Math.clz32`,
|
|
955
|
+
* which does ToUint32 internally -- so the signed int32 and its Uint32 twin yield
|
|
956
|
+
* the IDENTICAL level. The LOW bits of any power-of-two-modulus LCG are periodic
|
|
957
|
+
* (here the lowest bit strictly alternates, since a is odd and c is odd), so
|
|
958
|
+
* counting halvings from the low end would be non-random -- the high bits are the
|
|
959
|
+
* well-mixed ones.
|
|
960
|
+
* @param {number} s current state, a signed 32-bit integer
|
|
961
|
+
* @returns {number} the next state, a signed 32-bit integer (same 32-bit word)
|
|
962
|
+
*/
|
|
963
|
+
function _lcgNext(s) {
|
|
964
|
+
return (Math.imul(s, 1664525) + 1013904223) | 0;
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
/** SkipList tower-height ceiling: at most this many parallel `_next` columns. */
|
|
968
|
+
const SL_MAXLEVEL = 32;
|
|
969
|
+
|
|
970
|
+
/**
|
|
971
|
+
* Column count actually allocated for a `capacity`-slot SkipList: `ceil(log2 cap)`
|
|
972
|
+
* plus one column of headroom for the geometric tail, clamped to SL_MAXLEVEL.
|
|
973
|
+
* Fixed at construction so `_next` NEVER reallocates -- the memory risk (a full
|
|
974
|
+
* cap * 32 column set) is avoided by sizing to the capacity's real need, and level
|
|
975
|
+
* generation clamps to it, so there is no lazy grow to threaten the 0-B/op gate
|
|
976
|
+
* (decisions/0006-skiplist.md). The clamp only ever bites the extreme geometric
|
|
977
|
+
* tail (probability ~ 2^-log2(cap)), which cannot change ordering or membership --
|
|
978
|
+
* a node merely stops gaining express lanes above the ceiling.
|
|
979
|
+
* @param {number} capacity data-slot count
|
|
980
|
+
* @returns {number} column count in [1, SL_MAXLEVEL]
|
|
981
|
+
*/
|
|
982
|
+
function _levelCap(capacity) {
|
|
983
|
+
let l = 1;
|
|
984
|
+
while ((1 << l) < capacity && l < SL_MAXLEVEL) l++;
|
|
985
|
+
l += 1; // one column of headroom above ceil(log2 cap)
|
|
986
|
+
return l > SL_MAXLEVEL ? SL_MAXLEVEL : l;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** SkipList default PRNG seed when the caller does not supply one. */
|
|
990
|
+
const SL_DEFAULT_SEED = 0x9E3779B9;
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* A private, pointer-free slot allocator: a free-list (a LIFO free-stack over a
|
|
994
|
+
* `Uint32Array`) that hands out a slot INDEX in [1, capacity] rather than a heap
|
|
995
|
+
* object, so nothing is collected per insert. `NIL = 0`; slot 0 is RESERVED (the
|
|
996
|
+
* SkipList head sentinel) and is never allocatable. `alloc()` returns 0 when the
|
|
997
|
+
* pool is exhausted so the caller can fail closed. The conservation invariant
|
|
998
|
+
* `activeSlots + freeListLength === capacity` holds after every operation.
|
|
999
|
+
*
|
|
1000
|
+
* D-01 bind (decisions/0006-skiplist.md): this is DESIGN-PARITY with
|
|
1001
|
+
* `@zakkster/lite-o1`'s private pools (FreqO1 / BucketQueue / TimerWheel) and its
|
|
1002
|
+
* deferred `SlotPool` -- the identical free-list contract (allocate an index,
|
|
1003
|
+
* `NIL = 0`, slot 0 reserved) and the same conservation invariant -- NOT shared
|
|
1004
|
+
* code. A runtime dependency on lite-o1 was REJECTED: the suite's zero-runtime-deps
|
|
1005
|
+
* law forbids it, and lite-o1's `SlotPool` was never made public. Shaped so a later
|
|
1006
|
+
* pointer member (Treap) can reuse it, without over-engineering it now.
|
|
1007
|
+
*/
|
|
1008
|
+
class NodePool {
|
|
1009
|
+
/** @param {number} capacity allocatable data-slot count (excludes slot 0). */
|
|
1010
|
+
constructor(capacity) {
|
|
1011
|
+
this._cap = capacity;
|
|
1012
|
+
this._free = new Uint32Array(capacity); // free-stack of slot indices
|
|
1013
|
+
this._freeLen = capacity;
|
|
1014
|
+
for (let i = 0; i < capacity; i++) this._free[i] = capacity - i; // top = slot 1
|
|
1015
|
+
this._active = 0;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/** Allocatable data-slot count (excludes the reserved slot 0). O(1). */
|
|
1019
|
+
get capacity() { return this._cap; }
|
|
1020
|
+
/** Slots currently handed out and not yet freed. O(1). */
|
|
1021
|
+
get activeSlots() { return this._active; }
|
|
1022
|
+
/** Slots currently on the free-stack. O(1). */
|
|
1023
|
+
get freeListLength() { return this._freeLen; }
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Hand out a free slot INDEX in [1, capacity], or 0 (NIL) if exhausted. O(1),
|
|
1027
|
+
* zero allocation.
|
|
1028
|
+
* @returns {number}
|
|
1029
|
+
*/
|
|
1030
|
+
alloc() {
|
|
1031
|
+
const n = this._freeLen;
|
|
1032
|
+
if (n === 0) return 0; // NIL: exhausted -> caller fails closed
|
|
1033
|
+
const slot = this._free[n - 1];
|
|
1034
|
+
this._freeLen = n - 1;
|
|
1035
|
+
this._active++;
|
|
1036
|
+
return slot;
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* Return a slot INDEX to the free-stack. O(1), zero allocation. The caller owns
|
|
1041
|
+
* correctness: a slot must be live and freed at most once (the SkipList only
|
|
1042
|
+
* frees a node it just unlinked).
|
|
1043
|
+
* @param {number} slot a slot previously returned by alloc()
|
|
1044
|
+
*/
|
|
1045
|
+
free(slot) {
|
|
1046
|
+
const n = this._freeLen;
|
|
1047
|
+
this._free[n] = slot;
|
|
1048
|
+
this._freeLen = n + 1;
|
|
1049
|
+
this._active--;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/** Reset to all-free (O(capacity) cold path), restoring the conservation invariant. */
|
|
1053
|
+
clear() {
|
|
1054
|
+
const cap = this._cap, free = this._free;
|
|
1055
|
+
for (let i = 0; i < cap; i++) free[i] = cap - i;
|
|
1056
|
+
this._freeLen = cap;
|
|
1057
|
+
this._active = 0;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* Max SkipList capacity: `0x03FFFFFF` (2^26 - 1). Every node is addressed by a slot
|
|
1063
|
+
* INDEX stored in `Uint32Array` link columns, so an index must fit an unsigned
|
|
1064
|
+
* 32-bit word; `NIL = 0` reserves slot 0 as the head sentinel, so live slots run
|
|
1065
|
+
* [1, capacity]. The backing `_next` is a SINGLE flat `Uint32Array` of
|
|
1066
|
+
* `columns * (capacity + 1)` cells, stride-indexed `lvl*(capacity + 1) + slot`; the
|
|
1067
|
+
* 2^26 ceiling keeps `columns * (capacity + 1)` an addressable typed-array length
|
|
1068
|
+
* (at most ~27 columns for the largest capacity) and every stride offset an exact
|
|
1069
|
+
* integer. The index arithmetic (Uint32 slot indices + `NIL = 0` + the MAXLEVEL
|
|
1070
|
+
* column width), not the byte count, is the hard ceiling -- the same "the
|
|
1071
|
+
* arithmetic caps it" reasoning as the array-embedded members, one column-set wider.
|
|
1072
|
+
*/
|
|
1073
|
+
const SL_MAX_CAPACITY = 0x03FFFFFF; // 2^26 - 1
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* A SKIP LIST: a pointer-free ordered map (key -> value) whose get / set / delete /
|
|
1077
|
+
* successor / predecessor are EXPECTED O(log n) via a probabilistic tower of
|
|
1078
|
+
* forward links -- the family's first randomized member and its first pointer-based
|
|
1079
|
+
* one. Where BinaryHeap / Fenwick / SegmentTree embed a FIXED-shape tree in index
|
|
1080
|
+
* arithmetic, a skip list's shape is random, so it needs real per-node links; the
|
|
1081
|
+
* trick that keeps it zero-GC is storing those links as slot INDICES in flat
|
|
1082
|
+
* `Uint32Array` columns over a private free-list (NodePool), never as heap objects.
|
|
1083
|
+
*
|
|
1084
|
+
* Storage (allocated once, sized to capacity):
|
|
1085
|
+
* - `_key` / `_val` `Float64Array(capacity + 1)` -- key and value at each slot.
|
|
1086
|
+
* - `_next` a SINGLE flat `Uint32Array(columns * (capacity + 1))`, stride-indexed
|
|
1087
|
+
* `lvl*(capacity + 1) + slot`: the forward link of `slot` at level `lvl`, or
|
|
1088
|
+
* `NIL = 0` for end-of-list. Slot 0 is the HEAD sentinel (its links are the
|
|
1089
|
+
* first node at each level); no real node's link ever points AT the head, so a
|
|
1090
|
+
* link value of 0 unambiguously means NIL.
|
|
1091
|
+
* - `_pool` NodePool -- the free-list handing out slot indices [1, capacity].
|
|
1092
|
+
* - `_update` `Uint32Array(columns)` -- reused predecessor scratch for the ONE
|
|
1093
|
+
* structural descent (`_find`); preallocated so set / delete allocate nothing.
|
|
1094
|
+
*
|
|
1095
|
+
* Level generation is one LCG step (the repo's NR generator) whose HIGH bits pick a
|
|
1096
|
+
* geometric height: `level = 1 + clz32(word)`, clamped to the allocated column
|
|
1097
|
+
* count (the low bits of a power-of-two LCG are periodic -- see `_lcgNext`). The
|
|
1098
|
+
* seed is instance-local, so a fixed seed replays an IDENTICAL structure and a
|
|
1099
|
+
* different seed diverges -- deterministic, never Math.random.
|
|
1100
|
+
*
|
|
1101
|
+
* Honesty (randomized member): a hot op is EXPECTED O(log n), not worst-case. An
|
|
1102
|
+
* unlucky seed can build a tall thin tower and spike a single op; the witness prints
|
|
1103
|
+
* that MAX single-op alongside the fitted line so the expectation never masquerades
|
|
1104
|
+
* as a worst-case guarantee (decisions/0006-skiplist.md).
|
|
1105
|
+
*
|
|
1106
|
+
* Keys are FINITE numbers (typeof-guarded BEFORE coercion -- Symbol / BigInt / NaN /
|
|
1107
|
+
* +-Infinity fail closed with a `[lite-logn]` throw); values are Float64 (zero-GC).
|
|
1108
|
+
* `set` on an EXISTING key updates its value in place (no new node). An empty or
|
|
1109
|
+
* missing query returns `undefined` (never throws). Fixed capacity: a full pool
|
|
1110
|
+
* throws, never silently drops. `rangeIter` is a VERSION-STAMPED iterator -- any
|
|
1111
|
+
* structural mutation mid-iteration throws `[lite-logn]` rather than yield garbage.
|
|
1112
|
+
*/
|
|
1113
|
+
export class SkipList {
|
|
1114
|
+
/**
|
|
1115
|
+
* @param {number} capacity exact max live entries; integer in [1, 2^26-1].
|
|
1116
|
+
* @param {number} [seed] PRNG seed; unsigned 32-bit integer (default fixed).
|
|
1117
|
+
*/
|
|
1118
|
+
constructor(capacity, seed) {
|
|
1119
|
+
// typeof guard BEFORE coercion (Number.isInteger is Symbol/BigInt-safe).
|
|
1120
|
+
if (typeof capacity !== 'number' || !Number.isInteger(capacity) ||
|
|
1121
|
+
capacity < 1 || capacity > SL_MAX_CAPACITY) {
|
|
1122
|
+
throw new RangeError(
|
|
1123
|
+
'[lite-logn] SkipList capacity must be an integer in [1, 2^26-1], got ' +
|
|
1124
|
+
String(capacity));
|
|
1125
|
+
}
|
|
1126
|
+
let s;
|
|
1127
|
+
if (seed === undefined) {
|
|
1128
|
+
s = SL_DEFAULT_SEED;
|
|
1129
|
+
} else if (typeof seed !== 'number' || !Number.isInteger(seed) ||
|
|
1130
|
+
seed < 0 || seed > 0xFFFFFFFF) {
|
|
1131
|
+
throw new RangeError(
|
|
1132
|
+
'[lite-logn] SkipList seed must be an unsigned 32-bit integer, got ' +
|
|
1133
|
+
String(seed));
|
|
1134
|
+
} else {
|
|
1135
|
+
s = seed >>> 0;
|
|
1136
|
+
}
|
|
1137
|
+
s = s | 0; // store the LCG state as a SIGNED int32 (an unboxed Smi) -- see _lcgNext
|
|
1138
|
+
const cols = _levelCap(capacity);
|
|
1139
|
+
this._cap = capacity; // max live entries
|
|
1140
|
+
this._stride = capacity + 1; // slots 0..capacity (0 = head)
|
|
1141
|
+
this._maxLevel = cols; // allocated column count
|
|
1142
|
+
this._key = new Float64Array(capacity + 1); // key at each slot
|
|
1143
|
+
this._val = new Float64Array(capacity + 1); // value at each slot
|
|
1144
|
+
this._next = new Uint32Array(cols * (capacity + 1)); // links; all NIL (0)
|
|
1145
|
+
this._update = new Uint32Array(cols); // reused predecessor scratch
|
|
1146
|
+
this._pool = new NodePool(capacity); // free-list over slots [1, capacity]
|
|
1147
|
+
this._level = 1; // current live tower height
|
|
1148
|
+
this._size = 0; // live entries
|
|
1149
|
+
this._version = 0; // iterator invalidation stamp
|
|
1150
|
+
this._seed0 = s; // initial seed (clear resets to it)
|
|
1151
|
+
this._seed = s; // live LCG state
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/** Live entry count. O(1). */
|
|
1155
|
+
get size() { return this._size; }
|
|
1156
|
+
|
|
1157
|
+
/** The fixed capacity this list was sized for. O(1). */
|
|
1158
|
+
get capacity() { return this._cap; }
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* The value stored under `key`, or `undefined` if absent (never throws on a
|
|
1162
|
+
* missing / empty query). EXPECTED O(log n): a top-down descent that at each
|
|
1163
|
+
* level advances while the next key is strictly less than `key`. Fails closed:
|
|
1164
|
+
* a non-number / non-finite key (typeof-guarded first) throws `[lite-logn]`.
|
|
1165
|
+
* @param {number} key a finite number
|
|
1166
|
+
* @returns {number|undefined}
|
|
1167
|
+
*/
|
|
1168
|
+
get(key) {
|
|
1169
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1170
|
+
const next = this._next, K = this._key, stride = this._stride;
|
|
1171
|
+
let slot = 0; // head
|
|
1172
|
+
for (let lvl = this._level - 1; lvl >= 0; lvl--) {
|
|
1173
|
+
const base = lvl * stride;
|
|
1174
|
+
let nx = next[base + slot];
|
|
1175
|
+
while (nx !== 0 && K[nx] < key) { slot = nx; nx = next[base + slot]; }
|
|
1176
|
+
}
|
|
1177
|
+
const cand = next[slot]; // level-0 next (base 0)
|
|
1178
|
+
return (cand !== 0 && K[cand] === key) ? this._val[cand] : undefined;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
/**
|
|
1182
|
+
* Insert `key -> value`, or UPDATE the value in place if `key` already exists
|
|
1183
|
+
* (no new node). EXPECTED O(log n). Fails closed: a non-finite key or value
|
|
1184
|
+
* (typeof-guarded first), or a full pool, each throw `[lite-logn]` as a no-op.
|
|
1185
|
+
* @param {number} key a finite number
|
|
1186
|
+
* @param {number} value a finite number
|
|
1187
|
+
* @returns {this}
|
|
1188
|
+
*/
|
|
1189
|
+
set(key, value) {
|
|
1190
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1191
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) return this._badValue(value);
|
|
1192
|
+
const cand = this._find(key); // fills _update with per-level predecessors
|
|
1193
|
+
const K = this._key;
|
|
1194
|
+
if (cand !== 0 && K[cand] === key) { // existing key: update value in place
|
|
1195
|
+
this._val[cand] = value;
|
|
1196
|
+
this._version = (this._version + 1) | 0;
|
|
1197
|
+
return this;
|
|
1198
|
+
}
|
|
1199
|
+
const slot = this._pool.alloc();
|
|
1200
|
+
if (slot === 0) return this._full();
|
|
1201
|
+
// One LCG step; HIGH bits pick a geometric height, clamped to the columns.
|
|
1202
|
+
const word = this._seed = _lcgNext(this._seed);
|
|
1203
|
+
let nl = 1 + Math.clz32(word);
|
|
1204
|
+
if (nl > this._maxLevel) nl = this._maxLevel;
|
|
1205
|
+
const upd = this._update, next = this._next, stride = this._stride;
|
|
1206
|
+
if (nl > this._level) {
|
|
1207
|
+
for (let lvl = this._level; lvl < nl; lvl++) upd[lvl] = 0; // head is predecessor
|
|
1208
|
+
this._level = nl;
|
|
1209
|
+
}
|
|
1210
|
+
K[slot] = key;
|
|
1211
|
+
this._val[slot] = value;
|
|
1212
|
+
for (let lvl = 0; lvl < nl; lvl++) {
|
|
1213
|
+
const base = lvl * stride;
|
|
1214
|
+
const p = upd[lvl];
|
|
1215
|
+
next[base + slot] = next[base + p]; // splice slot after predecessor p
|
|
1216
|
+
next[base + p] = slot;
|
|
1217
|
+
}
|
|
1218
|
+
this._size++;
|
|
1219
|
+
this._version = (this._version + 1) | 0;
|
|
1220
|
+
return this;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* Remove `key`. EXPECTED O(log n). Idempotent: returns `false` if `key` is
|
|
1225
|
+
* absent (no throw), `true` if it was present and removed. Fails closed on a
|
|
1226
|
+
* non-finite key (typeof-guarded first) with a `[lite-logn]` throw.
|
|
1227
|
+
* @param {number} key a finite number
|
|
1228
|
+
* @returns {boolean}
|
|
1229
|
+
*/
|
|
1230
|
+
delete(key) {
|
|
1231
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1232
|
+
const cand = this._find(key); // fills _update
|
|
1233
|
+
const K = this._key;
|
|
1234
|
+
if (cand === 0 || K[cand] !== key) return false; // absent (no throw)
|
|
1235
|
+
const next = this._next, stride = this._stride, upd = this._update;
|
|
1236
|
+
for (let lvl = 0; lvl < this._level; lvl++) {
|
|
1237
|
+
const base = lvl * stride;
|
|
1238
|
+
const p = upd[lvl];
|
|
1239
|
+
if (next[base + p] === cand) next[base + p] = next[base + cand];
|
|
1240
|
+
}
|
|
1241
|
+
// Shrink the live height while the top levels are empty (head link == NIL).
|
|
1242
|
+
let lv = this._level;
|
|
1243
|
+
while (lv > 1 && next[(lv - 1) * stride] === 0) lv--;
|
|
1244
|
+
this._level = lv;
|
|
1245
|
+
this._pool.free(cand);
|
|
1246
|
+
this._size--;
|
|
1247
|
+
this._version = (this._version + 1) | 0;
|
|
1248
|
+
return true;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* The smallest key STRICTLY greater than `key`, or `undefined` if none. EXPECTED
|
|
1253
|
+
* O(log n). `key` itself need not be present. Fails closed on a non-finite key.
|
|
1254
|
+
* @param {number} key a finite number
|
|
1255
|
+
* @returns {number|undefined}
|
|
1256
|
+
*/
|
|
1257
|
+
successor(key) {
|
|
1258
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1259
|
+
const next = this._next, K = this._key, stride = this._stride;
|
|
1260
|
+
let slot = 0;
|
|
1261
|
+
for (let lvl = this._level - 1; lvl >= 0; lvl--) {
|
|
1262
|
+
const base = lvl * stride;
|
|
1263
|
+
let nx = next[base + slot];
|
|
1264
|
+
while (nx !== 0 && K[nx] <= key) { slot = nx; nx = next[base + slot]; }
|
|
1265
|
+
}
|
|
1266
|
+
const cand = next[slot];
|
|
1267
|
+
return cand !== 0 ? K[cand] : undefined;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
/**
|
|
1271
|
+
* The largest key STRICTLY less than `key`, or `undefined` if none. EXPECTED
|
|
1272
|
+
* O(log n). `key` itself need not be present. Fails closed on a non-finite key.
|
|
1273
|
+
* @param {number} key a finite number
|
|
1274
|
+
* @returns {number|undefined}
|
|
1275
|
+
*/
|
|
1276
|
+
predecessor(key) {
|
|
1277
|
+
if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
|
|
1278
|
+
const next = this._next, K = this._key, stride = this._stride;
|
|
1279
|
+
let slot = 0;
|
|
1280
|
+
for (let lvl = this._level - 1; lvl >= 0; lvl--) {
|
|
1281
|
+
const base = lvl * stride;
|
|
1282
|
+
let nx = next[base + slot];
|
|
1283
|
+
while (nx !== 0 && K[nx] < key) { slot = nx; nx = next[base + slot]; }
|
|
1284
|
+
}
|
|
1285
|
+
return slot !== 0 ? K[slot] : undefined; // slot = largest key < key, or head
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
/**
|
|
1289
|
+
* A VERSION-STAMPED iterator over the keys in `[lo, hi]` INCLUSIVE, in ascending
|
|
1290
|
+
* order. Bounds may be any number INCLUDING +-Infinity (an unbounded end);
|
|
1291
|
+
* `NaN` (unordered) fails closed, as does `lo > hi`. The generator captures the
|
|
1292
|
+
* list's version and throws `[lite-logn]` if any STRUCTURAL mutation (set of a
|
|
1293
|
+
* new key, delete, clear -- or any value update) happens mid-iteration, rather
|
|
1294
|
+
* than yield stale / recycled data. The one documented per-protocol allocator
|
|
1295
|
+
* (a {value, done} per step); the loop body itself allocates nothing.
|
|
1296
|
+
* @param {number} lo lower bound (inclusive); may be -Infinity
|
|
1297
|
+
* @param {number} hi upper bound (inclusive); may be +Infinity
|
|
1298
|
+
* @returns {IterableIterator<number>} the keys in [lo, hi], ascending
|
|
1299
|
+
*/
|
|
1300
|
+
rangeIter(lo, hi) {
|
|
1301
|
+
if (typeof lo !== 'number' || Number.isNaN(lo)) return this._badBound(lo);
|
|
1302
|
+
if (typeof hi !== 'number' || Number.isNaN(hi)) return this._badBound(hi);
|
|
1303
|
+
if (lo > hi) return this._badRange(lo, hi);
|
|
1304
|
+
return this._rangeGen(lo, hi);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/** @private version-stamped range generator (see rangeIter). */
|
|
1308
|
+
*_rangeGen(lo, hi) {
|
|
1309
|
+
const ver = this._version;
|
|
1310
|
+
const next = this._next, K = this._key, stride = this._stride;
|
|
1311
|
+
let slot = 0;
|
|
1312
|
+
for (let lvl = this._level - 1; lvl >= 0; lvl--) {
|
|
1313
|
+
const base = lvl * stride;
|
|
1314
|
+
let nx = next[base + slot];
|
|
1315
|
+
while (nx !== 0 && K[nx] < lo) { slot = nx; nx = next[base + slot]; }
|
|
1316
|
+
}
|
|
1317
|
+
slot = next[slot]; // first slot with key >= lo
|
|
1318
|
+
while (slot !== 0 && K[slot] <= hi) {
|
|
1319
|
+
if (this._version !== ver) {
|
|
1320
|
+
throw new Error('[lite-logn] SkipList mutated during iteration');
|
|
1321
|
+
}
|
|
1322
|
+
yield K[slot];
|
|
1323
|
+
slot = next[slot]; // level-0 next (base 0)
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/**
|
|
1328
|
+
* Visit every live `(key, value)` pair in ASCENDING key order. O(n) cold scan,
|
|
1329
|
+
* allocation-free in the loop body (pass a hoisted callback). Unlike rangeIter
|
|
1330
|
+
* this is NOT version-stamped -- mutating from within the callback is the
|
|
1331
|
+
* caller's responsibility (matching the other members' forEach).
|
|
1332
|
+
* @param {(key:number, value:number, list:SkipList)=>void} fn
|
|
1333
|
+
*/
|
|
1334
|
+
forEach(fn) {
|
|
1335
|
+
const next = this._next, K = this._key, V = this._val;
|
|
1336
|
+
let slot = next[0]; // level-0 first (base 0, head)
|
|
1337
|
+
while (slot !== 0) {
|
|
1338
|
+
fn(K[slot], V[slot], this);
|
|
1339
|
+
slot = next[slot];
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
/**
|
|
1344
|
+
* Empty the list, keeping the fixed capacity. O(capacity) cold path: returns
|
|
1345
|
+
* every node to the pool, points the head's links at NIL, resets the live
|
|
1346
|
+
* height, and restores the PRNG to its initial seed (a cleared list replays a
|
|
1347
|
+
* fresh one). @returns {this}
|
|
1348
|
+
*/
|
|
1349
|
+
clear() {
|
|
1350
|
+
this._pool.clear();
|
|
1351
|
+
const next = this._next, stride = this._stride, cols = this._maxLevel;
|
|
1352
|
+
for (let lvl = 0; lvl < cols; lvl++) next[lvl * stride] = 0; // head links -> NIL
|
|
1353
|
+
this._level = 1;
|
|
1354
|
+
this._size = 0;
|
|
1355
|
+
this._seed = this._seed0;
|
|
1356
|
+
this._version = (this._version + 1) | 0;
|
|
1357
|
+
return this;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
// ---- private structural descent (hot body) -----------------------------
|
|
1361
|
+
|
|
1362
|
+
/**
|
|
1363
|
+
* The ONE structural descent (set / delete): walk top-down, at each level
|
|
1364
|
+
* advancing while the next key is strictly less than `key`, recording the
|
|
1365
|
+
* predecessor per level in the reused `_update` scratch. Returns the level-0
|
|
1366
|
+
* candidate (first slot with key >= `key`, or NIL). Zero allocation.
|
|
1367
|
+
* @private
|
|
1368
|
+
*/
|
|
1369
|
+
_find(key) {
|
|
1370
|
+
const next = this._next, K = this._key, stride = this._stride, upd = this._update;
|
|
1371
|
+
let slot = 0; // head
|
|
1372
|
+
for (let lvl = this._level - 1; lvl >= 0; lvl--) {
|
|
1373
|
+
const base = lvl * stride;
|
|
1374
|
+
let nx = next[base + slot];
|
|
1375
|
+
while (nx !== 0 && K[nx] < key) { slot = nx; nx = next[base + slot]; }
|
|
1376
|
+
upd[lvl] = slot;
|
|
1377
|
+
}
|
|
1378
|
+
return next[slot]; // level-0 next (base 0)
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
// ---- cold path only: throw builders (string concat off the hot body) ----
|
|
1382
|
+
|
|
1383
|
+
/** @private */
|
|
1384
|
+
_badKey(key) {
|
|
1385
|
+
throw new TypeError(
|
|
1386
|
+
'[lite-logn] SkipList key must be a finite number, got ' + String(key));
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
/** @private */
|
|
1390
|
+
_badValue(value) {
|
|
1391
|
+
throw new TypeError(
|
|
1392
|
+
'[lite-logn] SkipList value must be a finite number, got ' + String(value));
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/** @private */
|
|
1396
|
+
_badBound(b) {
|
|
1397
|
+
throw new TypeError(
|
|
1398
|
+
'[lite-logn] SkipList rangeIter bound must be a number (not NaN), got ' + String(b));
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/** @private */
|
|
1402
|
+
_badRange(lo, hi) {
|
|
1403
|
+
throw new RangeError(
|
|
1404
|
+
'[lite-logn] SkipList rangeIter needs lo <= hi, got lo=' + String(lo) +
|
|
1405
|
+
' hi=' + String(hi));
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
/** @private */
|
|
1409
|
+
_full() {
|
|
1410
|
+
throw new RangeError('[lite-logn] SkipList full (capacity ' + this._cap + ')');
|
|
1411
|
+
}
|
|
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
|
+
}
|