@llblab/uniqueue 1.2.1 → 1.4.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 +15 -7
- package/dist/index.d.ts +8 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -38
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -19,6 +19,10 @@ Combines a binary heap with a key-to-index `Map`, enabling O(log n) inserts/upda
|
|
|
19
19
|
npm install @llblab/uniqueue
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
```bash
|
|
23
|
+
deno add jsr:@llblab/uniqueue
|
|
24
|
+
```
|
|
25
|
+
|
|
22
26
|
## Usage
|
|
23
27
|
|
|
24
28
|
### Basic Example (Top-N Leaderboard)
|
|
@@ -34,8 +38,8 @@ import { Uniqueue } from "@llblab/uniqueue";
|
|
|
34
38
|
// the root (smallest/worst) is evicted, keeping the best scores.
|
|
35
39
|
const leaderboard = new Uniqueue({
|
|
36
40
|
compare: (a, b) => a.score - b.score,
|
|
37
|
-
extractKey: (item) => item.playerId,
|
|
38
|
-
maxSize: 3,
|
|
41
|
+
extractKey: (item) => item.playerId,
|
|
42
|
+
maxSize: 3,
|
|
39
43
|
});
|
|
40
44
|
|
|
41
45
|
// Add items
|
|
@@ -53,7 +57,7 @@ leaderboard.push({ playerId: "alice", score: 150 });
|
|
|
53
57
|
leaderboard.push({ playerId: "dave", score: 200 });
|
|
54
58
|
// Bob (lowest score at root) is evicted
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
leaderboard.snapshot();
|
|
57
61
|
// Contains charlie (120), alice (150), dave (200)
|
|
58
62
|
|
|
59
63
|
// Iterate over all items
|
|
@@ -75,7 +79,7 @@ Creates a new priority queue instance.
|
|
|
75
79
|
| `data` | `T[]` | `[]` | Initial data array. |
|
|
76
80
|
| `maxSize` | `number` | `Infinity` | Maximum number of items. If exceeded, lowest priority item is evicted. |
|
|
77
81
|
| `compare` | `(a, b) => number` | `(a, b) => (a < b ? -1 : a > b ? 1 : 0)` | Comparison function for heap ordering. |
|
|
78
|
-
| `extractKey` | `(item) =>
|
|
82
|
+
| `extractKey` | `(item) => K` | `(item) => item` | Function to extract unique key any type from item. |
|
|
79
83
|
|
|
80
84
|
### Instance Methods
|
|
81
85
|
|
|
@@ -94,15 +98,15 @@ Removes and returns the highest priority item (the root of the heap).
|
|
|
94
98
|
|
|
95
99
|
Returns the highest priority item without removing it.
|
|
96
100
|
|
|
97
|
-
#### `remove(key:
|
|
101
|
+
#### `remove(key: K): boolean`
|
|
98
102
|
|
|
99
103
|
Removes the item with the given key. Returns `true` if removed, `false` otherwise.
|
|
100
104
|
|
|
101
|
-
#### `has(key:
|
|
105
|
+
#### `has(key: K): boolean`
|
|
102
106
|
|
|
103
107
|
Checks if an item with the given key exists.
|
|
104
108
|
|
|
105
|
-
#### `get(key:
|
|
109
|
+
#### `get(key: K): T | undefined`
|
|
106
110
|
|
|
107
111
|
Returns the item with the given key without removing it.
|
|
108
112
|
|
|
@@ -114,6 +118,10 @@ Removes all items from the queue.
|
|
|
114
118
|
|
|
115
119
|
Getter property that returns the number of items.
|
|
116
120
|
|
|
121
|
+
#### `snapshot(): T[]`
|
|
122
|
+
|
|
123
|
+
Returns a shallow copy of the underlying heap array. Safe for sorting or other mutations without affecting the queue.
|
|
124
|
+
|
|
117
125
|
#### `[Symbol.iterator](): IterableIterator<T>`
|
|
118
126
|
|
|
119
127
|
Iterates over items in heap order (not sorted).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
export interface UniqueueOptions<T> {
|
|
1
|
+
export interface UniqueueOptions<T, K = string> {
|
|
2
2
|
data?: T[];
|
|
3
3
|
maxSize?: number;
|
|
4
4
|
compare?: (a: T, b: T) => number;
|
|
5
|
-
extractKey?: (item: T) =>
|
|
5
|
+
extractKey?: (item: T) => K;
|
|
6
6
|
}
|
|
7
|
-
export declare class Uniqueue<T> {
|
|
7
|
+
export declare class Uniqueue<T, K = string> {
|
|
8
8
|
#private;
|
|
9
|
-
data
|
|
10
|
-
indexes: Map<string, number>;
|
|
11
|
-
constructor({ data, maxSize, compare, extractKey, }?: UniqueueOptions<T>);
|
|
9
|
+
constructor({ data, maxSize, compare, extractKey, }?: UniqueueOptions<T, K>);
|
|
12
10
|
push(item: T): T | undefined;
|
|
13
11
|
pop(): T | undefined;
|
|
14
12
|
peek(): T | undefined;
|
|
15
|
-
remove(key:
|
|
16
|
-
has(key:
|
|
17
|
-
get(key:
|
|
13
|
+
remove(key: K): boolean;
|
|
14
|
+
has(key: K): boolean;
|
|
15
|
+
get(key: K): T | undefined;
|
|
18
16
|
clear(): void;
|
|
19
17
|
get size(): number;
|
|
18
|
+
snapshot(): T[];
|
|
20
19
|
[Symbol.iterator](): IterableIterator<T>;
|
|
21
20
|
}
|
|
22
21
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,eAAe,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM;IAC5C,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,CAAC,CAAC;CAC7B;AAED,qBAAa,QAAQ,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM;;gBAOrB,EACV,IAAS,EACT,OAAkB,EAClB,OAAgD,EAChD,UAA2C,GAC5C,GAAE,eAAe,CAAC,CAAC,EAAE,CAAC,CAAM;IA0E7B,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,CAAC,GAAG,OAAO;IA0BvB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,OAAO;IAIpB,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAK1B,KAAK,IAAI,IAAI;IAKb,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,QAAQ,IAAI,CAAC,EAAE;IAId,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,CAAC,CAAC;CAG1C"}
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
export class Uniqueue {
|
|
2
|
-
data;
|
|
3
|
-
indexes;
|
|
2
|
+
#data;
|
|
3
|
+
#indexes;
|
|
4
4
|
#maxSize;
|
|
5
5
|
#compare;
|
|
6
6
|
#extractKey;
|
|
7
7
|
constructor({ data = [], maxSize = Infinity, compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0), extractKey = (item) => item, } = {}) {
|
|
8
|
-
this
|
|
9
|
-
this
|
|
8
|
+
this.#data = [];
|
|
9
|
+
this.#indexes = new Map();
|
|
10
10
|
this.#maxSize = maxSize;
|
|
11
11
|
this.#compare = compare;
|
|
12
12
|
this.#extractKey = extractKey;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
for (const item of data) {
|
|
14
|
+
const key = extractKey(item);
|
|
15
|
+
if (!this.#indexes.has(key)) {
|
|
16
|
+
this.#indexes.set(key, this.#data.length);
|
|
17
|
+
this.#data.push(item);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (this.#data.length > 0) {
|
|
21
|
+
for (let i = (this.#data.length >>> 1) - 1; i >= 0; i--) {
|
|
15
22
|
this.#siftDown(i);
|
|
16
23
|
}
|
|
17
24
|
}
|
|
18
25
|
}
|
|
19
26
|
#siftUp(pos) {
|
|
20
|
-
const
|
|
27
|
+
const data = this.#data;
|
|
28
|
+
const indexes = this.#indexes;
|
|
21
29
|
const compare = this.#compare;
|
|
22
30
|
const extractKey = this.#extractKey;
|
|
23
31
|
const item = data[pos];
|
|
@@ -34,7 +42,8 @@ export class Uniqueue {
|
|
|
34
42
|
data[pos] = item;
|
|
35
43
|
}
|
|
36
44
|
#siftDown(pos) {
|
|
37
|
-
const
|
|
45
|
+
const data = this.#data;
|
|
46
|
+
const indexes = this.#indexes;
|
|
38
47
|
const compare = this.#compare;
|
|
39
48
|
const extractKey = this.#extractKey;
|
|
40
49
|
const item = data[pos];
|
|
@@ -61,16 +70,16 @@ export class Uniqueue {
|
|
|
61
70
|
}
|
|
62
71
|
push(item) {
|
|
63
72
|
const key = this.#extractKey(item);
|
|
64
|
-
const index = this
|
|
73
|
+
const index = this.#indexes.get(key);
|
|
65
74
|
if (index === undefined) {
|
|
66
|
-
this
|
|
67
|
-
this.#siftUp(this
|
|
68
|
-
if (this
|
|
75
|
+
this.#data.push(item);
|
|
76
|
+
this.#siftUp(this.#data.length - 1);
|
|
77
|
+
if (this.#data.length <= this.#maxSize)
|
|
69
78
|
return;
|
|
70
79
|
return this.pop();
|
|
71
80
|
}
|
|
72
|
-
const oldItem = this
|
|
73
|
-
this
|
|
81
|
+
const oldItem = this.#data[index];
|
|
82
|
+
this.#data[index] = item;
|
|
74
83
|
const cmp = this.#compare(oldItem, item);
|
|
75
84
|
if (cmp < 0) {
|
|
76
85
|
this.#siftDown(index);
|
|
@@ -80,37 +89,37 @@ export class Uniqueue {
|
|
|
80
89
|
}
|
|
81
90
|
}
|
|
82
91
|
pop() {
|
|
83
|
-
if (this
|
|
92
|
+
if (this.#data.length === 0)
|
|
84
93
|
return;
|
|
85
|
-
const top = this
|
|
86
|
-
this
|
|
87
|
-
const bottom = this
|
|
88
|
-
if (this
|
|
89
|
-
this
|
|
90
|
-
this
|
|
94
|
+
const top = this.#data[0];
|
|
95
|
+
this.#indexes.delete(this.#extractKey(top));
|
|
96
|
+
const bottom = this.#data.pop();
|
|
97
|
+
if (this.#data.length > 0 && bottom !== undefined) {
|
|
98
|
+
this.#indexes.set(this.#extractKey(bottom), 0);
|
|
99
|
+
this.#data[0] = bottom;
|
|
91
100
|
this.#siftDown(0);
|
|
92
101
|
}
|
|
93
102
|
return top;
|
|
94
103
|
}
|
|
95
104
|
peek() {
|
|
96
|
-
return this
|
|
105
|
+
return this.#data[0];
|
|
97
106
|
}
|
|
98
107
|
remove(key) {
|
|
99
|
-
const index = this
|
|
108
|
+
const index = this.#indexes.get(key);
|
|
100
109
|
if (index === undefined)
|
|
101
110
|
return false;
|
|
102
|
-
const lastIndex = this
|
|
111
|
+
const lastIndex = this.#data.length - 1;
|
|
103
112
|
if (index === lastIndex) {
|
|
104
|
-
this
|
|
105
|
-
this
|
|
113
|
+
this.#indexes.delete(key);
|
|
114
|
+
this.#data.pop();
|
|
106
115
|
return true;
|
|
107
116
|
}
|
|
108
|
-
const item = this
|
|
109
|
-
this
|
|
110
|
-
this
|
|
111
|
-
this
|
|
117
|
+
const item = this.#data.pop();
|
|
118
|
+
this.#indexes.delete(key);
|
|
119
|
+
this.#indexes.set(this.#extractKey(item), index);
|
|
120
|
+
this.#data[index] = item;
|
|
112
121
|
const parentIndex = (index - 1) >>> 1;
|
|
113
|
-
if (index > 0 && this.#compare(item, this
|
|
122
|
+
if (index > 0 && this.#compare(item, this.#data[parentIndex]) < 0) {
|
|
114
123
|
this.#siftUp(index);
|
|
115
124
|
}
|
|
116
125
|
else {
|
|
@@ -119,20 +128,23 @@ export class Uniqueue {
|
|
|
119
128
|
return true;
|
|
120
129
|
}
|
|
121
130
|
has(key) {
|
|
122
|
-
return this
|
|
131
|
+
return this.#indexes.has(key);
|
|
123
132
|
}
|
|
124
133
|
get(key) {
|
|
125
|
-
const index = this
|
|
126
|
-
return index !== undefined ? this
|
|
134
|
+
const index = this.#indexes.get(key);
|
|
135
|
+
return index !== undefined ? this.#data[index] : undefined;
|
|
127
136
|
}
|
|
128
137
|
clear() {
|
|
129
|
-
this
|
|
130
|
-
this
|
|
138
|
+
this.#data = [];
|
|
139
|
+
this.#indexes.clear();
|
|
131
140
|
}
|
|
132
141
|
get size() {
|
|
133
|
-
return this
|
|
142
|
+
return this.#data.length;
|
|
143
|
+
}
|
|
144
|
+
snapshot() {
|
|
145
|
+
return [...this.#data];
|
|
134
146
|
}
|
|
135
147
|
*[Symbol.iterator]() {
|
|
136
|
-
yield* this
|
|
148
|
+
yield* this.#data;
|
|
137
149
|
}
|
|
138
150
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/uniqueue",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "High-performance priority queue with unique key constraint (O(1) lookup, O(log n) update)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -28,7 +28,6 @@
|
|
|
28
28
|
"deduplication",
|
|
29
29
|
"leaderboard",
|
|
30
30
|
"cache",
|
|
31
|
-
"lru",
|
|
32
31
|
"performance"
|
|
33
32
|
],
|
|
34
33
|
"author": "LLB <shlavik@gmail.com>",
|