@llblab/uniqueue 1.0.0 → 1.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/README.md +24 -14
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +147 -0
- package/package.json +15 -6
- package/index.js +0 -245
package/README.md
CHANGED
|
@@ -10,7 +10,8 @@ Combines a binary heap with a key-to-index `Map`, enabling O(log n) inserts/upda
|
|
|
10
10
|
- **In-Place Updates**: If an item with the same key is pushed, it updates the existing entry in-place (bubbling up or down as needed).
|
|
11
11
|
- **O(1) Lookup**: Tracks item positions internally, avoiding O(n) scans for updates.
|
|
12
12
|
- **Max Size Eviction**: Automatically removes the lowest-priority item when the limit is reached.
|
|
13
|
-
- **
|
|
13
|
+
- **Iterable**: Supports `for...of` iteration over items.
|
|
14
|
+
- **Zero Dependencies**: ~1KB minified.
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
17
|
|
|
@@ -23,7 +24,7 @@ npm install @llblab/uniqueue
|
|
|
23
24
|
### Basic Example (Max Heap)
|
|
24
25
|
|
|
25
26
|
```javascript
|
|
26
|
-
import { UniQueue } from "uniqueue";
|
|
27
|
+
import { UniQueue } from "@llblab/uniqueue";
|
|
27
28
|
|
|
28
29
|
// Create a max-heap for leaderboard scores
|
|
29
30
|
const leaderboard = new UniQueue({
|
|
@@ -50,6 +51,11 @@ leaderboard.push({ playerId: "dave", score: 200 });
|
|
|
50
51
|
|
|
51
52
|
console.log(leaderboard.data);
|
|
52
53
|
// Contains dave (200), alice (150), charlie (120)
|
|
54
|
+
|
|
55
|
+
// Iterate over all items
|
|
56
|
+
for (const player of leaderboard) {
|
|
57
|
+
console.log(player.playerId, player.score);
|
|
58
|
+
}
|
|
53
59
|
```
|
|
54
60
|
|
|
55
61
|
## API
|
|
@@ -60,21 +66,21 @@ Creates a new priority queue instance.
|
|
|
60
66
|
|
|
61
67
|
#### Options
|
|
62
68
|
|
|
63
|
-
| Option | Type | Default
|
|
64
|
-
| :----------- | :----------------- |
|
|
65
|
-
| `data` | `
|
|
66
|
-
| `maxSize` | `number` | `Infinity`
|
|
67
|
-
| `compare` | `(a, b) => number` | `(a, b) => a
|
|
68
|
-
| `extractKey` | `(item) => string` | `(item) => item`
|
|
69
|
+
| Option | Type | Default | Description |
|
|
70
|
+
| :----------- | :----------------- | :--------------------------------------- | :--------------------------------------------------------------------- |
|
|
71
|
+
| `data` | `T[]` | `[]` | Initial data array. |
|
|
72
|
+
| `maxSize` | `number` | `Infinity` | Maximum number of items. If exceeded, lowest priority item is evicted. |
|
|
73
|
+
| `compare` | `(a, b) => number` | `(a, b) => (a < b ? -1 : a > b ? 1 : 0)` | Comparison function for heap ordering. |
|
|
74
|
+
| `extractKey` | `(item) => string` | `(item) => item` | Function to extract unique key string from item. |
|
|
69
75
|
|
|
70
76
|
### Instance Methods
|
|
71
77
|
|
|
72
|
-
#### `push(item: T):
|
|
78
|
+
#### `push(item: T): T | undefined`
|
|
73
79
|
|
|
74
80
|
Adds an item to the queue or updates an existing item with the same key.
|
|
75
81
|
|
|
76
|
-
- If the key is new: Adds item. If size > maxSize, removes and returns the
|
|
77
|
-
- If the key exists: Updates the existing item
|
|
82
|
+
- If the key is new: Adds item. If size > maxSize, removes and returns the evicted item.
|
|
83
|
+
- If the key exists: Updates the existing item and rebalances. Returns `undefined`.
|
|
78
84
|
|
|
79
85
|
#### `pop(): T | undefined`
|
|
80
86
|
|
|
@@ -86,11 +92,11 @@ Returns the highest priority item without removing it.
|
|
|
86
92
|
|
|
87
93
|
#### `remove(key: string): boolean`
|
|
88
94
|
|
|
89
|
-
Removes the item with the given key
|
|
95
|
+
Removes the item with the given key. Returns `true` if removed, `false` otherwise.
|
|
90
96
|
|
|
91
97
|
#### `has(key: string): boolean`
|
|
92
98
|
|
|
93
|
-
Checks if an item with the given key exists
|
|
99
|
+
Checks if an item with the given key exists.
|
|
94
100
|
|
|
95
101
|
#### `get(key: string): T | undefined`
|
|
96
102
|
|
|
@@ -102,7 +108,11 @@ Removes all items from the queue.
|
|
|
102
108
|
|
|
103
109
|
#### `size: number`
|
|
104
110
|
|
|
105
|
-
Getter property that returns the number of items
|
|
111
|
+
Getter property that returns the number of items.
|
|
112
|
+
|
|
113
|
+
#### `[Symbol.iterator](): IterableIterator<T>`
|
|
114
|
+
|
|
115
|
+
Iterates over items in heap order (not sorted).
|
|
106
116
|
|
|
107
117
|
## Complexity
|
|
108
118
|
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface UniQueueOptions<T> {
|
|
2
|
+
data?: T[];
|
|
3
|
+
maxSize?: number;
|
|
4
|
+
compare?: (a: T, b: T) => number;
|
|
5
|
+
extractKey?: (item: T) => string;
|
|
6
|
+
}
|
|
7
|
+
export declare class UniQueue<T> {
|
|
8
|
+
#private;
|
|
9
|
+
data: T[];
|
|
10
|
+
indexes: Map<string, number>;
|
|
11
|
+
constructor({ data, maxSize, compare, extractKey, }?: UniQueueOptions<T>);
|
|
12
|
+
push(item: T): T | undefined;
|
|
13
|
+
pop(): T | undefined;
|
|
14
|
+
peek(): T | undefined;
|
|
15
|
+
remove(key: string): boolean;
|
|
16
|
+
has(key: string): boolean;
|
|
17
|
+
get(key: string): T | undefined;
|
|
18
|
+
clear(): void;
|
|
19
|
+
get size(): number;
|
|
20
|
+
[Symbol.iterator](): IterableIterator<T>;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IACX,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC;IACjC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;CAClC;AAED,qBAAa,QAAQ,CAAC,CAAC;;IACrB,IAAI,EAAE,CAAC,EAAE,CAAC;IACV,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;gBAMjB,EACV,IAAS,EACT,OAAkB,EAClB,OAAgD,EAChD,UAAgD,GACjD,GAAE,eAAe,CAAC,CAAC,CAAM;IAkE1B,IAAI,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAsB5B,GAAG,IAAI,CAAC,GAAG,SAAS;IAgBpB,IAAI,IAAI,CAAC,GAAG,SAAS;IAIrB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IA0B5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAK/B,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI,MAAM,CAEjB;IAEA,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC;CAG1C"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _UniQueue_instances, _UniQueue_maxSize, _UniQueue_compare, _UniQueue_extractKey, _UniQueue_siftUp, _UniQueue_siftDown;
|
|
13
|
+
export class UniQueue {
|
|
14
|
+
constructor({ data = [], maxSize = Infinity, compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0), extractKey = (item) => item, } = {}) {
|
|
15
|
+
_UniQueue_instances.add(this);
|
|
16
|
+
_UniQueue_maxSize.set(this, void 0);
|
|
17
|
+
_UniQueue_compare.set(this, void 0);
|
|
18
|
+
_UniQueue_extractKey.set(this, void 0);
|
|
19
|
+
this.data = data;
|
|
20
|
+
this.indexes = new Map(data.map((item, index) => [extractKey(item), index]));
|
|
21
|
+
__classPrivateFieldSet(this, _UniQueue_maxSize, maxSize, "f");
|
|
22
|
+
__classPrivateFieldSet(this, _UniQueue_compare, compare, "f");
|
|
23
|
+
__classPrivateFieldSet(this, _UniQueue_extractKey, extractKey, "f");
|
|
24
|
+
if (data.length > 0) {
|
|
25
|
+
for (let i = (data.length >>> 1) - 1; i >= 0; i--) {
|
|
26
|
+
__classPrivateFieldGet(this, _UniQueue_instances, "m", _UniQueue_siftDown).call(this, i);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
push(item) {
|
|
31
|
+
const key = __classPrivateFieldGet(this, _UniQueue_extractKey, "f").call(this, item);
|
|
32
|
+
const index = this.indexes.get(key);
|
|
33
|
+
if (index === undefined) {
|
|
34
|
+
this.data.push(item);
|
|
35
|
+
__classPrivateFieldGet(this, _UniQueue_instances, "m", _UniQueue_siftUp).call(this, this.data.length - 1);
|
|
36
|
+
if (this.data.length <= __classPrivateFieldGet(this, _UniQueue_maxSize, "f"))
|
|
37
|
+
return;
|
|
38
|
+
return this.pop();
|
|
39
|
+
}
|
|
40
|
+
const oldItem = this.data[index];
|
|
41
|
+
this.data[index] = item;
|
|
42
|
+
const cmp = __classPrivateFieldGet(this, _UniQueue_compare, "f").call(this, oldItem, item);
|
|
43
|
+
if (cmp < 0) {
|
|
44
|
+
__classPrivateFieldGet(this, _UniQueue_instances, "m", _UniQueue_siftDown).call(this, index);
|
|
45
|
+
}
|
|
46
|
+
else if (cmp > 0) {
|
|
47
|
+
__classPrivateFieldGet(this, _UniQueue_instances, "m", _UniQueue_siftUp).call(this, index);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
pop() {
|
|
51
|
+
if (this.data.length === 0)
|
|
52
|
+
return;
|
|
53
|
+
const top = this.data[0];
|
|
54
|
+
this.indexes.delete(__classPrivateFieldGet(this, _UniQueue_extractKey, "f").call(this, top));
|
|
55
|
+
const bottom = this.data.pop();
|
|
56
|
+
if (this.data.length > 0 && bottom !== undefined) {
|
|
57
|
+
this.indexes.set(__classPrivateFieldGet(this, _UniQueue_extractKey, "f").call(this, bottom), 0);
|
|
58
|
+
this.data[0] = bottom;
|
|
59
|
+
__classPrivateFieldGet(this, _UniQueue_instances, "m", _UniQueue_siftDown).call(this, 0);
|
|
60
|
+
}
|
|
61
|
+
return top;
|
|
62
|
+
}
|
|
63
|
+
peek() {
|
|
64
|
+
return this.data[0];
|
|
65
|
+
}
|
|
66
|
+
remove(key) {
|
|
67
|
+
const index = this.indexes.get(key);
|
|
68
|
+
if (index === undefined)
|
|
69
|
+
return false;
|
|
70
|
+
const lastIndex = this.data.length - 1;
|
|
71
|
+
if (index === lastIndex) {
|
|
72
|
+
this.indexes.delete(key);
|
|
73
|
+
this.data.pop();
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const item = this.data.pop();
|
|
77
|
+
this.indexes.delete(key);
|
|
78
|
+
this.indexes.set(__classPrivateFieldGet(this, _UniQueue_extractKey, "f").call(this, item), index);
|
|
79
|
+
this.data[index] = item;
|
|
80
|
+
const parentIndex = (index - 1) >>> 1;
|
|
81
|
+
if (index > 0 && __classPrivateFieldGet(this, _UniQueue_compare, "f").call(this, item, this.data[parentIndex]) < 0) {
|
|
82
|
+
__classPrivateFieldGet(this, _UniQueue_instances, "m", _UniQueue_siftUp).call(this, index);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
__classPrivateFieldGet(this, _UniQueue_instances, "m", _UniQueue_siftDown).call(this, index);
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
has(key) {
|
|
90
|
+
return this.indexes.has(key);
|
|
91
|
+
}
|
|
92
|
+
get(key) {
|
|
93
|
+
const index = this.indexes.get(key);
|
|
94
|
+
return index !== undefined ? this.data[index] : undefined;
|
|
95
|
+
}
|
|
96
|
+
clear() {
|
|
97
|
+
this.data = [];
|
|
98
|
+
this.indexes.clear();
|
|
99
|
+
}
|
|
100
|
+
get size() {
|
|
101
|
+
return this.data.length;
|
|
102
|
+
}
|
|
103
|
+
*[(_UniQueue_maxSize = new WeakMap(), _UniQueue_compare = new WeakMap(), _UniQueue_extractKey = new WeakMap(), _UniQueue_instances = new WeakSet(), _UniQueue_siftUp = function _UniQueue_siftUp(pos) {
|
|
104
|
+
const { data, indexes } = this;
|
|
105
|
+
const compare = __classPrivateFieldGet(this, _UniQueue_compare, "f");
|
|
106
|
+
const extractKey = __classPrivateFieldGet(this, _UniQueue_extractKey, "f");
|
|
107
|
+
const item = data[pos];
|
|
108
|
+
while (pos > 0) {
|
|
109
|
+
const parentIndex = (pos - 1) >>> 1;
|
|
110
|
+
const parent = data[parentIndex];
|
|
111
|
+
if (compare(item, parent) >= 0)
|
|
112
|
+
break;
|
|
113
|
+
indexes.set(extractKey(parent), pos);
|
|
114
|
+
data[pos] = parent;
|
|
115
|
+
pos = parentIndex;
|
|
116
|
+
}
|
|
117
|
+
indexes.set(extractKey(item), pos);
|
|
118
|
+
data[pos] = item;
|
|
119
|
+
}, _UniQueue_siftDown = function _UniQueue_siftDown(pos) {
|
|
120
|
+
const { data, indexes } = this;
|
|
121
|
+
const compare = __classPrivateFieldGet(this, _UniQueue_compare, "f");
|
|
122
|
+
const extractKey = __classPrivateFieldGet(this, _UniQueue_extractKey, "f");
|
|
123
|
+
const item = data[pos];
|
|
124
|
+
const halfLength = data.length >>> 1;
|
|
125
|
+
while (pos < halfLength) {
|
|
126
|
+
let leftIndex = (pos << 1) + 1;
|
|
127
|
+
let best = data[leftIndex];
|
|
128
|
+
const rightIndex = leftIndex + 1;
|
|
129
|
+
if (rightIndex < data.length) {
|
|
130
|
+
const right = data[rightIndex];
|
|
131
|
+
if (compare(right, best) < 0) {
|
|
132
|
+
leftIndex = rightIndex;
|
|
133
|
+
best = right;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (compare(best, item) >= 0)
|
|
137
|
+
break;
|
|
138
|
+
indexes.set(extractKey(best), pos);
|
|
139
|
+
data[pos] = best;
|
|
140
|
+
pos = leftIndex;
|
|
141
|
+
}
|
|
142
|
+
indexes.set(extractKey(item), pos);
|
|
143
|
+
data[pos] = item;
|
|
144
|
+
}, Symbol.iterator)]() {
|
|
145
|
+
yield* this.data;
|
|
146
|
+
}
|
|
147
|
+
}
|
package/package.json
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/uniqueue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "High-performance priority queue with unique key constraint (O(1) lookup, O(log n) update)",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
9
13
|
},
|
|
10
14
|
"files": [
|
|
11
|
-
"
|
|
15
|
+
"dist",
|
|
12
16
|
"README.md"
|
|
13
17
|
],
|
|
14
18
|
"repository": "github:llblab/uniqueue",
|
|
15
19
|
"scripts": {
|
|
16
|
-
"
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"prepack": "npm run build",
|
|
22
|
+
"test": "npm run build && node --test test.js"
|
|
17
23
|
},
|
|
18
24
|
"keywords": [
|
|
19
25
|
"priority-queue",
|
|
@@ -27,5 +33,8 @@
|
|
|
27
33
|
],
|
|
28
34
|
"author": "LLB <shlavik@gmail.com>",
|
|
29
35
|
"license": "MIT",
|
|
30
|
-
"sideEffects": false
|
|
36
|
+
"sideEffects": false,
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"typescript": "^5.9.3"
|
|
39
|
+
}
|
|
31
40
|
}
|
package/index.js
DELETED
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @template T
|
|
3
|
-
* @typedef {Object} UniQueueOptions
|
|
4
|
-
* @property {T[]} [data] - Initial data array
|
|
5
|
-
* @property {number} [maxSize] - Maximum queue size (default: Infinity)
|
|
6
|
-
* @property {(a: T, b: T) => number} [compare] - Comparison function for heap ordering
|
|
7
|
-
* @property {(item: T) => string} [extractKey] - Function to extract unique key from item
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Priority queue with unique key constraint.
|
|
12
|
-
* Combines a min-heap with a key-to-index map for O(log n) push/pop with deduplication.
|
|
13
|
-
*
|
|
14
|
-
* @template T
|
|
15
|
-
*/
|
|
16
|
-
export class UniQueue {
|
|
17
|
-
/** @type {T[]} */
|
|
18
|
-
data;
|
|
19
|
-
|
|
20
|
-
/** @type {Map<string, number>} */
|
|
21
|
-
indexes;
|
|
22
|
-
|
|
23
|
-
/** @type {number} */
|
|
24
|
-
#maxSize;
|
|
25
|
-
|
|
26
|
-
/** @type {(a: T, b: T) => number} */
|
|
27
|
-
#compare;
|
|
28
|
-
|
|
29
|
-
/** @type {(item: T) => string} */
|
|
30
|
-
#extractKey;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {UniQueueOptions<T>} [options]
|
|
34
|
-
*/
|
|
35
|
-
constructor({
|
|
36
|
-
data = [],
|
|
37
|
-
maxSize = Infinity,
|
|
38
|
-
compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0),
|
|
39
|
-
extractKey = (item) =>
|
|
40
|
-
/** @type {string} */ (/** @type {unknown} */ (item)),
|
|
41
|
-
} = {}) {
|
|
42
|
-
this.data = data;
|
|
43
|
-
this.indexes = new Map(
|
|
44
|
-
data.map((item, index) => [extractKey(item), index]),
|
|
45
|
-
);
|
|
46
|
-
this.#maxSize = maxSize;
|
|
47
|
-
this.#compare = compare;
|
|
48
|
-
this.#extractKey = extractKey;
|
|
49
|
-
|
|
50
|
-
if (data.length > 0) {
|
|
51
|
-
for (let i = (data.length >>> 1) - 1; i >= 0; i--) {
|
|
52
|
-
this.#siftDown(i);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Move item up the heap to maintain heap property.
|
|
59
|
-
* @param {number} pos
|
|
60
|
-
*/
|
|
61
|
-
#siftUp(pos) {
|
|
62
|
-
const { data, indexes } = this;
|
|
63
|
-
const compare = this.#compare;
|
|
64
|
-
const extractKey = this.#extractKey;
|
|
65
|
-
const item = data[pos];
|
|
66
|
-
|
|
67
|
-
while (pos > 0) {
|
|
68
|
-
const parentIndex = (pos - 1) >>> 1;
|
|
69
|
-
const parent = data[parentIndex];
|
|
70
|
-
if (compare(item, parent) >= 0) break;
|
|
71
|
-
indexes.set(extractKey(parent), pos);
|
|
72
|
-
data[pos] = parent;
|
|
73
|
-
pos = parentIndex;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
indexes.set(extractKey(item), pos);
|
|
77
|
-
data[pos] = item;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Move item down the heap to maintain heap property.
|
|
82
|
-
* @param {number} pos
|
|
83
|
-
*/
|
|
84
|
-
#siftDown(pos) {
|
|
85
|
-
const { data, indexes } = this;
|
|
86
|
-
const compare = this.#compare;
|
|
87
|
-
const extractKey = this.#extractKey;
|
|
88
|
-
const item = data[pos];
|
|
89
|
-
const halfLength = data.length >>> 1;
|
|
90
|
-
|
|
91
|
-
while (pos < halfLength) {
|
|
92
|
-
let leftIndex = (pos << 1) + 1;
|
|
93
|
-
let best = data[leftIndex];
|
|
94
|
-
const rightIndex = leftIndex + 1;
|
|
95
|
-
|
|
96
|
-
if (rightIndex < data.length) {
|
|
97
|
-
const right = data[rightIndex];
|
|
98
|
-
if (compare(right, best) < 0) {
|
|
99
|
-
leftIndex = rightIndex;
|
|
100
|
-
best = right;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (compare(best, item) >= 0) break;
|
|
105
|
-
|
|
106
|
-
indexes.set(extractKey(best), pos);
|
|
107
|
-
data[pos] = best;
|
|
108
|
-
pos = leftIndex;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
indexes.set(extractKey(item), pos);
|
|
112
|
-
data[pos] = item;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Add or update an item in the queue.
|
|
117
|
-
* - If key exists: Updates item and rebalances (unconditional update).
|
|
118
|
-
* - If key new: Adds item. If size > maxSize, evicts and returns min item.
|
|
119
|
-
* @param {T} item
|
|
120
|
-
* @returns {T | undefined} Evicted item if queue was full
|
|
121
|
-
*/
|
|
122
|
-
push(item) {
|
|
123
|
-
const key = this.#extractKey(item);
|
|
124
|
-
const index = this.indexes.get(key);
|
|
125
|
-
|
|
126
|
-
if (index === undefined) {
|
|
127
|
-
this.data.push(item);
|
|
128
|
-
this.#siftUp(this.data.length - 1);
|
|
129
|
-
if (this.data.length <= this.#maxSize) return;
|
|
130
|
-
return this.pop();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const oldItem = this.data[index];
|
|
134
|
-
this.data[index] = item;
|
|
135
|
-
const cmp = this.#compare(oldItem, item);
|
|
136
|
-
|
|
137
|
-
if (cmp < 0) {
|
|
138
|
-
this.#siftDown(index);
|
|
139
|
-
} else if (cmp > 0) {
|
|
140
|
-
this.#siftUp(index);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Remove and return the top (minimum) item.
|
|
146
|
-
* @returns {T | undefined}
|
|
147
|
-
*/
|
|
148
|
-
pop() {
|
|
149
|
-
if (this.data.length === 0) return;
|
|
150
|
-
|
|
151
|
-
const top = this.data[0];
|
|
152
|
-
this.indexes.delete(this.#extractKey(top));
|
|
153
|
-
|
|
154
|
-
const bottom = this.data.pop();
|
|
155
|
-
if (this.data.length > 0 && bottom !== undefined) {
|
|
156
|
-
this.indexes.set(this.#extractKey(bottom), 0);
|
|
157
|
-
this.data[0] = bottom;
|
|
158
|
-
this.#siftDown(0);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return top;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Return the top (minimum) item without removing it.
|
|
166
|
-
* @returns {T | undefined}
|
|
167
|
-
*/
|
|
168
|
-
peek() {
|
|
169
|
-
return this.data[0];
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Remove an item by key.
|
|
174
|
-
* @param {string} key
|
|
175
|
-
* @returns {boolean} true if item was removed
|
|
176
|
-
*/
|
|
177
|
-
remove(key) {
|
|
178
|
-
const index = this.indexes.get(key);
|
|
179
|
-
if (index === undefined) return false;
|
|
180
|
-
|
|
181
|
-
const lastIndex = this.data.length - 1;
|
|
182
|
-
if (index === lastIndex) {
|
|
183
|
-
this.indexes.delete(key);
|
|
184
|
-
this.data.pop();
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const item = /** @type {T} */ (this.data.pop());
|
|
189
|
-
this.indexes.delete(key);
|
|
190
|
-
this.indexes.set(this.#extractKey(item), index);
|
|
191
|
-
this.data[index] = item;
|
|
192
|
-
|
|
193
|
-
const parentIndex = (index - 1) >>> 1;
|
|
194
|
-
if (index > 0 && this.#compare(item, this.data[parentIndex]) < 0) {
|
|
195
|
-
this.#siftUp(index);
|
|
196
|
-
} else {
|
|
197
|
-
this.#siftDown(index);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return true;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Check if an item exists.
|
|
205
|
-
* @param {string} key
|
|
206
|
-
* @returns {boolean}
|
|
207
|
-
*/
|
|
208
|
-
has(key) {
|
|
209
|
-
return this.indexes.has(key);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Get an item by key.
|
|
214
|
-
* @param {string} key
|
|
215
|
-
* @returns {T | undefined}
|
|
216
|
-
*/
|
|
217
|
-
get(key) {
|
|
218
|
-
const index = this.indexes.get(key);
|
|
219
|
-
return index !== undefined ? this.data[index] : undefined;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Remove all items.
|
|
224
|
-
*/
|
|
225
|
-
clear() {
|
|
226
|
-
this.data = [];
|
|
227
|
-
this.indexes.clear();
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Get item count.
|
|
232
|
-
* @returns {number}
|
|
233
|
-
*/
|
|
234
|
-
get size() {
|
|
235
|
-
return this.data.length;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Iterate over items (arbitrary heap order).
|
|
240
|
-
* @returns {IterableIterator<T>}
|
|
241
|
-
*/
|
|
242
|
-
*[Symbol.iterator]() {
|
|
243
|
-
yield* this.data;
|
|
244
|
-
}
|
|
245
|
-
}
|