@zakkster/lite-logn 0.1.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 ADDED
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@zakkster/lite-logn` are documented here. The format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
5
+ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-09-17
10
+
11
+ ### Added
12
+
13
+ - **BinaryHeap** -- the first member: an indexed binary min|max heap (addressable
14
+ priority queue) over three parallel typed arrays (`_key` Float64Array, `_id`
15
+ Uint32Array, `_pos` Int32Array reverse map). Surface: `push` / `pop` / `peek` /
16
+ `topKey` / `keyOf` / `has` / `changeKey` / `remove`, `size` / `capacity` /
17
+ `kind` getters, `clear`, `forEach` / `[Symbol.iterator]` (unspecified order),
18
+ and a static `BinaryHeap.build(kind, ids, keys, capacity)` Floyd O(n) bulk
19
+ build. changeKey / remove address elements by caller-supplied entity id via the
20
+ reverse-index map. push / pop / changeKey / remove are O(log n) with
21
+ hole-punching sift; peek / topKey / keyOf / has are O(1). Fixed-capacity
22
+ fail-closed (overflow, duplicate id, non-member changeKey, out-of-range id, and
23
+ non-finite key all throw `[lite-logn]`; never a silent drop). Zero allocation on
24
+ every hot path.
25
+ - **Scaffold release.** Stands up the repo for the O(log n) family, the sibling
26
+ of `@zakkster/lite-o1`. Ships the six `files[]` entries: `LogN.js` (header +
27
+ the `VERSION` const, no member yet), `LogN.d.ts`, `llms.txt`, `README.md`,
28
+ `CHANGELOG.md`, and `LICENSE`. Repo-only (not shipped): `GUIDE.md` and the
29
+ gate harnesses (`test/torture.mjs`, `test/witness.mjs`,
30
+ `test/perf/PerfGate.test.mjs`, `test/QaAudit.test.js`, `test/Bench.test.mjs`).
31
+ With zero members the harnesses run green and empty.
32
+ - **The witness harness stub.** The log-linear fit machinery
33
+ (`nsPerOp = intercept + slope*log2(n)`, least-squares `R^2` + slope) and an
34
+ O(n) foil are in place; the `R^2` floor + slope band are deliberately NOT
35
+ gated yet -- they are calibrated in the BinaryHeap session (decision D-02).
36
+ - **Design decisions on the record.** [`decisions/0001-nodepool.md`](./decisions/0001-nodepool.md)
37
+ (D-01: reconcile the pointer-free node pool against lite-o1's SlotPool, do not
38
+ fork), [`decisions/0002-spine.md`](./decisions/0002-spine.md) (D-03: the
39
+ ordered-collection spine reach -- offer where honest, force nowhere), and
40
+ [`decisions/0003-pack.md`](./decisions/0003-pack.md) (D-07: `files[]` ships the
41
+ six files only; `test/`, `benchmark/`, `decisions/`, `demo/` are repo-only).
42
+
43
+ [Unreleased]: https://github.com/PeshoVurtoleta/lite-logn/compare/v0.1.0...HEAD
44
+ [0.1.0]: https://github.com/PeshoVurtoleta/lite-logn/releases/tag/v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Zahary Shinikchiev <shinikchiev@yahoo.com>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/LogN.d.ts ADDED
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @zakkster/lite-logn -- ambient type surface.
3
+ *
4
+ * Hand-written to mirror EXACTLY the runtime exports of LogN.js. The three-place
5
+ * version sync (package.json / LogN.js VERSION / llms.txt) is enforced in review;
6
+ * at v0.1.0 (the scaffold release) the only export is `VERSION`. One ambient
7
+ * declaration block per member is appended here as each member ships. ASCII-only.
8
+ *
9
+ * @license MIT
10
+ */
11
+
12
+ /** Package version string. */
13
+ export const VERSION: string;
14
+
15
+ // --- member type blocks land here, append-only, one per shipped member -------
16
+
17
+ /**
18
+ * An indexed binary heap (addressable priority queue): a min|max binary heap over
19
+ * three parallel typed arrays plus a reverse-index map, giving O(log n)
20
+ * changeKey / remove by caller-supplied entity id. Entity ids are integers in
21
+ * [0, capacity); keys are finite numbers. Every hot op allocates zero bytes.
22
+ */
23
+ export class BinaryHeap {
24
+ /** @param capacity exact max live entries; integer in [1, 2^31-1].
25
+ * @param kind the frozen extreme this heap reports (default 'min'). */
26
+ constructor(capacity: number, kind?: 'min' | 'max');
27
+
28
+ /** Live entry count. */
29
+ readonly size: number;
30
+ /** The fixed capacity this heap was sized for. */
31
+ readonly capacity: number;
32
+ /** The frozen extreme this heap reports. */
33
+ readonly kind: 'min' | 'max';
34
+
35
+ /** Insert entity id with priority key. Throws on out-of-range/duplicate id,
36
+ * non-finite key, or a full heap. */
37
+ push(id: number, key: number): void;
38
+ /** Remove and return the extremum's entity id, or undefined if empty. */
39
+ pop(): number | undefined;
40
+ /** The extremum's entity id, or undefined if empty. */
41
+ peek(): number | undefined;
42
+ /** The extremum's key, or undefined if empty. */
43
+ topKey(): number | undefined;
44
+ /** The key associated with id, or undefined if absent. Out-of-range id throws. */
45
+ keyOf(id: number): number | undefined;
46
+ /** True iff id is currently in the heap. Out-of-range id throws. */
47
+ has(id: number): boolean;
48
+ /** Reprioritize a present entity, auto-directing the sift. Non-member throws. */
49
+ changeKey(id: number, newKey: number): void;
50
+ /** Remove entity id; true if it was present, false if absent (idempotent). */
51
+ remove(id: number): boolean;
52
+ /** Empty the heap. */
53
+ clear(): void;
54
+ /** Visit every live (id, key) pair in unspecified (heap-array) order. */
55
+ forEach(fn: (id: number, key: number, heap: BinaryHeap) => void): void;
56
+ /** Iterate live entity ids in unspecified (heap-array) order (NOT sorted). */
57
+ [Symbol.iterator](): IterableIterator<number>;
58
+
59
+ /** Floyd O(n) bulk build from parallel ids/keys arrays. */
60
+ static build(
61
+ kind: 'min' | 'max',
62
+ ids: ArrayLike<number>,
63
+ keys: ArrayLike<number>,
64
+ capacity: number,
65
+ ): BinaryHeap;
66
+ }
package/LogN.js ADDED
@@ -0,0 +1,394 @@
1
+ /**
2
+ * @zakkster/lite-logn -- a tree-shakeable, zero-GC family of O(log n) data
3
+ * structures that doubles as a teachable textbook: each member solves a real
4
+ * problem AND proves its logarithm is real (the O(log n) Witness -- see
5
+ * test/witness.mjs).
6
+ *
7
+ * v0.1.0 ships its FIRST member: BinaryHeap, an INDEXED binary heap -- an
8
+ * addressable priority queue (a min|max binary heap over three parallel typed
9
+ * arrays plus a reverse-index map that makes changeKey / remove O(log n) by
10
+ * caller-supplied entity id). Members land append-only, leaving this header and
11
+ * the `VERSION` const the only prior lines that ever change. Planned roster:
12
+ * BinaryHeap (this release, array-embedded O(log n) push / pop min|max heap),
13
+ * Fenwick / BIT (O(log n) point-update AND prefix-sum via the
14
+ * `i & -i` walk), SegmentTree (O(log n) associative range-query + point-update),
15
+ * and SkipList (pointer-free expected-O(log n) ordered map). Members are
16
+ * independent (no shared mutable module state), so a bundler that imports one
17
+ * drops the others (`sideEffects: false`).
18
+ *
19
+ * The family delta: lite-o1 proves a FLAT ops/ms line on a log-x axis (the
20
+ * constant, slope ~ 0); lite-logn proves a STRAIGHT line on that same axis (one
21
+ * added level per doubling of n, slope > 0 within a per-member band). Every hot
22
+ * op each member ships is O(log n) worst-case (or expected/amortized where
23
+ * stated) and allocates ZERO bytes after construction. The witness harness
24
+ * (never imported here) fits `nsPerOp = intercept + slope * log2(n)` and shows
25
+ * the straight log line while an O(n) foil leaves it -- that line is the theorem
26
+ * made visible.
27
+ *
28
+ * @license MIT
29
+ */
30
+
31
+ /** Package version. One of the three version sites (package.json / VERSION / llms.txt). */
32
+ export const VERSION = '0.1.0';
33
+
34
+ // --- members land here, append-only, one tree-shakeable class each -----------
35
+ // BinaryHeap (v0.1.0 session) -- indexed O(log n) min|max heap (BELOW)
36
+ // Fenwick (v0.2.0) -- O(log n) point-update + prefix-sum
37
+ // SegmentTree (v0.3.0) -- O(log n) associative range-query + point-update
38
+ // SkipList (v0.4.0) -- pointer-free expected-O(log n) ordered map
39
+
40
+ /** Max heap capacity: slot indices 0..cap-1 must fit the Int32Array _pos map. */
41
+ const BH_MAX_CAPACITY = 0x7FFFFFFF; // 2^31 - 1
42
+
43
+ /**
44
+ * An INDEXED binary heap: an addressable priority queue over three parallel,
45
+ * pointer-free typed arrays plus a reverse-index map. A plain binary heap gives
46
+ * O(log n) push / pop but cannot find an arbitrary element to reprioritize; the
47
+ * `_pos` map (entity id -> current heap slot, sentinel -1 == absent) buys
48
+ * O(log n) `changeKey` / `remove` by a caller-supplied entity id. Every hot op
49
+ * (push / pop / peek / topKey / keyOf / has / changeKey / remove) allocates ZERO
50
+ * bytes after construction.
51
+ *
52
+ * Storage (allocated once, sized to capacity):
53
+ * - `_key` Float64Array -- the priority key at each heap SLOT (heap-ordered).
54
+ * - `_id` Uint32Array -- the entity id at each heap SLOT (moves with its key).
55
+ * - `_pos` Int32Array -- reverse map id -> slot; -1 means "not in heap".
56
+ * `_pos` is initialised to -1 (fill), NOT 0, because slot 0 is a valid position
57
+ * -- null is not zero. Children of slot i are 2i+1 / 2i+2, parent (i-1)>>1.
58
+ *
59
+ * The sift is HOLE-PUNCHING (not a 3-write swap): the moving element is cached in
60
+ * locals once, the hole walks down / up writing ONE slot per level (updating
61
+ * `_pos` for each shifted id), then the cached element drops into the final hole.
62
+ *
63
+ * ID contract: entity ids are integers in [0, capacity). Any out-of-range id is a
64
+ * hard `[lite-logn]` throw (a programming error). A key must be a finite number
65
+ * where required; NaN / non-finite / non-number fail closed rather than corrupt
66
+ * heap order. Fixed capacity: overflow throws, never silently drops.
67
+ */
68
+ export class BinaryHeap {
69
+ /**
70
+ * @param {number} capacity exact max live entries; integer in [1, 2^31-1].
71
+ * @param {'min'|'max'} [kind] the frozen extreme this heap reports (default 'min').
72
+ */
73
+ constructor(capacity, kind = 'min') {
74
+ // typeof guard BEFORE coercion (Number.isInteger is Symbol/BigInt-safe).
75
+ if (typeof capacity !== 'number' || !Number.isInteger(capacity) ||
76
+ capacity < 1 || capacity > BH_MAX_CAPACITY) {
77
+ throw new RangeError(
78
+ '[lite-logn] BinaryHeap capacity must be an integer in [1, 2^31-1], got ' +
79
+ String(capacity));
80
+ }
81
+ if (kind !== 'min' && kind !== 'max') {
82
+ throw new RangeError(
83
+ '[lite-logn] BinaryHeap kind must be "min" or "max", got ' + String(kind));
84
+ }
85
+ this._key = new Float64Array(capacity); // key at each heap slot
86
+ this._id = new Uint32Array(capacity); // entity id at each heap slot
87
+ this._pos = new Int32Array(capacity); // id -> slot; -1 == absent
88
+ this._pos.fill(-1); // slot 0 is valid: null is not zero
89
+ this._cap = capacity; // exact fixed capacity
90
+ this._n = 0; // live entries (heap size)
91
+ this._min = kind === 'min'; // ctor-frozen hot compare flag
92
+ }
93
+
94
+ /** Live entry count. O(1). */
95
+ get size() { return this._n; }
96
+
97
+ /** The fixed capacity this heap was sized for. O(1). */
98
+ get capacity() { return this._cap; }
99
+
100
+ /** The frozen extreme this heap reports, 'min' or 'max'. O(1). */
101
+ get kind() { return this._min ? 'min' : 'max'; }
102
+
103
+ /**
104
+ * Insert entity `id` with priority `key`. O(log n). Fails closed: an
105
+ * out-of-range id, an already-present id (no silent overwrite), a non-finite /
106
+ * non-number key, or a full heap each throw `[lite-logn]` as a no-op.
107
+ * @param {number} id integer in [0, capacity), not already present
108
+ * @param {number} key a finite number
109
+ */
110
+ push(id, key) {
111
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id >= this._cap) {
112
+ return this._badId(id);
113
+ }
114
+ if (typeof key !== 'number' || !Number.isFinite(key)) return this._badKey(key);
115
+ if (this._pos[id] !== -1) return this._dup(id);
116
+ const n = this._n;
117
+ if (n === this._cap) return this._full();
118
+ this._n = n + 1;
119
+ this._siftUp(n, key, id);
120
+ }
121
+
122
+ /**
123
+ * Remove and return the entity id at the extremum (min or max per kind), or
124
+ * `undefined` if empty (never throws on empty). O(log n).
125
+ * @returns {number|undefined}
126
+ */
127
+ pop() {
128
+ const n = this._n;
129
+ if (n === 0) return undefined;
130
+ const top = this._id[0];
131
+ this._pos[top] = -1;
132
+ const last = n - 1;
133
+ this._n = last;
134
+ if (last > 0) this._siftDown(0, this._key[last], this._id[last]);
135
+ return top;
136
+ }
137
+
138
+ /** The entity id at the extremum, or `undefined` if empty. Read-only. O(1). */
139
+ peek() { return this._n === 0 ? undefined : this._id[0]; }
140
+
141
+ /** The key at the extremum (root), or `undefined` if empty. O(1). */
142
+ topKey() { return this._n === 0 ? undefined : this._key[0]; }
143
+
144
+ /**
145
+ * The key currently associated with `id`, or `undefined` if id is not in the
146
+ * heap. O(1). An out-of-range id still throws.
147
+ * @param {number} id
148
+ * @returns {number|undefined}
149
+ */
150
+ keyOf(id) {
151
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id >= this._cap) {
152
+ return this._badId(id);
153
+ }
154
+ const slot = this._pos[id];
155
+ return slot === -1 ? undefined : this._key[slot];
156
+ }
157
+
158
+ /**
159
+ * True iff `id` is currently in the heap. O(1). An out-of-range id throws.
160
+ * @param {number} id
161
+ * @returns {boolean}
162
+ */
163
+ has(id) {
164
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id >= this._cap) {
165
+ return this._badId(id);
166
+ }
167
+ return this._pos[id] !== -1;
168
+ }
169
+
170
+ /**
171
+ * Reprioritize a present entity to `newKey`, auto-directing the sift. O(log n).
172
+ * Compares the new key against the current parent to pick the single direction
173
+ * that can violate order (up XOR down), then sifts that way with hole-punching.
174
+ * Fails closed: a non-member id throws `[lite-logn]` (never a silent no-op); an
175
+ * out-of-range id or non-finite key throws.
176
+ * @param {number} id integer in [0, capacity), currently present
177
+ * @param {number} newKey a finite number
178
+ */
179
+ changeKey(id, newKey) {
180
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id >= this._cap) {
181
+ return this._badId(id);
182
+ }
183
+ if (typeof newKey !== 'number' || !Number.isFinite(newKey)) return this._badKey(newKey);
184
+ const slot = this._pos[id];
185
+ if (slot === -1) return this._notMember(id);
186
+ if (slot > 0) {
187
+ const pk = this._key[(slot - 1) >> 1];
188
+ if (this._min ? (newKey < pk) : (newKey > pk)) {
189
+ this._siftUp(slot, newKey, id);
190
+ return;
191
+ }
192
+ }
193
+ this._siftDown(slot, newKey, id);
194
+ }
195
+
196
+ /**
197
+ * Remove entity `id`. O(log n). Idempotent: returns false if id is absent (no
198
+ * throw), true if it was present and removed. The last entry fills the vacated
199
+ * slot and sifts the single direction that can violate order. An out-of-range
200
+ * id throws.
201
+ * @param {number} id
202
+ * @returns {boolean}
203
+ */
204
+ remove(id) {
205
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id >= this._cap) {
206
+ return this._badId(id);
207
+ }
208
+ const slot = this._pos[id];
209
+ if (slot === -1) return false;
210
+ this._pos[id] = -1;
211
+ const last = this._n - 1;
212
+ this._n = last;
213
+ if (slot !== last) {
214
+ const mk = this._key[last];
215
+ const mid = this._id[last];
216
+ if (slot > 0) {
217
+ const pk = this._key[(slot - 1) >> 1];
218
+ if (this._min ? (mk < pk) : (mk > pk)) {
219
+ this._siftUp(slot, mk, mid);
220
+ return true;
221
+ }
222
+ }
223
+ this._siftDown(slot, mk, mid);
224
+ }
225
+ return true;
226
+ }
227
+
228
+ /** Empty the heap. O(capacity) cold path (resets size + the reverse map). */
229
+ clear() {
230
+ this._n = 0;
231
+ this._pos.fill(-1);
232
+ }
233
+
234
+ /**
235
+ * Visit every live (id, key) pair in UNSPECIFIED (heap-array) order -- NOT
236
+ * sorted / pop order. O(n) cold scan, allocation-free (pass a hoisted callback).
237
+ * @param {(id:number, key:number, heap:BinaryHeap)=>void} fn
238
+ */
239
+ forEach(fn) {
240
+ const id = this._id, key = this._key, n = this._n;
241
+ for (let i = 0; i < n; i++) fn(id[i], key[i], this);
242
+ }
243
+
244
+ /**
245
+ * Iterate live entity ids in UNSPECIFIED (heap-array) order -- NOT sorted. The
246
+ * one documented per-protocol allocator (a {value, done} per step); use forEach
247
+ * for the alloc-free scan.
248
+ */
249
+ *[Symbol.iterator]() {
250
+ const id = this._id, n = this._n;
251
+ for (let i = 0; i < n; i++) yield id[i];
252
+ }
253
+
254
+ /**
255
+ * Floyd O(n) bulk build: load every (ids[i], keys[i]) pair then heapify
256
+ * bottom-up in O(n), rather than n individual O(log n) pushes. COLD path;
257
+ * fails closed on any violation (duplicate / out-of-range id, non-finite key,
258
+ * count > capacity) before the heap is usable.
259
+ * @param {'min'|'max'} kind
260
+ * @param {ArrayLike<number>} ids unique integers in [0, capacity)
261
+ * @param {ArrayLike<number>} keys finite numbers, keys.length === ids.length
262
+ * @param {number} capacity
263
+ * @returns {BinaryHeap}
264
+ */
265
+ static build(kind, ids, keys, capacity) {
266
+ const heap = new BinaryHeap(capacity, kind);
267
+ if (ids == null || keys == null ||
268
+ typeof ids.length !== 'number' || typeof keys.length !== 'number') {
269
+ throw new TypeError('[lite-logn] BinaryHeap.build needs array-like ids and keys');
270
+ }
271
+ const count = ids.length;
272
+ if (keys.length !== count) {
273
+ throw new RangeError(
274
+ '[lite-logn] BinaryHeap.build ids/keys length mismatch (' +
275
+ count + ' vs ' + keys.length + ')');
276
+ }
277
+ if (count > capacity) {
278
+ throw new RangeError(
279
+ '[lite-logn] BinaryHeap.build count ' + count + ' exceeds capacity ' + capacity);
280
+ }
281
+ const K = heap._key, I = heap._id, P = heap._pos;
282
+ for (let i = 0; i < count; i++) {
283
+ const id = ids[i];
284
+ if (typeof id !== 'number' || !Number.isInteger(id) || id < 0 || id >= capacity) {
285
+ throw new RangeError(
286
+ '[lite-logn] BinaryHeap.build id must be an integer in [0, capacity), got ' +
287
+ String(id));
288
+ }
289
+ const key = keys[i];
290
+ if (typeof key !== 'number' || !Number.isFinite(key)) {
291
+ throw new TypeError(
292
+ '[lite-logn] BinaryHeap.build key must be a finite number, got ' + String(key));
293
+ }
294
+ if (P[id] !== -1) {
295
+ throw new RangeError('[lite-logn] BinaryHeap.build duplicate id ' + id);
296
+ }
297
+ K[i] = key;
298
+ I[i] = id;
299
+ P[id] = i;
300
+ }
301
+ heap._n = count;
302
+ // Floyd: sift down every internal node, deepest-first. O(n).
303
+ for (let i = (count >> 1) - 1; i >= 0; i--) {
304
+ heap._siftDown(i, K[i], I[i]);
305
+ }
306
+ return heap;
307
+ }
308
+
309
+ // ---- private hole-punching sift (hot bodies) ---------------------------
310
+
311
+ /**
312
+ * Walk the hole UP from `hole`, pulling each larger (min) / smaller (max)
313
+ * parent down one level, then drop (key, id) into the settled hole. One write
314
+ * per level; `_pos` updated for every shifted id and for the placed id.
315
+ * @private
316
+ */
317
+ _siftUp(hole, key, id) {
318
+ const K = this._key, I = this._id, P = this._pos, min = this._min;
319
+ while (hole > 0) {
320
+ const parent = (hole - 1) >> 1;
321
+ const pk = K[parent];
322
+ if (min ? (key >= pk) : (key <= pk)) break;
323
+ K[hole] = pk;
324
+ const pid = I[parent];
325
+ I[hole] = pid;
326
+ P[pid] = hole;
327
+ hole = parent;
328
+ }
329
+ K[hole] = key;
330
+ I[hole] = id;
331
+ P[id] = hole;
332
+ }
333
+
334
+ /**
335
+ * Walk the hole DOWN from `hole`, pulling the extreme child up one level, then
336
+ * drop (key, id) into the settled hole. One write per level; `_pos` updated for
337
+ * every shifted id and for the placed id. Bound is the current `_n`.
338
+ * @private
339
+ */
340
+ _siftDown(hole, key, id) {
341
+ const K = this._key, I = this._id, P = this._pos, min = this._min;
342
+ const n = this._n;
343
+ const half = n >> 1; // nodes at index >= half are leaves
344
+ while (hole < half) {
345
+ let child = (hole << 1) + 1; // left child
346
+ const right = child + 1;
347
+ if (right < n && (min ? (K[right] < K[child]) : (K[right] > K[child]))) {
348
+ child = right;
349
+ }
350
+ const ck = K[child];
351
+ if (min ? (key <= ck) : (key >= ck)) break;
352
+ K[hole] = ck;
353
+ const cid = I[child];
354
+ I[hole] = cid;
355
+ P[cid] = hole;
356
+ hole = child;
357
+ }
358
+ K[hole] = key;
359
+ I[hole] = id;
360
+ P[id] = hole;
361
+ }
362
+
363
+ // ---- cold path only: throw builders (string concat off the hot body) ---
364
+
365
+ /** @private */
366
+ _badId(id) {
367
+ throw new RangeError(
368
+ '[lite-logn] BinaryHeap id must be an integer in [0, ' + this._cap + '), got ' +
369
+ String(id));
370
+ }
371
+
372
+ /** @private */
373
+ _badKey(key) {
374
+ throw new TypeError(
375
+ '[lite-logn] BinaryHeap key must be a finite number, got ' + String(key));
376
+ }
377
+
378
+ /** @private */
379
+ _dup(id) {
380
+ throw new RangeError(
381
+ '[lite-logn] BinaryHeap id ' + id + ' already present (no silent overwrite)');
382
+ }
383
+
384
+ /** @private */
385
+ _full() {
386
+ throw new RangeError('[lite-logn] BinaryHeap full (capacity ' + this._cap + ')');
387
+ }
388
+
389
+ /** @private */
390
+ _notMember(id) {
391
+ throw new RangeError(
392
+ '[lite-logn] BinaryHeap.changeKey: id ' + id + ' is not in the heap');
393
+ }
394
+ }
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # @zakkster/lite-logn
2
+
3
+ > Zero-GC, O(log n) data structures that PROVE their logarithm. The O(log n) sibling of `@zakkster/lite-o1`: where lite-o1 holds the constant (a flat ops/ms line), lite-logn holds the logarithm (a straight line on a log-x axis -- one added level per doubling of n). v0.1.0 is the scaffold release; the planned roster is BinaryHeap (array-embedded O(log n) push / pop min-heap), Fenwick / BIT (O(log n) point-update AND prefix-sum via the `i & -i` walk), SegmentTree (O(log n) associative range-query + point-update), and SkipList (pointer-free expected-O(log n) ordered map) -- each zero-GC, each shipped with a log-linear Witness that fits `nsPerOp = intercept + slope*log2(n)` and shows the straight log line while an O(n) foil leaves it.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@zakkster/lite-logn.svg?style=for-the-badge&color=latest)](https://www.npmjs.com/package/@zakkster/lite-logn)
6
+ [![sponsor](https://img.shields.io/badge/sponsor-PeshoVurtoleta-ea4aaa.svg?logo=github)](https://github.com/sponsors/PeshoVurtoleta)
7
+ ![Zero-GC](https://img.shields.io/badge/Zero--GC-Engine-00C853?style=for-the-badge&logo=leaf&logoColor=white)
8
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@zakkster/lite-logn?style=for-the-badge)](https://bundlephobia.com/result?p=@zakkster/lite-logn)
9
+ [![npm downloads](https://img.shields.io/npm/dm/@zakkster/lite-logn?style=for-the-badge&color=blue)](https://www.npmjs.com/package/@zakkster/lite-logn)
10
+ [![npm total downloads](https://img.shields.io/npm/dt/@zakkster/lite-logn?style=for-the-badge&color=blue)](https://www.npmjs.com/package/@zakkster/lite-logn)
11
+ ![Tree-Shakeable](https://img.shields.io/badge/tree--shakeable-yes-brightgreen)
12
+ ![TypeScript](https://img.shields.io/badge/TypeScript-Types-informational)
13
+ ![Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)
14
+ [![license](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](./LICENSE)
15
+
16
+ ## The O(log n) toolkit the ecosystem was missing
17
+
18
+ Almost no JavaScript data-structure library ships the evidence that its Big-O claim survives contact with a real engine -- megamorphic call sites, GC pauses, cache misses, deopts. `lite-logn` is a curated, tree-shakeable family of the O(log n) structures that actually matter, each zero-GC, each written to teach the trick that buys the logarithm, and each shipped with a harness that DEMONSTRATES the straight log line rather than asserting it. The complexity class IS the product.
19
+
20
+ lite-logn is the O(log n) sibling of [`@zakkster/lite-o1`](https://www.npmjs.com/package/@zakkster/lite-o1). lite-o1 proves a FLAT ops/ms line on a log-x axis (the constant -- slope ~ 0); lite-logn proves a STRAIGHT line on that same axis (one added level per doubling of `n` -- slope > 0, within a per-member band). The gate SHAPE differs; the discipline is identical: zero allocation on every hot path and a witness that turns "trust me, it is O(log n)" into a straight line you can see, with a foil that leaves it.
21
+
22
+ **v0.1.0 is the scaffold release.** It ships only the `VERSION` const -- there is no member yet. This first cut stands up the repo, the gates (torture / witness / perf), and the design decisions that outlive member 1 (see [`decisions/`](./decisions)). The planned roster below lands one member per session, each append-only so prior members stay byte-identical.
23
+
24
+ ```bash
25
+ npm install @zakkster/lite-logn
26
+ ```
27
+
28
+ ```js
29
+ import { VERSION } from '@zakkster/lite-logn';
30
+
31
+ console.log(VERSION); // -> '0.1.0' (scaffold release; members land per session)
32
+ ```
33
+
34
+ Once BinaryHeap ships (v0.1.0 member session), the quick-start becomes a heap push / pop whose every op is O(log n) worst-case and allocates zero bytes after construction, and `npm run witness` proves it holds the straight log line while a sorted-array-insert foil (O(n) shift) leaves it.
35
+
36
+ ---
37
+
38
+ ## Table of contents
39
+
40
+ - [Why this exists](#why-this-exists)
41
+ - [What you get](#what-you-get)
42
+ - [The planned roster](#the-planned-roster)
43
+ - [The O(log n) Witness](#the-olog-n-witness)
44
+ - [API reference](#api-reference)
45
+ - [Constants](#constants)
46
+ - [Zero-GC design notes](#zero-gc-design-notes)
47
+ - [Testing](#testing)
48
+ - [What this is not](#what-this-is-not)
49
+ - [Ecosystem](#ecosystem)
50
+ - [License](#license)
51
+
52
+ ---
53
+
54
+ ## Why this exists
55
+
56
+ A working programmer reaching for "keep the smallest element to hand" or "prefix sums that stay correct under updates" usually pays an O(n) cost hidden behind a friendly method name -- `Array.prototype.shift`, a full re-sum, a re-sort on every insert. The logarithm is the honest price of order, and it is cheap: one extra level of work per doubling of the data. But a naive O(log n) structure does `new Node` per insert, and that per-op allocation is a GC pause an engine will not honor -- it turns the clean logarithm into jitter.
57
+
58
+ lite-logn ships the O(log n) structures that matter with the allocation removed (array-embedded members are naturally node-free; pointer-based members use a pointer-free node pool) and the logarithm proven (the witness fits a straight log line and shows an O(n) foil leaving it). The complexity class is the product: you get the structure AND the evidence its bound is real on a real engine.
59
+
60
+ ## What you get
61
+
62
+ - **Zero runtime dependencies.** ESM only, ASCII-only source, one PascalCase main file (`LogN.js`), `sideEffects: false`.
63
+ - **Zero allocation on every hot path.** Proven by `node --expose-gc test/torture.mjs` (`@zakkster/lite-leak` + `@zakkster/lite-gc-profiler`): 0 B/op, `gc major = 0`, leak tracker `size 0/0`.
64
+ - **A logarithm you can see.** `npm run witness` fits `nsPerOp = intercept + slope*log2(n)` across a geometric `n` sweep, gates the `R^2` floor + slope band, and shows an O(n) foil departing the line.
65
+ - **Tree-shakeable named exports.** Members share no mutable module state, so a bundler that imports one drops the others.
66
+ - **Fail closed.** Fixed, preallocated capacity; a `typeof`-guard at the door of every mutating op; `null` is not zero; an unknown option key is an error with a hint, never a silent ignore.
67
+
68
+ ## The planned roster
69
+
70
+ One member per session, each landing append-only (prior members stay byte-identical). At v0.1.0 none are shipped yet -- this is the scaffold.
71
+
72
+ | Member | Version | Shape | Hot ops |
73
+ | --- | --- | --- | --- |
74
+ | **BinaryHeap** | 0.1.0 | array-embedded complete binary min-heap over a flat `Float64Array` | `push` / `pop` O(log n), `peek` O(1) |
75
+ | **Fenwick** (BIT) | 0.2.0 | flat array, lowest-set-bit walk (`i & -i`) | `update` / `prefix` / `rangeSum` O(log n) |
76
+ | **SegmentTree** | 0.3.0 | flat, array-embedded tree; associative fold chosen at construction | `rangeQuery` / `pointUpdate` O(log n) |
77
+ | **SkipList** | 0.4.0 | pointer-free over a shared node pool; expected O(log n) | `get` / `set` / `delete` / `successor` |
78
+
79
+ Later tiers (Treap / Scapegoat, OrderStatTree, IndexedHeap, SortedArray, MinMaxHeap, SplayTree, and presets) are queued in [`ROADMAP.md`](./ROADMAP.md).
80
+
81
+ ## The O(log n) Witness
82
+
83
+ The family anchor. Time a fixed batch of the hot op at each `n` in a geometric sweep, fit `nsPerOp = intercept + slope * log2(n)` by least squares, and gate:
84
+
85
+ - `R^2 >= floor` (a straight line fits -- genuinely logarithmic), AND
86
+ - `slope` inside the member's band (the per-level cost, ns/level), AND
87
+ - the FOIL leaves the line (low `R^2` -- the O(n) default a working programmer reaches for, shown losing as `n` grows).
88
+
89
+ For amortized / randomized members the witness also prints the MAX single-op time -- the honesty hook: a rebuild spike or a degenerate tail shows as a tall bar even when the mean still fits the line. The `R^2` floor + slope band are calibrated in BinaryHeap and become the shared FAMILY gate. At v0.1.0 the witness harness is a stub: with zero members it runs green and empty (there is no member to fit yet).
90
+
91
+ ## API reference
92
+
93
+ ### Constants
94
+
95
+ | Export | Type | Value | Meaning |
96
+ | --- | --- | --- | --- |
97
+ | `VERSION` | `string` | `'0.1.0'` | The package version. One of the three version sites (package.json / `LogN.js` `VERSION` const / `llms.txt`), kept in lockstep and enforced in review. |
98
+
99
+ ### BinaryHeap
100
+
101
+ An **indexed binary heap** (an addressable priority queue): a min|max binary heap over three parallel, pointer-free typed arrays -- `_key` (`Float64Array`, the priority at each heap slot), `_id` (`Uint32Array`, the entity id at each slot), and `_pos` (`Int32Array`, the reverse map entity-id -> slot, sentinel `-1` == absent). A plain binary heap gives O(log n) `push` / `pop` but cannot find an arbitrary element to reprioritize; the reverse-index map buys O(log n) `changeKey` / `remove` by a caller-supplied entity id. Children of slot `i` are `2i+1` / `2i+2`. Entity ids are integers in `[0, capacity)`; keys are finite numbers. Every hot op allocates zero bytes after construction (hole-punching sift -- one write per level, no 3-write swap).
102
+
103
+ ```js
104
+ import { BinaryHeap } from '@zakkster/lite-logn';
105
+
106
+ const pq = new BinaryHeap(1024, 'min'); // capacity 1024, min-heap
107
+ pq.push(7, 5.0); // entity 7 at priority 5.0
108
+ pq.push(3, 2.5);
109
+ pq.push(9, 8.0);
110
+ pq.peek(); // -> 3 (id of the extremum)
111
+ pq.topKey(); // -> 2.5 (its key)
112
+ pq.changeKey(9, 1.0); // reprioritize entity 9 to the front
113
+ pq.pop(); // -> 9 (removes and returns the new extremum)
114
+ pq.remove(7); // -> true (addressable delete by id)
115
+
116
+ // Floyd O(n) bulk build from parallel arrays:
117
+ const heap = BinaryHeap.build('max', [0, 1, 2, 3], [4.0, 1.0, 9.0, 2.0], 16);
118
+ heap.pop(); // -> 2 (the id whose key 9.0 is the max)
119
+ ```
120
+
121
+ | Member | Signature | Complexity | Notes |
122
+ | --- | --- | --- | --- |
123
+ | constructor | `new BinaryHeap(capacity, kind = 'min')` | O(capacity) | `capacity` integer in `[1, 2^31-1]`; `kind` is `'min'` or `'max'`. Allocates the three typed arrays once; `_pos.fill(-1)`. |
124
+ | `push` | `push(id, key) -> void` | O(log n) | id in `[0, capacity)`, not already present; key finite. Throws on out-of-range/duplicate id, non-finite key, or full heap. |
125
+ | `pop` | `pop() -> number \| undefined` | O(log n) | Removes and returns the extremum's id; `undefined` if empty (no throw). |
126
+ | `peek` | `peek() -> number \| undefined` | O(1) | The extremum's id; `undefined` if empty. |
127
+ | `topKey` | `topKey() -> number \| undefined` | O(1) | The extremum's key; `undefined` if empty. |
128
+ | `keyOf` | `keyOf(id) -> number \| undefined` | O(1) | The key associated with id; `undefined` if absent. Out-of-range id throws. |
129
+ | `has` | `has(id) -> boolean` | O(1) | True iff id is resident. Out-of-range id throws. |
130
+ | `changeKey` | `changeKey(id, newKey) -> void` | O(log n) | Reprioritize a present entity (auto-direction sift); a non-member id throws. |
131
+ | `remove` | `remove(id) -> boolean` | O(log n) | Idempotent: `false` if absent, `true` if removed. |
132
+ | `clear` | `clear() -> void` | O(capacity) | Resets size and the reverse map. |
133
+ | `forEach` | `forEach(fn) -> void` | O(n) | Visits `(id, key)` in UNSPECIFIED (heap-array) order -- NOT sorted / pop order. |
134
+ | `[Symbol.iterator]` | `for (const id of heap)` | O(n) | Yields live ids in UNSPECIFIED order. |
135
+ | `size` / `capacity` / `kind` | getters | O(1) | Live count / fixed capacity / `'min'` \| `'max'`. |
136
+ | `BinaryHeap.build` | `build(kind, ids, keys, capacity) -> BinaryHeap` | O(n) | Floyd bulk build from parallel arrays; fails closed on duplicate/out-of-range id, non-finite key, or `count > capacity`. |
137
+
138
+ Member signatures for later members are appended here as each ships.
139
+
140
+ ## Zero-GC design notes
141
+
142
+ - **Array-embedded members allocate no nodes.** BinaryHeap, Fenwick, and SegmentTree live in flat typed arrays; there is no `new Node` per op, so there is nothing to collect. The parent / child / sibling relationships are index arithmetic (`2i+1`, `i & -i`), not pointers.
143
+ - **Pointer-based members use a pointer-free node pool.** SkipList and the later balanced-BST members allocate a slot INDEX from a free list over parallel `Uint32Array` link columns -- never a heap object. `NIL = 0`, slot 0 unused.
144
+ - **Fixed, preallocated capacity.** Overflow fails closed (a `[lite-logn]`-tagged throw), never a silent grow + amortized resize -- a resize would break the worst-case bound the witness proves.
145
+
146
+ | Op class | Allocation |
147
+ | --- | --- |
148
+ | `BinaryHeap` push / pop / peek / topKey / keyOf / has / changeKey / remove | 0 B/op |
149
+ | `BinaryHeap` constructor / `build` / `clear` | O(capacity) typed arrays, once (cold) |
150
+
151
+ The allocation table is filled in per member as each lands, with the gated `R^2` / slope numbers from its witness run.
152
+
153
+ ## Testing
154
+
155
+ `node:test` only, zero runtime deps. At v0.1.0 the harnesses are scaffolds that run green and empty (no member to exercise yet); each member session fills its tier.
156
+
157
+ - `npm test` -- per-member contract + boundary + fuzz-vs-oracle suites, plus the cross-member `QaAudit` block (VERSION trinity, author-spelling guard, ASCII-only source, the six-file pack).
158
+ - `npm run torture` -- `node --expose-gc test/torture.mjs`: 0 B/op on every hot path, `gc major = 0`, leak tracker `size 0/0`.
159
+ - `npm run witness` -- the O(log n) witness harness: log-linear `R^2` + slope fit, with an O(n) foil that must leave the line.
160
+ - `npm run test:perf` -- `@zakkster/lite-perf-gate` zero-alloc scenarios, each with a must-fail teeth case that proves the instrument has teeth.
161
+ - `npm run test:types` -- `tsc` over the ambient `LogN.d.ts` surface.
162
+ - `npm run verify` -- all of the above in sequence.
163
+
164
+ ## What this is not
165
+
166
+ - **Not a bounded-integer priority queue.** If your priorities are small bounded integers, a heap's O(log n) is the wrong tool -- use `@zakkster/lite-o1`'s `BucketQueue` (Dial, O(1)) or `@zakkster/lite-scheduler`'s `FastBitScheduler`. lite-logn's heap is the GENERAL comparator PQ at O(log n).
167
+ - **Not an approximate-membership library.** Bloom / cuckoo / binary-fuse filters live in `@zakkster/lite-filter`. lite-logn owns exact ordered structures.
168
+ - **Not a cache.** `@zakkster/lite-lru` uses ordering internally for eviction but is a cache, not an ordered-collection library.
169
+ - **Not a grow-on-demand collection.** Capacity is fixed at construction and overflow fails closed.
170
+
171
+ ## Ecosystem
172
+
173
+ Part of the `@zakkster/*` LiteLibrariesSuite of zero-GC, single-file ESM micro-libraries.
174
+
175
+ - [`@zakkster/lite-o1`](https://www.npmjs.com/package/@zakkster/lite-o1) -- the O(1) sibling and intended pair. lite-o1 holds the constant; lite-logn holds the logarithm. Keep the witnesses kin: a flat line vs a straight-log line.
176
+ - [`@zakkster/lite-filter`](https://www.npmjs.com/package/@zakkster/lite-filter) -- approximate / probabilistic membership.
177
+ - [`@zakkster/lite-lru`](https://www.npmjs.com/package/@zakkster/lite-lru) -- zero-GC cache family.
178
+
179
+ ## License
180
+
181
+ MIT (c) Zahary Shinikchiev <shinikchiev@yahoo.com>
package/llms.txt ADDED
@@ -0,0 +1,83 @@
1
+ # @zakkster/lite-logn
2
+
3
+ Version: 0.1.0
4
+ License: MIT (c) Zahary Shinikchiev <shinikchiev@yahoo.com>
5
+ Runtime dependencies: none. ESM only. ASCII-only source. sideEffects: false.
6
+ Node: >= 18.
7
+
8
+ ## What it is
9
+
10
+ A tree-shakeable, zero-GC family of O(log n) data structures that PROVES its
11
+ logarithm instead of asserting it. The complexity class is the product: every
12
+ hot op is O(log n) worst-case (or expected / amortized where stated) and
13
+ allocates zero bytes after construction, and a shipped witness harness fits
14
+ `nsPerOp = intercept + slope * log2(n)` and demonstrates the straight log line
15
+ against a built-in O(n) foil that leaves it.
16
+
17
+ It is the O(log n) sibling of @zakkster/lite-o1: lite-o1 holds the constant (a
18
+ FLAT ops/ms line on a log-x axis), lite-logn holds the logarithm (a STRAIGHT
19
+ line on that same axis, one added level per doubling of n).
20
+
21
+ v0.1.0 ships its first member, BinaryHeap. The planned roster (one member per
22
+ session, each landing append-only):
23
+
24
+ - BinaryHeap (v0.1.0) -- an INDEXED binary heap (addressable priority queue): a
25
+ min|max binary heap over three parallel typed arrays (`_key` Float64Array,
26
+ `_id` Uint32Array, `_pos` Int32Array reverse map) with children at 2i+1 / 2i+2.
27
+ push / pop / changeKey / remove are O(log n); peek / topKey / keyOf / has are
28
+ O(1); nothing allocated per op. The reverse-index map makes changeKey / remove
29
+ addressable by a caller-supplied entity id. The cleanest witness in the family
30
+ and the one that calibrates the shared R^2 floor + slope band gate (pop).
31
+ - Fenwick / BIT (v0.2.0) -- BOTH point-update AND prefix-sum in O(log n) over a
32
+ single flat array via the lowest-set-bit walk (`i & -i`); rangeSum is two
33
+ prefix queries. Two straight log lines.
34
+ - SegmentTree (v0.3.0) -- general associative range-query (min / max / sum / gcd)
35
+ + point-update over a flat, array-embedded tree, O(log n) worst-case; the fold
36
+ is chosen once at construction.
37
+ - SkipList (v0.4.0) -- the ordered map (get / set / delete / successor /
38
+ rangeIter), pointer-free over a shared node pool (parallel Uint32Array `next`
39
+ columns), expected O(log n), deterministic seed, tail reported.
40
+
41
+ ## Exports (from the single main file LogN.js)
42
+
43
+ - `VERSION` -- string. The package version; one of the three version sites
44
+ (package.json / LogN.js VERSION const / this file's `Version:` header).
45
+ - `BinaryHeap` -- class. An indexed binary heap (addressable priority queue).
46
+ - `new BinaryHeap(capacity, kind = 'min')` -- capacity is an integer in
47
+ [1, 2^31-1]; kind is 'min' or 'max'. Allocates the three typed arrays once.
48
+ - `push(id, key)` -> void. id integer in [0, capacity), not already present;
49
+ key finite. Throws on out-of-range/duplicate id, non-finite key, full heap.
50
+ - `pop()` -> id | undefined. Removes the extremum; undefined if empty (no throw).
51
+ - `peek()` -> id | undefined. `topKey()` -> key | undefined. Read-only.
52
+ - `keyOf(id)` -> key | undefined. `has(id)` -> boolean. Out-of-range id throws.
53
+ - `changeKey(id, newKey)` -> void. Reprioritize a present entity (auto-direction
54
+ sift); a non-member id throws (never a silent no-op).
55
+ - `remove(id)` -> boolean. Idempotent: false if absent, true if removed.
56
+ - `size` / `capacity` / `kind` getters; `clear()`; `forEach(fn)` and
57
+ `[Symbol.iterator]()` yield live ids in UNSPECIFIED (heap-array) order -- NOT
58
+ sorted / pop order.
59
+ - `BinaryHeap.build(kind, ids, keys, capacity)` -> BinaryHeap. Floyd O(n) bulk
60
+ build from parallel arrays; fails closed on duplicate/out-of-range id,
61
+ non-finite key, or count > capacity.
62
+
63
+ Member exports (one tree-shakeable class each) are appended here as each member
64
+ ships.
65
+
66
+ ## Design bounds
67
+
68
+ - Zero runtime deps; node:test only; ASCII-only source.
69
+ - Single PascalCase main file LogN.js; sideEffects: false; tree-shakeable named
70
+ exports, no barrel.
71
+ - Fixed, preallocated capacity (node / element count, not bytes). Fail closed on
72
+ every unverified state; `null` is not zero.
73
+ - typeof-guard FIRST, before any coercion, at the door of every mutating op.
74
+ - Zero allocation on every steady-state hot path (0 B/op), proven by
75
+ `node --expose-gc test/torture.mjs`.
76
+
77
+ ## Cross-package boundaries
78
+
79
+ - @zakkster/lite-o1 -- the O(1) sibling and intended pair. Any BOUNDED-INTEGER
80
+ priority queue is O(1) and belongs there (Dial's bucket queue); lite-logn's
81
+ heap is the GENERAL comparator PQ at O(log n).
82
+ - @zakkster/lite-filter -- owns approximate / probabilistic membership;
83
+ lite-logn owns exact ordered structures. No overlap.
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@zakkster/lite-logn",
3
+ "author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
4
+ "version": "0.1.0",
5
+ "description": "Zero-dependency, zero-GC family of O(log n) data structures that proves its logarithm: BinaryHeap (array-embedded O(log n) push/pop min-heap), Fenwick/BIT (O(log n) point-update AND prefix-sum via the i & -i walk), SegmentTree (O(log n) associative range-query + point-update), and SkipList (pointer-free expected-O(log n) ordered map) -- planned -- with a log-linear O(log n) Witness harness that fits nsPerOp = intercept + slope*log2(n) and shows the straight log line while an O(n) foil leaves it. Tree-shakeable named exports.",
6
+ "type": "module",
7
+ "main": "./LogN.js",
8
+ "module": "./LogN.js",
9
+ "types": "./LogN.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./LogN.d.ts",
13
+ "node": "./LogN.js",
14
+ "import": "./LogN.js",
15
+ "default": "./LogN.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "LogN.js",
20
+ "LogN.d.ts",
21
+ "llms.txt",
22
+ "README.md",
23
+ "CHANGELOG.md",
24
+ "LICENSE"
25
+ ],
26
+ "scripts": {
27
+ "test": "node --test test/*.test.js test/Bench.test.mjs",
28
+ "test:types": "tsc -p test/types/tsconfig.json",
29
+ "torture": "node --expose-gc test/torture.mjs",
30
+ "witness": "node test/witness.mjs",
31
+ "test:perf": "node --expose-gc --max-semi-space-size=4 --test test/perf/PerfGate.test.mjs",
32
+ "bench": "node benchmark/Bench.mjs",
33
+ "bench:report": "node benchmark/Bench.mjs && node benchmark/Report.mjs",
34
+ "verify": "npm test && npm run test:types && npm run torture && npm run witness && npm run test:perf"
35
+ },
36
+ "keywords": [
37
+ "binary-heap",
38
+ "heap",
39
+ "priority-queue",
40
+ "d-ary-heap",
41
+ "fenwick",
42
+ "bit",
43
+ "binary-indexed-tree",
44
+ "prefix-sum",
45
+ "segment-tree",
46
+ "range-query",
47
+ "range-sum",
48
+ "skip-list",
49
+ "ordered-set",
50
+ "balanced-bst",
51
+ "bst",
52
+ "log-n",
53
+ "logarithmic",
54
+ "order-statistics",
55
+ "rank-select",
56
+ "data-structures",
57
+ "zero-gc",
58
+ "gc",
59
+ "typed-array",
60
+ "soa",
61
+ "tree-shakeable",
62
+ "witness",
63
+ "performance",
64
+ "lightweight",
65
+ "zero-dependency"
66
+ ],
67
+ "license": "MIT",
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "devDependencies": {
72
+ "@zakkster/lite-gc-profiler": "^1.16.0",
73
+ "@zakkster/lite-leak": "^1.10.0",
74
+ "@zakkster/lite-perf-gate": "^1.4.2",
75
+ "esbuild": "^0.28.2",
76
+ "typescript": "^7.0.2"
77
+ },
78
+ "homepage": "https://github.com/PeshoVurtoleta/lite-logn#readme",
79
+ "repository": {
80
+ "type": "git",
81
+ "url": "git+https://github.com/PeshoVurtoleta/lite-logn.git"
82
+ },
83
+ "bugs": {
84
+ "url": "https://github.com/PeshoVurtoleta/lite-logn/issues",
85
+ "email": "shinikchiev@yahoo.com"
86
+ },
87
+ "engines": {
88
+ "node": ">=18"
89
+ },
90
+ "funding": {
91
+ "type": "github",
92
+ "url": "https://github.com/sponsors/PeshoVurtoleta"
93
+ },
94
+ "sideEffects": false
95
+ }