@gkucmierz/utils 4.0.1 → 4.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
CHANGED
|
@@ -24,6 +24,7 @@ This library provides a wide range of mathematical functions and data structures
|
|
|
24
24
|
- `bin2gray`, `gray2bin`: Pure array-cloning bit formatters for Gray code translations.
|
|
25
25
|
|
|
26
26
|
- **Data Structures**:
|
|
27
|
+
- `ConflatingQueue`: Asynchronous coalescing work queue with key-based deduplication and controllable concurrency (`class`).
|
|
27
28
|
- `SetCnt`: A set-like structure with element counting (`class`).
|
|
28
29
|
- `Trie`: Efficient prefix tree implementation (`class`).
|
|
29
30
|
- `Heap`: Min-heap priority queue (`class`).
|
package/main.mjs
CHANGED
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
import {
|
|
15
15
|
permutations, permutationsIterator
|
|
16
16
|
} from './src/combinatorics/permutations.mjs'
|
|
17
|
+
import {
|
|
18
|
+
ConflatingQueue
|
|
19
|
+
} from './src/data-structures/ConflatingQueue.mjs'
|
|
17
20
|
import {
|
|
18
21
|
SetCnt
|
|
19
22
|
} from './src/data-structures/SetCnt.mjs'
|
|
@@ -137,6 +140,7 @@ export * from './src/combinatorics/combinations.mjs';
|
|
|
137
140
|
export * from './src/combinatorics/gray-code.mjs';
|
|
138
141
|
export * from './src/combinatorics/n-choose-k.mjs';
|
|
139
142
|
export * from './src/combinatorics/permutations.mjs';
|
|
143
|
+
export * from './src/data-structures/ConflatingQueue.mjs';
|
|
140
144
|
export * from './src/data-structures/SetCnt.mjs';
|
|
141
145
|
export * from './src/data-structures/Trie.mjs';
|
|
142
146
|
export * from './src/data-structures/heap.mjs';
|
|
@@ -178,5 +182,5 @@ export * from './src/string-arrays/copy-case.mjs';
|
|
|
178
182
|
export * from './src/string-arrays/format-big-number.mjs';
|
|
179
183
|
|
|
180
184
|
export default [
|
|
181
|
-
createLangtonsAnt, createUnlimitedGrid, combinations, combinationsIterator, bin2gray, gray2bin, nChooseK, permutations, permutationsIterator, SetCnt, Trie, Heap, ListNode, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, consumeIteratorNonBlocking, getType, measurePerformance, memoize, naturalSearch, randNormal, array2range, range2array, setSafeInterval, barycentricCoordinates, axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball, matrixAsArray, egcd, factors, factorsBI, gcd, gcdBI, lcm, lcmBI, lucasLehmerBI, mobius, mobiusBI, mod, modBI, phi, phiBI, powMod, powModBI, tonelliShanksBI, nelderMead, particleSwarmOptimization, simulatedAnnealing, goldenRatio, goldenRatioBI, goldenRatioStr, gpn, gpnBI, heronsFormula, heronsFormulaBI, squareRoot, squareRootBI, arrayHistogram, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, chunks, chunksAsyncIterator, chunksIterator, copyCase, formatBigNumber, formatBigNumberBI, wrapFn
|
|
185
|
+
createLangtonsAnt, createUnlimitedGrid, combinations, combinationsIterator, bin2gray, gray2bin, nChooseK, permutations, permutationsIterator, ConflatingQueue, SetCnt, Trie, Heap, ListNode, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, consumeIteratorNonBlocking, getType, measurePerformance, memoize, naturalSearch, randNormal, array2range, range2array, setSafeInterval, barycentricCoordinates, axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball, matrixAsArray, egcd, factors, factorsBI, gcd, gcdBI, lcm, lcmBI, lucasLehmerBI, mobius, mobiusBI, mod, modBI, phi, phiBI, powMod, powModBI, tonelliShanksBI, nelderMead, particleSwarmOptimization, simulatedAnnealing, goldenRatio, goldenRatioBI, goldenRatioStr, gpn, gpnBI, heronsFormula, heronsFormulaBI, squareRoot, squareRootBI, arrayHistogram, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, chunks, chunksAsyncIterator, chunksIterator, copyCase, formatBigNumber, formatBigNumberBI, wrapFn
|
|
182
186
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gkucmierz/utils",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Usefull functions for solving programming tasks",
|
|
6
6
|
"keywords": [
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"benchmark",
|
|
13
13
|
"combinations",
|
|
14
14
|
"competitive-programming",
|
|
15
|
+
"conflating-queue",
|
|
16
|
+
"coalescing-queue",
|
|
15
17
|
"data-structures",
|
|
16
18
|
"factors",
|
|
17
19
|
"gcd",
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module data-structures
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ConflatingQueue (Coalescing Keyed Work Queue).
|
|
7
|
+
*
|
|
8
|
+
* A specialized asynchronous task queue that executes tasks with controllable concurrency
|
|
9
|
+
* (default 1 = sequential mutex) while automatically conflating (superseding/deduplicating)
|
|
10
|
+
* pending tasks with matching keys.
|
|
11
|
+
*
|
|
12
|
+
* When multiple tasks with the same key are enqueued before the previous task has started,
|
|
13
|
+
* the older pending task is superseded (resolved with `{ superseded: true }`), and only the
|
|
14
|
+
* freshest task is executed.
|
|
15
|
+
*
|
|
16
|
+
* Ideal for hardware control (DDC/CI, I2C, Serial), UI sliders, telemetry, and rate-limited APIs.
|
|
17
|
+
* @see {@link https://instacode.app/run/FASwtgDg9gTgLgAgN4IMJQHYDMA2BDOEDAcwEUBXAUyoQF8EsYowEAiAAWIGtyBjMEJRgAvAPTlCOAM6sA3MGDps+QiQrVK8oA|▶ Try it live in Instacode}
|
|
18
|
+
*/
|
|
19
|
+
export class ConflatingQueue {
|
|
20
|
+
#concurrency;
|
|
21
|
+
#maxPending;
|
|
22
|
+
#pendingTasks = new Map();
|
|
23
|
+
#runningCount = 0;
|
|
24
|
+
#idleResolvers = [];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initializes the ConflatingQueue.
|
|
28
|
+
*
|
|
29
|
+
* @param {object} [options={}]
|
|
30
|
+
* @param {number} [options.concurrency=1] - Maximum concurrent tasks executing simultaneously.
|
|
31
|
+
* @param {number} [options.maxPending=Infinity] - Maximum allowed distinct pending keys in queue.
|
|
32
|
+
*/
|
|
33
|
+
constructor({ concurrency = 1, maxPending = Infinity } = {}) {
|
|
34
|
+
this.#concurrency = Math.max(1, concurrency || 1);
|
|
35
|
+
this.#maxPending = maxPending > 0 ? maxPending : Infinity;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Enqueues an asynchronous task associated with a specific key.
|
|
40
|
+
* If a pending task with the same key already exists, it is superseded by the new task.
|
|
41
|
+
*
|
|
42
|
+
* @template T
|
|
43
|
+
* @param {string|number|symbol} key - Unique identifier for task coalescing.
|
|
44
|
+
* @param {() => Promise<T>|T} taskFn - The async/sync function to execute.
|
|
45
|
+
* @returns {Promise<T|{ superseded: boolean }>} Resolves with task result or `{ superseded: true }`.
|
|
46
|
+
*/
|
|
47
|
+
enqueue(key, taskFn) {
|
|
48
|
+
if (typeof taskFn !== 'function') {
|
|
49
|
+
return Promise.reject(new TypeError('taskFn must be a function'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.#pendingTasks.has(key)) {
|
|
53
|
+
const prev = this.#pendingTasks.get(key);
|
|
54
|
+
prev.resolve({ superseded: true });
|
|
55
|
+
} else if (this.#pendingTasks.size >= this.#maxPending) {
|
|
56
|
+
return Promise.reject(new Error(`ConflatingQueue maxPending limit (${this.#maxPending}) exceeded`));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
this.#pendingTasks.set(key, { taskFn, resolve, reject });
|
|
61
|
+
this.#drain();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Cancels a pending unstarted task by key.
|
|
67
|
+
*
|
|
68
|
+
* @param {string|number|symbol} key - Key of the pending task to cancel.
|
|
69
|
+
* @returns {boolean} True if task was cancelled, false if not found in pending queue.
|
|
70
|
+
*/
|
|
71
|
+
cancel(key) {
|
|
72
|
+
if (!this.#pendingTasks.has(key)) return false;
|
|
73
|
+
const item = this.#pendingTasks.get(key);
|
|
74
|
+
this.#pendingTasks.delete(key);
|
|
75
|
+
item.resolve({ cancelled: true });
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Clears and cancels all currently pending unstarted tasks.
|
|
81
|
+
*/
|
|
82
|
+
clear() {
|
|
83
|
+
for (const item of this.#pendingTasks.values()) {
|
|
84
|
+
item.resolve({ cancelled: true });
|
|
85
|
+
}
|
|
86
|
+
this.#pendingTasks.clear();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Checks if the queue is completely idle (no running or pending tasks).
|
|
91
|
+
*
|
|
92
|
+
* @type {boolean}
|
|
93
|
+
*/
|
|
94
|
+
get isIdle() {
|
|
95
|
+
return this.#runningCount === 0 && this.#pendingTasks.size === 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Returns current task count metrics.
|
|
100
|
+
*
|
|
101
|
+
* @type {{ running: number, pending: number, total: number }}
|
|
102
|
+
*/
|
|
103
|
+
get size() {
|
|
104
|
+
return {
|
|
105
|
+
running: this.#runningCount,
|
|
106
|
+
pending: this.#pendingTasks.size,
|
|
107
|
+
total: this.#runningCount + this.#pendingTasks.size
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Returns complete diagnostic statistics of the queue.
|
|
113
|
+
*
|
|
114
|
+
* @returns {{ running: number, pending: number, total: number, concurrency: number, maxPending: number, isIdle: boolean, pendingKeys: Array<string|number|symbol> }}
|
|
115
|
+
*/
|
|
116
|
+
getStats() {
|
|
117
|
+
return {
|
|
118
|
+
running: this.#runningCount,
|
|
119
|
+
pending: this.#pendingTasks.size,
|
|
120
|
+
total: this.#runningCount + this.#pendingTasks.size,
|
|
121
|
+
concurrency: this.#concurrency,
|
|
122
|
+
maxPending: this.#maxPending,
|
|
123
|
+
isIdle: this.isIdle,
|
|
124
|
+
pendingKeys: Array.from(this.#pendingTasks.keys())
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Returns a promise that resolves when the queue becomes completely idle.
|
|
130
|
+
*
|
|
131
|
+
* @returns {Promise<void>}
|
|
132
|
+
*/
|
|
133
|
+
onIdle() {
|
|
134
|
+
if (this.isIdle) return Promise.resolve();
|
|
135
|
+
return new Promise(resolve => {
|
|
136
|
+
this.#idleResolvers.push(resolve);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async #drain() {
|
|
141
|
+
while (this.#runningCount < this.#concurrency && this.#pendingTasks.size > 0) {
|
|
142
|
+
const [key, item] = this.#pendingTasks.entries().next().value;
|
|
143
|
+
this.#pendingTasks.delete(key);
|
|
144
|
+
this.#runningCount++;
|
|
145
|
+
|
|
146
|
+
(async () => {
|
|
147
|
+
try {
|
|
148
|
+
const result = await item.taskFn();
|
|
149
|
+
item.resolve(result);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
item.reject(err);
|
|
152
|
+
} finally {
|
|
153
|
+
this.#runningCount--;
|
|
154
|
+
if (this.isIdle) {
|
|
155
|
+
const resolvers = this.#idleResolvers.splice(0);
|
|
156
|
+
resolvers.forEach(r => r());
|
|
157
|
+
}
|
|
158
|
+
this.#drain();
|
|
159
|
+
}
|
|
160
|
+
})();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
const formatBigNumberBoth = (num, separator = '', wrapFn = _ => _) => {
|
|
16
16
|
const str = String(num);
|
|
17
17
|
const rev = [...str].reverse().join('');
|
|
18
|
-
const match = rev.match(/(\d
|
|
18
|
+
const match = rev.match(/(\d*\.[+-]?)|(\d{3}[+-]?)|(\d{1,3}[+-]?)|([+-])/g);
|
|
19
19
|
const revInside = (match && match.join('') === rev)
|
|
20
20
|
? match.map(part => [...part].reverse().join(''))
|
|
21
21
|
: [str];
|
|
@@ -45,6 +45,13 @@ export const formatBigNumber = formatBigNumberBoth;
|
|
|
45
45
|
*/
|
|
46
46
|
export const formatBigNumberBI = formatBigNumberBoth;
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Creates an alternating span wrapper function for number segments with 'even' and 'odd' classes.
|
|
50
|
+
* Useful for syntax highlighting or alternating styles of formatted number segments.
|
|
51
|
+
*
|
|
52
|
+
* @returns {function(string): string} A wrapper callback function for number segments.
|
|
53
|
+
* @see {@link https://instacode.app/run/FASwtgDg9gTgLgAgN4IO4wIYQGIDsEC+CAZjFGAgEQACA5gNYCuAxmCAKYwBeA9I3CAA2AZ0oBuYMHRY8EoA|▶ Try it live in Instacode}
|
|
54
|
+
*/
|
|
48
55
|
export const wrapFn = () => {
|
|
49
56
|
let even = true;
|
|
50
57
|
return part => {
|
|
@@ -53,3 +60,4 @@ export const wrapFn = () => {
|
|
|
53
60
|
return `<span class="${cls}">${part}</span>`;
|
|
54
61
|
};
|
|
55
62
|
};
|
|
63
|
+
|