@gkucmierz/utils 1.32.1 → 2.0.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/main.mjs CHANGED
@@ -5,6 +5,9 @@ import {
5
5
  import {
6
6
  Trie
7
7
  } from './src/Trie.mjs'
8
+ import {
9
+ arrayHistogram
10
+ } from './src/array-histogram.mjs'
8
11
  import {
9
12
  fromBase64, fromBase64Url, toBase64, toBase64Url
10
13
  } from './src/base64.mjs'
@@ -14,6 +17,9 @@ import {
14
17
  import {
15
18
  binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl
16
19
  } from './src/binary-search.mjs'
20
+ import {
21
+ combinations, combinationsIterator
22
+ } from './src/combinations.mjs'
17
23
  import {
18
24
  consumeIteratorNonBlocking
19
25
  } from './src/consume-iterator.mjs'
@@ -38,6 +44,9 @@ import {
38
44
  import {
39
45
  gpn, gpnBI
40
46
  } from './src/gpn.mjs'
47
+ import {
48
+ bin2gray, gray2bin
49
+ } from './src/gray-code.mjs'
41
50
  import {
42
51
  Heap
43
52
  } from './src/heap.mjs'
@@ -62,6 +71,9 @@ import {
62
71
  import {
63
72
  mod, modBI
64
73
  } from './src/mod.mjs'
74
+ import {
75
+ nChooseK
76
+ } from './src/n-choose-k.mjs'
65
77
  import {
66
78
  naturalSearch
67
79
  } from './src/natural-search.mjs'
@@ -71,6 +83,9 @@ import {
71
83
  import {
72
84
  particleSwarmOptimization
73
85
  } from './src/particle-swarm.mjs'
86
+ import {
87
+ permutations, permutationsIterator
88
+ } from './src/permutations.mjs'
74
89
  import {
75
90
  phi, phiBI
76
91
  } from './src/phi.mjs'
@@ -92,9 +107,11 @@ import {
92
107
 
93
108
  export * from './src/SetCnt.mjs';
94
109
  export * from './src/Trie.mjs';
110
+ export * from './src/array-histogram.mjs';
95
111
  export * from './src/base64.mjs';
96
112
  export * from './src/bijective-numeration.mjs';
97
113
  export * from './src/binary-search.mjs';
114
+ export * from './src/combinations.mjs';
98
115
  export * from './src/consume-iterator.mjs';
99
116
  export * from './src/copy-case.mjs';
100
117
  export * from './src/egcd.mjs';
@@ -103,6 +120,7 @@ export * from './src/format-big-number.mjs';
103
120
  export * from './src/gcd.mjs';
104
121
  export * from './src/get-type.mjs';
105
122
  export * from './src/gpn.mjs';
123
+ export * from './src/gray-code.mjs';
106
124
  export * from './src/heap.mjs';
107
125
  export * from './src/herons-formula.mjs';
108
126
  export * from './src/lcm.mjs';
@@ -111,9 +129,11 @@ export * from './src/math3d.mjs';
111
129
  export * from './src/matrix.mjs';
112
130
  export * from './src/memoize.mjs';
113
131
  export * from './src/mod.mjs';
132
+ export * from './src/n-choose-k.mjs';
114
133
  export * from './src/natural-search.mjs';
115
134
  export * from './src/nelder-mead.mjs';
116
135
  export * from './src/particle-swarm.mjs';
136
+ export * from './src/permutations.mjs';
117
137
  export * from './src/phi.mjs';
118
138
  export * from './src/pow-mod.mjs';
119
139
  export * from './src/range-array.mjs';
@@ -122,5 +142,5 @@ export * from './src/square-root.mjs';
122
142
  export * from './src/tonelli-shanks.mjs';
123
143
 
124
144
  export default [
125
- SetCnt, Trie, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, consumeIteratorNonBlocking, copyCase, egcd, factors, factorsBI, formatBigNumber, formatBigNumberBI, wrapFn, gcd, gcdBI, getType, gpn, gpnBI, Heap, heronsFormula, heronsFormulaBI, lcm, lcmBI, ListNode, axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball, matrixAsArray, memoize, mod, modBI, naturalSearch, nelderMead, particleSwarmOptimization, phi, phiBI, powMod, powModBI, array2range, range2array, simulatedAnnealing, squareRoot, squareRootBI, tonelliShanksBI
145
+ SetCnt, Trie, arrayHistogram, fromBase64, fromBase64Url, toBase64, toBase64Url, bijective2num, bijective2numBI, num2bijective, num2bijectiveBI, binarySearchArr, binarySearchGE, binarySearchLE, binarySearchRangeIncl, combinations, combinationsIterator, consumeIteratorNonBlocking, copyCase, egcd, factors, factorsBI, formatBigNumber, formatBigNumberBI, wrapFn, gcd, gcdBI, getType, gpn, gpnBI, bin2gray, gray2bin, Heap, heronsFormula, heronsFormulaBI, lcm, lcmBI, ListNode, axisAngleToMatrix4, crossProduct, dotProduct, getRotationMatrixFromVectors, multiplyMatrix4, normalize, projectToTrackball, matrixAsArray, memoize, mod, modBI, nChooseK, naturalSearch, nelderMead, particleSwarmOptimization, permutations, permutationsIterator, phi, phiBI, powMod, powModBI, array2range, range2array, simulatedAnnealing, squareRoot, squareRootBI, tonelliShanksBI
126
146
  ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gkucmierz/utils",
3
- "version": "1.32.1",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "description": "Usefull functions for solving programming tasks",
6
6
  "keywords": [
package/src/Trie.mjs CHANGED
@@ -1,111 +1,121 @@
1
-
2
1
  /**
3
2
  * Creates a Trie (prefix tree) data structure.
4
- * @param {string[]} [words=[]] - Initial words to add to the Trie.
5
- * @param {boolean} [strict=true] - If true, removing a word cleans up unused nodes.
6
- * If false, removal is faster but may leave unused nodes.
7
- * @returns {object} The Trie instance with methods.
3
+ * Implemented as a proper ES6 Class to fix prototyping documentation and encapsulate nodes.
8
4
  */
9
- export const Trie = (words = [], strict = true) => {
10
- const HAS = 0;
11
- const MAP = 1;
12
- const data = [false, new Map()];
5
+ export class Trie {
6
+ static #HAS = 0;
7
+ static #MAP = 1;
8
+
9
+ #data = [false, new Map()];
10
+ #strict;
11
+
12
+ /**
13
+ * Initializes the Trie class object.
14
+ * @param {string[]} [words=[]] - Initial words to add to the Trie.
15
+ * @param {boolean} [strict=true] - If true, removing a word cleans up unused nodes.
16
+ * If false, removal is faster but may leave unused nodes.
17
+ */
18
+ constructor(words = [], strict = true) {
19
+ this.#strict = strict;
20
+ words.forEach(word => this.add(word));
21
+ }
13
22
 
14
23
  /**
15
24
  * Adds a word to the Trie.
16
25
  * @param {string} word - The word to add.
17
26
  * @returns {boolean} True if the word was added (didn't exist before), false otherwise.
18
27
  */
19
- const add = word => {
20
- let node = data;
28
+ add(word) {
29
+ let node = this.#data;
21
30
  for (let i = 0; i < word.length; ++i) {
22
31
  const char = word[i];
23
- if (!node[MAP]) node[MAP] = new Map();
24
- const child = node[MAP].get(char) ?? [false];
25
- node[MAP].set(char, child);
32
+ if (!node[Trie.#MAP]) node[Trie.#MAP] = new Map();
33
+ const child = node[Trie.#MAP].get(char) ?? [false];
34
+ node[Trie.#MAP].set(char, child);
26
35
  node = child;
27
36
  }
28
- if (node[HAS]) return false;
29
- node[HAS] = true;
37
+ if (node[Trie.#HAS]) return false;
38
+ node[Trie.#HAS] = true;
30
39
  return true;
31
- };
32
- const listNodes = word => {
33
- const nodes = [data];
40
+ }
41
+
42
+ #listNodes(word) {
43
+ const nodes = [this.#data];
34
44
  for (let i = 0; i < word.length; ++i) {
35
45
  const node = nodes.at(-1);
36
46
  const char = word[i];
37
- if (!node[MAP]?.has(char)) {
47
+ if (!node[Trie.#MAP]?.has(char)) {
38
48
  nodes.push([false]);
39
49
  break;
40
50
  }
41
- nodes.push(node[MAP].get(char));
51
+ nodes.push(node[Trie.#MAP].get(char));
42
52
  }
43
53
  return nodes;
44
- };
45
- const findNode = word => {
46
- return listNodes(word).at(-1);
47
- };
54
+ }
55
+
56
+ #findNode(word) {
57
+ return this.#listNodes(word).at(-1);
58
+ }
48
59
 
49
60
  /**
50
61
  * Removes a word from the Trie.
51
62
  * @param {string} word - The word to remove.
52
63
  * @returns {boolean} True if the word was removed, false if it didn't exist.
53
64
  */
54
- const remove = word => {
55
- const nodes = listNodes(word);
65
+ remove(word) {
66
+ const nodes = this.#listNodes(word);
56
67
  const rev = nodes.reverse();
57
- const removed = rev.at(0)[HAS];
58
- rev.at(0)[HAS] = false;
59
- if (!strict) return removed;
68
+ const removed = rev.at(0)[Trie.#HAS];
69
+ rev.at(0)[Trie.#HAS] = false;
70
+ if (!this.#strict) return removed;
60
71
  for (let i = 0; i < rev.length; ++i) {
61
72
  const node = rev[i];
62
73
  const first = i === 0;
63
- const noMap = !node[MAP];
64
- const size1 = node[MAP]?.size <= 1;
65
74
  if (first) {
66
- if (node[MAP]) break;
75
+ if (node[Trie.#MAP]) break;
67
76
  } else {
68
- if (node[MAP]?.size <= 1) {
69
- delete node[MAP];
77
+ if (node[Trie.#MAP]?.size <= 1) {
78
+ delete node[Trie.#MAP];
70
79
  } else {
71
80
  break;
72
81
  }
73
82
  }
74
- if (node[HAS]) break;
83
+ if (node[Trie.#HAS]) break;
75
84
  }
76
85
  return removed;
77
- };
86
+ }
78
87
 
79
88
  /**
80
89
  * Checks if a word exists in the Trie.
81
90
  * @param {string} word - The word to check.
82
91
  * @returns {boolean} True if the word exists, false otherwise.
83
92
  */
84
- const has = word => findNode(word)[HAS];
93
+ has(word) {
94
+ return this.#findNode(word)[Trie.#HAS];
95
+ }
85
96
 
86
97
  /**
87
98
  * Returns all words in the Trie that start with the given prefix.
88
99
  * @param {string} begin - The prefix to search for.
89
100
  * @returns {string[]} An array of words starting with the prefix.
90
101
  */
91
- const get = begin => {
102
+ get(begin) {
92
103
  const res = [];
93
104
  const loop = (node, str = '') => {
94
105
  if (!node) return;
95
- if (node[HAS]) res.push(begin + str);
96
- const map = node[MAP] || new Map();
97
- [...map].map(([char, node]) => loop(node, str + char));
106
+ if (node[Trie.#HAS]) res.push(begin + str);
107
+ const map = node[Trie.#MAP] || new Map();
108
+ [...map].forEach(([char, n]) => loop(n, str + char));
98
109
  };
99
- loop(findNode(begin));
110
+ loop(this.#findNode(begin));
100
111
  return res;
101
- };
102
- words.map(word => add(word));
103
- return {
104
- add, has, get, remove,
105
- /**
106
- * Returns the internal data structure of the Trie.
107
- * @returns {Array} The root node of the Trie.
108
- */
109
- getData: () => data,
110
- };
111
- };
112
+ }
113
+
114
+ /**
115
+ * Returns the internal data structure of the Trie.
116
+ * @returns {Array} The root node of the Trie.
117
+ */
118
+ getData() {
119
+ return this.#data;
120
+ }
121
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Generates a Map with unique elements and their occurring frequencies.
3
+ * Transforms an array into a Map of (key -> quantity) using an optional mapping callback.
4
+ *
5
+ * @param {Array} arr Target array
6
+ * @param {function} pick Projection callback (defaults to identity function)
7
+ * @returns {Map} Histogram mapping
8
+ */
9
+ export const arrayHistogram = (iter, pick = el => el) => {
10
+ const quant = new Map();
11
+ for (const obj of iter) {
12
+ const el = pick(obj);
13
+ const cnt = quant.get(el) ?? 0;
14
+ quant.set(el, cnt + 1);
15
+ }
16
+ return quant;
17
+ };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Generates all possible combinations of size n from a set of size k without repetition.
3
+ * Yields arrays of indices representing the current combination.
4
+ *
5
+ * Optimized with lexicographic progression to skip invalid states,
6
+ * running in O(1) amortized time per sequence instead of O(k^n).
7
+ *
8
+ * @param {number} n Size of the combination
9
+ * @param {number} k Size of the set to choose from (indices 0 to k-1)
10
+ * @yields {number[]} Array of indices
11
+ */
12
+ export const combinationsIterator = function* (n, k) {
13
+ if (n > k || n < 0) return;
14
+ const arr = new Array(n).fill(0).map((_, i) => i);
15
+ yield arr.slice();
16
+
17
+ while (true) {
18
+ let i = n - 1;
19
+ while (i >= 0 && arr[i] === k - n + i) {
20
+ i--;
21
+ }
22
+ if (i < 0) break;
23
+ arr[i]++;
24
+ for (let j = i + 1; j < n; j++) {
25
+ arr[j] = arr[j - 1] + 1;
26
+ }
27
+ yield arr.slice();
28
+ }
29
+ };
30
+
31
+ /**
32
+ * Returns all combinations synchronously.
33
+ * WARNING: Calling this with mathematically large sets will overflow local RAM.
34
+ *
35
+ * @param {number} n Size of the combination
36
+ * @param {number} k Size of the set
37
+ * @returns {Array<number[]>} Array containing all combinations
38
+ */
39
+ export const combinations = (n, k) => {
40
+ return [...combinationsIterator(n, k)];
41
+ };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Internal logic for toggling bit state based on MSB progression.
3
+ */
4
+ const convertBit = (p, v, i) => {
5
+ p[i] = i && p[i-1] ? 1 - v : v;
6
+ return p;
7
+ };
8
+
9
+ /**
10
+ * Transforms an array of binary bits into Gray Code.
11
+ *
12
+ * @param {Array<number|boolean>} bits Binary representation (MSB at index 0)
13
+ * @returns {Array<number>} Array of Gray code bits
14
+ */
15
+ export const bin2gray = (bits) => {
16
+ // Cloning array mathematically to prevent pure-function reference mutation
17
+ return [...bits].reduceRight(convertBit, [...bits]);
18
+ };
19
+
20
+ /**
21
+ * Decodes a Gray code array back into standard binary formatting.
22
+ *
23
+ * @param {Array<number|boolean>} gray Gray Code representation
24
+ * @returns {Array<number>} Decoded original binary bits
25
+ */
26
+ export const gray2bin = (gray) => {
27
+ return [...gray].reduce(convertBit, [...gray]);
28
+ };
package/src/heap.mjs CHANGED
@@ -1,72 +1,83 @@
1
-
2
1
  /**
3
2
  * Creates a Min Heap data structure.
4
- * @param {Function} [valFn] - A function to extract the comparison value from an element.
5
- * Defaults to identity (n => n).
6
- * @returns {object} The Heap instance with methods.
3
+ * Implemented as a ES6 Class to fix prototyping documentation
4
+ * and properly encapsulate internal mechanics.
7
5
  */
8
- export const Heap = (valFn = n => n) => {
9
- const arr = [-1];
6
+ export class Heap {
7
+ #arr = [-1];
8
+ #valFn;
9
+
10
+ /**
11
+ * Initializes the Heap class object.
12
+ * @param {Function} [valFn] - A function to extract the comparison value from an element. Defaults to identity (n => n).
13
+ */
14
+ constructor(valFn = n => n) {
15
+ this.#valFn = valFn;
16
+ }
10
17
 
11
- const up = idx => {
18
+ #up(idx) {
12
19
  while (idx > 1) {
13
20
  const ni = idx / 2 | 0;
14
- if (valFn(arr[idx]) < valFn(arr[ni])) {
15
- [arr[idx], arr[ni]] = [arr[ni], arr[idx]];
21
+ if (this.#valFn(this.#arr[idx]) < this.#valFn(this.#arr[ni])) {
22
+ [this.#arr[idx], this.#arr[ni]] = [this.#arr[ni], this.#arr[idx]];
16
23
  }
17
24
  idx = ni;
18
25
  }
19
26
  return idx;
20
- };
21
-
22
- return {
23
- /**
24
- * Adds an element to the heap.
25
- * @param {*} el - The element to add.
26
- * @returns {number} The new index of the added element.
27
- */
28
- add: el => up(arr.push(el) - 1),
27
+ }
29
28
 
30
- /**
31
- * Removes and returns the minimum element (root) from the heap.
32
- * @returns {*} The minimum element.
33
- */
34
- take: () => {
35
- const len = arr.length;
36
- if (len <= 1) return [][0];
37
- let idx = 1;
38
- const res = arr[idx];
39
- while (idx < len) {
40
- const ia = idx * 2;
41
- const ib = idx * 2 + 1;
42
- if (ia >= len) break;
43
- if (ib >= len || valFn(arr[ia]) < valFn(arr[ib])) {
44
- arr[idx] = arr[ia];
45
- idx = ia;
46
- } else {
47
- arr[idx] = arr[ib];
48
- idx = ib;
49
- }
50
- }
51
- if (idx === arr.length - 1) {
52
- arr.pop();
29
+ /**
30
+ * Adds an element to the heap.
31
+ * @param {*} el - The element to add.
32
+ * @returns {number} The new index of the added element.
33
+ */
34
+ add(el) {
35
+ return this.#up(this.#arr.push(el) - 1);
36
+ }
37
+
38
+ /**
39
+ * Removes and returns the minimum element (root) from the heap.
40
+ * @returns {*} The minimum element.
41
+ */
42
+ take() {
43
+ const len = this.#arr.length;
44
+ if (len <= 1) return [][0];
45
+ let idx = 1;
46
+ const res = this.#arr[idx];
47
+ while (idx < len) {
48
+ const ia = idx * 2;
49
+ const ib = idx * 2 + 1;
50
+ if (ia >= len) break;
51
+ if (ib >= len || this.#valFn(this.#arr[ia]) < this.#valFn(this.#arr[ib])) {
52
+ this.#arr[idx] = this.#arr[ia];
53
+ idx = ia;
53
54
  } else {
54
- arr[idx] = arr.pop();
55
- up(idx);
55
+ this.#arr[idx] = this.#arr[ib];
56
+ idx = ib;
56
57
  }
57
- return res;
58
- },
59
-
60
- /**
61
- * Returns the number of elements in the heap.
62
- * @returns {number} The size of the heap.
63
- */
64
- size: () => arr.length - 1,
58
+ }
59
+ if (idx === this.#arr.length - 1) {
60
+ this.#arr.pop();
61
+ } else {
62
+ this.#arr[idx] = this.#arr.pop();
63
+ this.#up(idx);
64
+ }
65
+ return res;
66
+ }
67
+
68
+ /**
69
+ * Returns the number of elements in the heap.
70
+ * @returns {number} The size of the heap.
71
+ */
72
+ size() {
73
+ return this.#arr.length - 1;
74
+ }
65
75
 
66
- /**
67
- * Returns the internal array representation of the heap (excluding the dummy first element).
68
- * @returns {Array} The heap elements.
69
- */
70
- data: () => arr.slice(1),
71
- };
72
- };
76
+ /**
77
+ * Returns the internal array representation of the heap (excluding the dummy first element).
78
+ * @returns {Array} The heap elements.
79
+ */
80
+ data() {
81
+ return this.#arr.slice(1);
82
+ }
83
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Newton's formula (Binomial Coefficient) n choose k.
3
+ * Evaluates the number of possible combinations.
4
+ * Built strictly on BigInt and optimized iterative fraction reduction
5
+ * to prevent RAM and precision loss during massive factorial expansions.
6
+ *
7
+ * @param {number|bigint|string} n Total elements
8
+ * @param {number|bigint|string} k Elements to pick
9
+ * @returns {bigint} Total possible subset combinations
10
+ */
11
+ export const nChooseK = (n, k) => {
12
+ const bigN = BigInt(n);
13
+ const bigK = BigInt(k);
14
+
15
+ if (bigK < 0n || bigK > bigN) return 0n;
16
+ if (bigK === 0n || bigK === bigN) return 1n;
17
+
18
+ // Optimization: dynamically reduce the factorial expansion
19
+ // equivalent to (n! / (k! * (n-k)!)), shrinking the multiplication loops.
20
+ const kMin = bigK > (bigN - bigK) ? (bigN - bigK) : bigK;
21
+
22
+ let numerator = 1n;
23
+ let denominator = 1n;
24
+
25
+ for (let i = 1n; i <= kMin; ++i) {
26
+ numerator *= (bigN - i + 1n);
27
+ denominator *= i;
28
+ }
29
+
30
+ return numerator / denominator;
31
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Creates a permutations generator from a specified size or an array of elements.
3
+ *
4
+ * @param {number|Array} sizeOrArr Integer size or an array of items
5
+ * @yields {Array} Re-arranged array of items/indices
6
+ */
7
+ export const permutationsIterator = function*(sizeOrArr) {
8
+ const arr = Array.isArray(sizeOrArr) ? [...sizeOrArr] : new Array(sizeOrArr).fill(0).map((_, i) => i);
9
+ const size = arr.length;
10
+
11
+ const gen = function*(res, used, shift) {
12
+ const len = size - used;
13
+ if (len === 0) {
14
+ yield res;
15
+ return;
16
+ }
17
+ for (let i = 0; i < len; ++i) {
18
+ yield* gen([...res, shift[i]], used + 1, [...shift.slice(0, i), ...shift.slice(i + 1)]);
19
+ }
20
+ };
21
+
22
+ yield* gen([], 0, arr);
23
+ };
24
+
25
+ /**
26
+ * Returns all permutations synchronously.
27
+ * WARNING: Generates size! (factorial) elements. Scales in O(N!).
28
+ *
29
+ * @param {number|Array} sizeOrArr
30
+ * @returns {Array<Array>} Array of permutation sets
31
+ */
32
+ export const permutations = (sizeOrArr) => {
33
+ return [...permutationsIterator(sizeOrArr)];
34
+ };