@cloudglides/nox 1.1.6 → 3.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.
Files changed (66) hide show
  1. package/README.md +9 -9
  2. package/example/package.json +1 -1
  3. package/example/src/App.css +38 -7
  4. package/example/src/App.jsx +328 -25
  5. package/package.json +7 -6
  6. package/pnpm-workspace.yaml +3 -0
  7. package/src/core.browser.js +37 -4
  8. package/src/core.js +37 -4
  9. package/src/generators/logistic.js +30 -25
  10. package/src/generators/mixer.js +7 -7
  11. package/src/generators/mt19937.js +10 -7
  12. package/src/generators/pcg64.js +23 -12
  13. package/src/generators/splitmix64.js +12 -6
  14. package/src/generators/tent.js +12 -7
  15. package/src/generators/xorshift64.js +6 -3
  16. package/src/index.browser.js +168 -1
  17. package/src/index.d.ts +68 -4
  18. package/src/index.js +169 -1
  19. package/src/rng.browser.js +5 -6
  20. package/src/rng.js +95 -94
  21. package/src/utils/arrays.js +149 -0
  22. package/src/utils/bits.js +146 -21
  23. package/src/utils/categorical.js +68 -31
  24. package/src/utils/clustering.js +143 -0
  25. package/src/utils/combinatorics.js +113 -69
  26. package/src/utils/confidence.js +145 -0
  27. package/src/utils/decomposition.js +204 -0
  28. package/src/utils/diagnostics.js +309 -0
  29. package/src/utils/distributions-advanced.js +122 -0
  30. package/src/utils/distributions-extra.js +102 -11
  31. package/src/utils/distributions-special.js +77 -20
  32. package/src/utils/distributions.js +99 -35
  33. package/src/utils/effects.js +172 -0
  34. package/src/utils/entropy.browser.js +29 -26
  35. package/src/utils/entropy.js +18 -8
  36. package/src/utils/helpers.js +64 -0
  37. package/src/utils/hypothesis.js +167 -0
  38. package/src/utils/integration.js +137 -0
  39. package/src/utils/interpolation.js +221 -0
  40. package/src/utils/logistic.js +91 -0
  41. package/src/utils/matrix.js +242 -0
  42. package/src/utils/noise.js +36 -22
  43. package/src/utils/odesolvers.js +176 -0
  44. package/src/utils/optimization.js +215 -0
  45. package/src/utils/polynomial.js +136 -0
  46. package/src/utils/precomputed.js +166 -0
  47. package/src/utils/probability.js +199 -0
  48. package/src/utils/pvalues.js +142 -0
  49. package/src/utils/regression.js +170 -0
  50. package/src/utils/resampling.js +112 -0
  51. package/src/utils/rootfinding.js +158 -0
  52. package/src/utils/sampling.js +86 -77
  53. package/src/utils/seed.js +10 -4
  54. package/src/utils/seeding.js +24 -12
  55. package/src/utils/sequence.js +116 -32
  56. package/src/utils/state.js +48 -36
  57. package/src/utils/statistics.js +64 -2
  58. package/src/utils/stochastic.js +91 -31
  59. package/src/utils/stratified.js +108 -0
  60. package/src/utils/timeseries.js +166 -0
  61. package/src/utils/transforms.js +146 -0
  62. package/test/comprehensive.js +4 -3
  63. package/test/new-features.js +52 -0
  64. package/IMPROVEMENTS.md +0 -58
  65. package/PERFORMANCE.md +0 -69
  66. package/example/pnpm-lock.yaml +0 -1006
package/src/utils/bits.js CHANGED
@@ -1,25 +1,150 @@
1
1
  export const rotateBits = (val, shift) => {
2
- const mask = BigInt(64);
3
- shift = BigInt(shift) % mask;
4
- return (val << shift) | (val >> (mask - shift));
5
- };
2
+ if (typeof val !== 'bigint') {
3
+ throw new TypeError('val must be bigint');
4
+ }
5
+ if (typeof shift !== 'number' && typeof shift !== 'bigint') {
6
+ throw new TypeError('shift must be number or bigint');
7
+ }
8
+ const mask = BigInt(64);
9
+ shift = BigInt(shift) % mask;
10
+ return (val << shift) | (val >> (mask - shift));
11
+ };
12
+
13
+ export const extractBits = (val, start, length) => {
14
+ if (typeof val !== 'bigint') {
15
+ throw new TypeError('val must be bigint');
16
+ }
17
+ if (typeof start !== 'number' || !Number.isInteger(start)) {
18
+ throw new TypeError('start must be an integer');
19
+ }
20
+ if (start < 0) {
21
+ throw new RangeError('start must be non-negative');
22
+ }
23
+ if (typeof length !== 'number' || !Number.isInteger(length)) {
24
+ throw new TypeError('length must be an integer');
25
+ }
26
+ if (length <= 0) {
27
+ throw new RangeError('length must be positive');
28
+ }
29
+ const mask = (BigInt(1) << BigInt(length)) - BigInt(1);
30
+ return (val >> BigInt(start)) & mask;
31
+ };
32
+
33
+ export const hammingWeight = (val) => {
34
+ if (typeof val !== 'bigint') {
35
+ throw new TypeError('val must be bigint');
36
+ }
37
+ let count = 0;
38
+ let v = val < 0n ? -val : val;
39
+ while (v > 0n) {
40
+ v = v & (v - 1n);
41
+ count++;
42
+ }
43
+ return count;
44
+ };
45
+
46
+ export const bitRange = (val, min, max) => {
47
+ if (typeof val !== 'bigint') {
48
+ throw new TypeError('val must be bigint');
49
+ }
50
+ if (typeof min !== 'number' || !Number.isInteger(min)) {
51
+ throw new TypeError('min must be an integer');
52
+ }
53
+ if (min < 0) {
54
+ throw new RangeError('min must be non-negative');
55
+ }
56
+ if (typeof max !== 'number' || !Number.isInteger(max)) {
57
+ throw new TypeError('max must be an integer');
58
+ }
59
+ if (max <= min) {
60
+ throw new RangeError('max must be greater than min');
61
+ }
62
+ const shift = BigInt(min);
63
+ const mask = (BigInt(1) << BigInt(max - min)) - BigInt(1);
64
+ return (val >> shift) & mask;
65
+ };
6
66
 
7
- export const extractBits = (val, start, length) => {
8
- const mask = (BigInt(1) << BigInt(length)) - BigInt(1);
9
- return (val >> BigInt(start)) & mask;
10
- };
67
+ export const popcountNum = (val) => {
68
+ if (typeof val !== 'number' || !Number.isInteger(val) || val < 0) {
69
+ throw new TypeError('val must be a non-negative integer');
70
+ }
71
+ let count = 0;
72
+ let v = val >>> 0;
73
+ while (v) {
74
+ count += v & 1;
75
+ v >>>= 1;
76
+ }
77
+ return count;
78
+ };
11
79
 
12
- export const hammingWeight = (val) => {
13
- let count = 0;
14
- while (val > 0n) {
15
- count += val & 1n ? 1 : 0;
16
- val >>= 1n;
17
- }
18
- return count;
19
- };
80
+ export const clz = (val) => {
81
+ if (typeof val !== 'bigint') {
82
+ throw new TypeError('val must be bigint');
83
+ }
84
+ if (val === 0n) return 64;
85
+ let count = 0;
86
+ let mask = 1n << 63n;
87
+ while ((val & mask) === 0n) {
88
+ count++;
89
+ mask >>= 1n;
90
+ }
91
+ return count;
92
+ };
20
93
 
21
- export const bitRange = (val, min, max) => {
22
- const shift = BigInt(min);
23
- const mask = (BigInt(1) << BigInt(max - min)) - BigInt(1);
24
- return (val >> shift) & mask;
25
- };
94
+ export const ctz = (val) => {
95
+ if (typeof val !== 'bigint') {
96
+ throw new TypeError('val must be bigint');
97
+ }
98
+ if (val === 0n) return 64;
99
+ let count = 0;
100
+ let mask = 1n;
101
+ while ((val & mask) === 0n) {
102
+ count++;
103
+ mask <<= 1n;
104
+ }
105
+ return count;
106
+ };
107
+
108
+ export const reverseBits = (val, width = 64) => {
109
+ if (typeof val !== 'bigint') {
110
+ throw new TypeError('val must be bigint');
111
+ }
112
+ if (typeof width !== 'number' || !Number.isInteger(width) || width <= 0) {
113
+ throw new TypeError('width must be a positive integer');
114
+ }
115
+ let result = 0n;
116
+ for (let i = 0; i < width; i++) {
117
+ result = (result << 1n) | ((val >> BigInt(i)) & 1n);
118
+ }
119
+ return result;
120
+ };
121
+
122
+ export const setBit = (val, pos) => {
123
+ if (typeof val !== 'bigint') {
124
+ throw new TypeError('val must be bigint');
125
+ }
126
+ if (typeof pos !== 'number' || !Number.isInteger(pos) || pos < 0) {
127
+ throw new TypeError('pos must be a non-negative integer');
128
+ }
129
+ return val | (1n << BigInt(pos));
130
+ };
131
+
132
+ export const clearBit = (val, pos) => {
133
+ if (typeof val !== 'bigint') {
134
+ throw new TypeError('val must be bigint');
135
+ }
136
+ if (typeof pos !== 'number' || !Number.isInteger(pos) || pos < 0) {
137
+ throw new TypeError('pos must be a non-negative integer');
138
+ }
139
+ return val & ~(1n << BigInt(pos));
140
+ };
141
+
142
+ export const toggleBit = (val, pos) => {
143
+ if (typeof val !== 'bigint') {
144
+ throw new TypeError('val must be bigint');
145
+ }
146
+ if (typeof pos !== 'number' || !Number.isInteger(pos) || pos < 0) {
147
+ throw new TypeError('pos must be a non-negative integer');
148
+ }
149
+ return val ^ (1n << BigInt(pos));
150
+ };
@@ -1,12 +1,21 @@
1
1
  export const categorical = (rng, categories, probabilities) => {
2
- if (!categories || !probabilities) throw new Error('Categories and probabilities required');
3
- if (categories.length === 0) throw new Error('Categories cannot be empty');
4
- if (probabilities.length !== categories.length) {
5
- throw new Error('Probabilities and categories must have same length');
6
- }
2
+ if (!rng || typeof rng.nextFloat !== 'function') {
3
+ throw new TypeError('First argument must be RNG instance');
4
+ }
5
+ if (!Array.isArray(categories) || !Array.isArray(probabilities)) {
6
+ throw new TypeError('Categories and probabilities must be arrays');
7
+ }
8
+ if (categories.length === 0) {
9
+ throw new RangeError('Categories cannot be empty');
10
+ }
11
+ if (probabilities.length !== categories.length) {
12
+ throw new RangeError('Probabilities and categories must have same length');
13
+ }
7
14
 
8
- const total = probabilities.reduce((a, b) => a + b, 0);
9
- if (total <= 0) throw new Error('Probabilities must sum to positive value');
15
+ const total = probabilities.reduce((a, b) => a + b, 0);
16
+ if (total <= 0) {
17
+ throw new RangeError('Probabilities must sum to positive value');
18
+ }
10
19
 
11
20
  const normalized = probabilities.map(p => p / total);
12
21
 
@@ -24,34 +33,62 @@ export const categorical = (rng, categories, probabilities) => {
24
33
  };
25
34
 
26
35
  export const multinomial = (rng, n, categories, probabilities) => {
27
- const result = {};
28
- for (const cat of categories) {
29
- result[cat] = 0;
30
- }
36
+ if (!rng || typeof rng.nextFloat !== 'function') {
37
+ throw new TypeError('First argument must be RNG instance');
38
+ }
39
+ if (typeof n !== 'number' || !Number.isInteger(n)) {
40
+ throw new TypeError('n must be an integer');
41
+ }
42
+ if (n <= 0) {
43
+ throw new RangeError('n must be positive');
44
+ }
45
+ if (!Array.isArray(categories) || categories.length === 0) {
46
+ throw new RangeError('categories must be a non-empty array');
47
+ }
48
+ if (!Array.isArray(probabilities) || probabilities.length === 0) {
49
+ throw new RangeError('probabilities must be a non-empty array');
50
+ }
51
+ if (probabilities.length !== categories.length) {
52
+ throw new RangeError('probabilities and categories must have same length');
53
+ }
54
+ const result = {};
55
+ for (const cat of categories) {
56
+ result[cat] = 0;
57
+ }
31
58
 
32
- for (let i = 0; i < n; i++) {
33
- const pick = categorical(rng, categories, probabilities);
34
- result[pick]++;
35
- }
59
+ for (let i = 0; i < n; i++) {
60
+ const pick = categorical(rng, categories, probabilities);
61
+ result[pick]++;
62
+ }
36
63
 
37
- return result;
38
- };
64
+ return result;
65
+ };
39
66
 
40
67
  export const categorical2D = (rng, matrix) => {
41
- const rows = matrix.length;
42
- const cols = matrix[0].length;
43
-
44
- const total = matrix.flat().reduce((a, b) => a + b, 0);
45
- let rand = rng.nextFloat() * total;
68
+ if (!rng || typeof rng.nextFloat !== 'function') {
69
+ throw new TypeError('First argument must be RNG instance');
70
+ }
71
+ if (!Array.isArray(matrix) || matrix.length === 0) {
72
+ throw new RangeError('matrix must be non-empty array');
73
+ }
74
+ const rows = matrix.length;
75
+ const cols = matrix[0].length;
46
76
 
47
- for (let i = 0; i < rows; i++) {
48
- for (let j = 0; j < cols; j++) {
49
- rand -= matrix[i][j];
50
- if (rand <= 0) {
51
- return [i, j];
52
- }
77
+ const flat = matrix.flat();
78
+ const total = flat.reduce((a, b) => a + b, 0);
79
+ if (total <= 0) {
80
+ throw new RangeError('Matrix values must sum to positive');
53
81
  }
54
- }
82
+
83
+ let cumsum = 0;
84
+ const rand = rng.nextFloat() * total;
55
85
 
56
- return [rows - 1, cols - 1];
57
- };
86
+ for (let idx = 0; idx < flat.length; idx++) {
87
+ cumsum += flat[idx];
88
+ if (rand < cumsum) {
89
+ return [Math.floor(idx / cols), idx % cols];
90
+ }
91
+ }
92
+
93
+ return [rows - 1, cols - 1];
94
+ };
@@ -0,0 +1,143 @@
1
+ export class KMeans {
2
+ constructor(k, maxIter = 100, tol = 1e-6) {
3
+ if (typeof k !== 'number' || k < 1) {
4
+ throw new RangeError('k must be positive integer');
5
+ }
6
+ if (typeof maxIter !== 'number' || maxIter < 1) {
7
+ throw new RangeError('maxIter must be positive integer');
8
+ }
9
+
10
+ this.k = k;
11
+ this.maxIter = maxIter;
12
+ this.tol = tol;
13
+ this.centroids = null;
14
+ this.labels = null;
15
+ this.inertia = null;
16
+ }
17
+
18
+ fit(X) {
19
+ if (!Array.isArray(X) || X.length === 0) {
20
+ throw new TypeError('X must be non-empty array');
21
+ }
22
+ if (!Array.isArray(X[0]) || X[0].length === 0) {
23
+ throw new TypeError('X must be array of numeric arrays');
24
+ }
25
+
26
+ const n = X.length;
27
+ const d = X[0].length;
28
+
29
+ if (this.k > n) {
30
+ throw new RangeError('k cannot exceed number of samples');
31
+ }
32
+
33
+ this.centroids = this._initializeCentroids(X);
34
+ let prevInertia = Infinity;
35
+
36
+ for (let iter = 0; iter < this.maxIter; iter++) {
37
+ this.labels = this._assignClusters(X);
38
+ const newCentroids = this._updateCentroids(X);
39
+
40
+ this.inertia = this._calculateInertia(X);
41
+
42
+ if (Math.abs(prevInertia - this.inertia) < this.tol) {
43
+ break;
44
+ }
45
+
46
+ prevInertia = this.inertia;
47
+ this.centroids = newCentroids;
48
+ }
49
+
50
+ return this;
51
+ }
52
+
53
+ predict(X) {
54
+ if (this.centroids === null) {
55
+ throw new Error('Model not fitted. Call fit() first.');
56
+ }
57
+ if (!Array.isArray(X) || X.length === 0) {
58
+ throw new TypeError('X must be non-empty array');
59
+ }
60
+
61
+ return this._assignClusters(X);
62
+ }
63
+
64
+ _initializeCentroids(X) {
65
+ const indices = new Set();
66
+ while (indices.size < this.k) {
67
+ indices.add(Math.floor(Math.random() * X.length));
68
+ }
69
+
70
+ return Array.from(indices).map(i => [...X[i]]);
71
+ }
72
+
73
+ _assignClusters(X) {
74
+ const labels = new Array(X.length);
75
+
76
+ for (let i = 0; i < X.length; i++) {
77
+ let minDist = Infinity;
78
+ let bestCluster = 0;
79
+
80
+ for (let j = 0; j < this.k; j++) {
81
+ const dist = this._euclideanDistance(X[i], this.centroids[j]);
82
+ if (dist < minDist) {
83
+ minDist = dist;
84
+ bestCluster = j;
85
+ }
86
+ }
87
+
88
+ labels[i] = bestCluster;
89
+ }
90
+
91
+ return labels;
92
+ }
93
+
94
+ _updateCentroids(X) {
95
+ const newCentroids = [];
96
+ const d = X[0].length;
97
+
98
+ for (let j = 0; j < this.k; j++) {
99
+ const clusterPoints = [];
100
+
101
+ for (let i = 0; i < X.length; i++) {
102
+ if (this.labels[i] === j) {
103
+ clusterPoints.push(X[i]);
104
+ }
105
+ }
106
+
107
+ if (clusterPoints.length === 0) {
108
+ newCentroids.push([...this.centroids[j]]);
109
+ } else {
110
+ const centroid = new Array(d).fill(0);
111
+ for (let dim = 0; dim < d; dim++) {
112
+ let sum = 0;
113
+ for (let point of clusterPoints) {
114
+ sum += point[dim];
115
+ }
116
+ centroid[dim] = sum / clusterPoints.length;
117
+ }
118
+ newCentroids.push(centroid);
119
+ }
120
+ }
121
+
122
+ return newCentroids;
123
+ }
124
+
125
+ _euclideanDistance(a, b) {
126
+ let sum = 0;
127
+ for (let i = 0; i < a.length; i++) {
128
+ sum += (a[i] - b[i]) ** 2;
129
+ }
130
+ return Math.sqrt(sum);
131
+ }
132
+
133
+ _calculateInertia(X) {
134
+ let inertia = 0;
135
+
136
+ for (let i = 0; i < X.length; i++) {
137
+ const dist = this._euclideanDistance(X[i], this.centroids[this.labels[i]]);
138
+ inertia += dist * dist;
139
+ }
140
+
141
+ return inertia;
142
+ }
143
+ }
@@ -1,85 +1,129 @@
1
1
  export const combinations = (arr, k) => {
2
- if (k > arr.length) return [];
3
- if (k === 1) return arr.map(x => [x]);
4
- if (k === arr.length) return [arr];
2
+ if (!Array.isArray(arr)) {
3
+ throw new TypeError('arr must be an array');
4
+ }
5
+ if (typeof k !== 'number' || !Number.isInteger(k)) {
6
+ throw new TypeError('k must be an integer');
7
+ }
8
+ if (k <= 0) {
9
+ throw new RangeError('k must be positive');
10
+ }
11
+ if (k > arr.length) {
12
+ throw new RangeError('k cannot exceed array length');
13
+ }
5
14
 
6
- const result = [];
7
- const combine = (start, current) => {
8
- if (current.length === k) {
9
- result.push([...current]);
10
- return;
11
- }
12
- for (let i = start; i < arr.length; i++) {
13
- current.push(arr[i]);
14
- combine(i + 1, current);
15
- current.pop();
16
- }
17
- };
15
+ if (k === 1) return arr.map(x => [x]);
16
+ if (k === arr.length) return [arr];
17
+
18
+ const result = [];
19
+ const combine = (start, current) => {
20
+ if (current.length === k) {
21
+ result.push([...current]);
22
+ return;
23
+ }
24
+ for (let i = start; i < arr.length; i++) {
25
+ current.push(arr[i]);
26
+ combine(i + 1, current);
27
+ current.pop();
28
+ }
29
+ };
18
30
 
19
- combine(0, []);
20
- return result;
21
- };
31
+ combine(0, []);
32
+ return result;
33
+ };
22
34
 
23
35
  export const permutations = (arr) => {
24
- if (arr.length <= 1) return [arr];
36
+ if (!Array.isArray(arr)) {
37
+ throw new TypeError('arr must be an array');
38
+ }
39
+ if (arr.length <= 1) return [arr];
25
40
 
26
- const result = [];
27
- const permute = (arr, start) => {
28
- if (start === arr.length - 1) {
29
- result.push([...arr]);
30
- return;
31
- }
32
- for (let i = start; i < arr.length; i++) {
33
- [arr[start], arr[i]] = [arr[i], arr[start]];
34
- permute(arr, start + 1);
35
- [arr[start], arr[i]] = [arr[i], arr[start]];
36
- }
37
- };
41
+ const result = [];
42
+ const permute = (arr, start) => {
43
+ if (start === arr.length - 1) {
44
+ result.push([...arr]);
45
+ return;
46
+ }
47
+ for (let i = start; i < arr.length; i++) {
48
+ [arr[start], arr[i]] = [arr[i], arr[start]];
49
+ permute(arr, start + 1);
50
+ [arr[start], arr[i]] = [arr[i], arr[start]];
51
+ }
52
+ };
38
53
 
39
- permute([...arr], 0);
40
- return result;
41
- };
54
+ permute([...arr], 0);
55
+ return result;
56
+ };
42
57
 
43
58
  export const kPermutations = (arr, k) => {
44
- if (k > arr.length) return [];
45
- if (k === 1) return arr.map(x => [x]);
59
+ if (!Array.isArray(arr)) {
60
+ throw new TypeError('arr must be an array');
61
+ }
62
+ if (typeof k !== 'number' || !Number.isInteger(k)) {
63
+ throw new TypeError('k must be an integer');
64
+ }
65
+ if (k <= 0) {
66
+ throw new RangeError('k must be positive');
67
+ }
68
+ if (k > arr.length) {
69
+ throw new RangeError('k cannot exceed array length');
70
+ }
71
+ if (k === 1) return arr.map(x => [x]);
46
72
 
47
- const result = [];
48
- const perm = (available, current) => {
49
- if (current.length === k) {
50
- result.push([...current]);
51
- return;
52
- }
53
- for (let i = 0; i < available.length; i++) {
54
- const item = available[i];
55
- const remaining = available.slice(0, i).concat(available.slice(i + 1));
56
- perm(remaining, [...current, item]);
57
- }
58
- };
73
+ const result = [];
74
+ const perm = (available, current) => {
75
+ if (current.length === k) {
76
+ result.push([...current]);
77
+ return;
78
+ }
79
+ for (let i = 0; i < available.length; i++) {
80
+ const item = available[i];
81
+ const remaining = available.slice(0, i).concat(available.slice(i + 1));
82
+ perm(remaining, [...current, item]);
83
+ }
84
+ };
59
85
 
60
- perm(arr, []);
61
- return result;
62
- };
86
+ perm(arr, []);
87
+ return result;
88
+ };
63
89
 
64
90
  export const randomCombination = (arr, k, rng) => {
65
- const indices = [];
66
- const n = arr.length;
67
-
68
- while (indices.length < k) {
69
- const idx = rng.nextInt(n);
70
- if (!indices.includes(idx)) {
71
- indices.push(idx);
91
+ if (!Array.isArray(arr)) {
92
+ throw new TypeError('arr must be an array');
93
+ }
94
+ if (typeof k !== 'number' || !Number.isInteger(k)) {
95
+ throw new TypeError('k must be an integer');
96
+ }
97
+ if (k <= 0) {
98
+ throw new RangeError('k must be positive');
99
+ }
100
+ if (k > arr.length) {
101
+ throw new RangeError('k cannot exceed array length');
102
+ }
103
+ if (!rng || typeof rng.nextInt !== 'function') {
104
+ throw new TypeError('rng must be an RNG instance');
72
105
  }
73
- }
74
-
75
- return indices.sort((a, b) => a - b).map(i => arr[i]);
76
- };
106
+ const indices = new Set();
107
+ const n = arr.length;
108
+
109
+ while (indices.size < k) {
110
+ indices.add(rng.nextInt(n));
111
+ }
112
+
113
+ return Array.from(indices).sort((a, b) => a - b).map(i => arr[i]);
114
+ };
77
115
 
78
116
  export const randomPermutation = (arr, rng) => {
79
- const copy = [...arr];
80
- for (let i = copy.length - 1; i > 0; i--) {
81
- const j = rng.nextInt(i + 1);
82
- [copy[i], copy[j]] = [copy[j], copy[i]];
83
- }
84
- return copy;
85
- };
117
+ if (!Array.isArray(arr)) {
118
+ throw new TypeError('arr must be array');
119
+ }
120
+ if (!rng || typeof rng.nextInt !== 'function') {
121
+ throw new TypeError('rng must be an RNG instance');
122
+ }
123
+ const copy = [...arr];
124
+ for (let i = copy.length - 1; i > 0; i--) {
125
+ const j = rng.nextInt(i + 1);
126
+ [copy[i], copy[j]] = [copy[j], copy[i]];
127
+ }
128
+ return copy;
129
+ };