@byloth/core 2.2.6 → 2.2.7

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.
@@ -2,15 +2,194 @@ import { ValueException } from "../models/index.js";
2
2
 
3
3
  /**
4
4
  * A wrapper class around the native {@link Math.random} function that
5
- * provides a set of methods to generate random values more easily.
5
+ * provides a set of methods to generate random values more easily.
6
6
  * It can be used to generate random numbers, booleans and other different values.
7
7
  *
8
- * It cannot be instantiated directly.
8
+ * The class exposes two coexisting surfaces:
9
+ * - A **static API** (`Random.Integer`, `Random.Boolean`, …) that uses
10
+ * {@link Math.random} and is therefore non-deterministic.
11
+ * - An **instance API** with the same method names that uses a seeded PRNG
12
+ * (Mulberry32) for reproducible sequences. Instances are created via the
13
+ * {@link Random.FromSeed} factory; the constructor is private.
9
14
  */
10
15
  export default class Random
11
16
  {
17
+ static #Mulberry32(seed: number): () => number
18
+ {
19
+ let state = seed | 0;
20
+
21
+ return () =>
22
+ {
23
+ state = (state + 0x6D2B79F5) | 0;
24
+
25
+ let t = state;
26
+ t = Math.imul(t ^ (t >>> 15), t | 1);
27
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
28
+
29
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
30
+ };
31
+ }
32
+
33
+ private static _Boolean(random: () => number, ratio: number): boolean
34
+ {
35
+ return (random() < ratio);
36
+ }
37
+
38
+ private static _Integer(random: () => number, min: number, max?: number): number
39
+ {
40
+ if (max === undefined) { return Math.floor(random() * min); }
41
+
42
+ return Math.floor(random() * (max - min) + min);
43
+ }
44
+
45
+ private static _Decimal(random: () => number, min?: number, max?: number): number
46
+ {
47
+ if (min === undefined) { return random(); }
48
+ if (max === undefined) { return (random() * min); }
49
+
50
+ return (random() * (max - min) + min);
51
+ }
52
+
53
+ private static _Index<T>(random: () => number, elements: readonly T[]): number
54
+ {
55
+ if (elements.length === 0) { throw new ValueException("You must provide at least one element."); }
56
+
57
+ return Random._Integer(random, elements.length);
58
+ }
59
+
60
+ private static _Choice<T>(random: () => number, elements: readonly T[]): T
61
+ {
62
+ return elements[Random._Index(random, elements)];
63
+ }
64
+
65
+ private static _Sample<T>(
66
+ random: () => number,
67
+ elements: readonly T[],
68
+ count: number,
69
+ weights?: readonly number[]
70
+ ): T[]
71
+ {
72
+ const length = elements.length;
73
+
74
+ if (length === 0) { throw new ValueException("You must provide at least one element."); }
75
+ if (count < 0) { throw new ValueException("Count must be non-negative."); }
76
+ if (count > length) { throw new ValueException("Count cannot exceed the number of elements."); }
77
+
78
+ if (count === 0) { return []; }
79
+
80
+ if (weights === undefined)
81
+ {
82
+ const pool = Array.from(elements);
83
+ const result: T[] = new Array(count);
84
+
85
+ for (let index = 0; index < count; index += 1)
86
+ {
87
+ const randomIndex = Random._Integer(random, index, length);
88
+
89
+ result[index] = pool[randomIndex];
90
+ pool[randomIndex] = pool[index];
91
+ }
92
+
93
+ return result;
94
+ }
95
+
96
+ if (weights.length !== length)
97
+ {
98
+ throw new ValueException("Weights array must have the same length as elements array.");
99
+ }
100
+
101
+ const keys: ({ index: number, key: number })[] = new Array(length);
102
+ for (let index = 0; index < length; index += 1)
103
+ {
104
+ if (weights[index] <= 0)
105
+ {
106
+ throw new ValueException(`Weight for element #${index} must be greater than zero.`);
107
+ }
108
+
109
+ keys[index] = { index: index, key: Math.pow(random(), 1 / weights[index]) };
110
+ }
111
+
112
+ keys.sort((a, b) => b.key - a.key);
113
+
114
+ const result: T[] = new Array(count);
115
+ for (let index = 0; index < count; index += 1)
116
+ {
117
+ result[index] = elements[keys[index].index];
118
+ }
119
+
120
+ return result;
121
+ }
122
+
123
+ static #Split(random: () => number, total: number, parts: number): number[]
124
+ {
125
+ const cuts: number[] = new Array(parts - 1);
126
+ for (let index = 0; index < cuts.length; index += 1)
127
+ {
128
+ cuts[index] = random() * total;
129
+ }
130
+
131
+ cuts.sort((a, b) => (a - b));
132
+
133
+ const boundaries = [0, ...cuts, total];
134
+ const values: number[] = new Array(parts);
135
+
136
+ for (let index = 0; index < parts; index += 1)
137
+ {
138
+ values[index] = Math.floor(boundaries[index + 1] - boundaries[index]);
139
+ }
140
+
141
+ let remainder = total - values.reduce((sum, val) => (sum + val), 0);
142
+ while (remainder > 0)
143
+ {
144
+ values[Random._Integer(random, parts)] += 1;
145
+
146
+ remainder -= 1;
147
+ }
148
+
149
+ return values;
150
+ }
151
+
152
+ private static _Split<T>(
153
+ random: () => number,
154
+ totalOrElements: number | Iterable<T>,
155
+ parts: number
156
+ ): number[] | T[][]
157
+ {
158
+ if (parts < 1) { throw new ValueException("The number of splits must be greater than zero."); }
159
+
160
+ if (typeof totalOrElements === "number")
161
+ {
162
+ if (totalOrElements < 0) { throw new ValueException("The total must be a non-negative number."); }
163
+
164
+ return Random.#Split(random, totalOrElements, parts);
165
+ }
166
+
167
+ const elements = Array.from(totalOrElements);
168
+ const length = elements.length;
169
+
170
+ if (length === 0) { throw new ValueException("You must provide at least one element."); }
171
+ if (parts > length)
172
+ {
173
+ throw new ValueException("The number of splits cannot exceed the number of elements.");
174
+ }
175
+
176
+ const sizes = Random.#Split(random, length, parts);
177
+ const groups: T[][] = new Array(parts);
178
+
179
+ let offset = 0;
180
+ for (let index = 0; index < parts; index += 1)
181
+ {
182
+ groups[index] = elements.slice(offset, offset + sizes[index]);
183
+
184
+ offset += sizes[index];
185
+ }
186
+
187
+ return groups;
188
+ }
189
+
12
190
  /**
13
- * Generates a random boolean value.
191
+ * Generates a random boolean value.
192
+ * See also {@link Random.boolean} for the seeded & deterministic counterpart.
14
193
  *
15
194
  * ---
16
195
  *
@@ -33,17 +212,18 @@ export default class Random
33
212
  */
34
213
  public static Boolean(ratio = 0.5): boolean
35
214
  {
36
- return (Math.random() < ratio);
215
+ return Random._Boolean(Math.random, ratio);
37
216
  }
38
217
 
39
218
  /**
40
- * Generates a random integer value between `0` (included) and `max` (excluded).
219
+ * Generates a random integer value between `0` (included) and `max` (excluded).
220
+ * See also {@link Random.integer} for the seeded & deterministic counterpart.
41
221
  *
42
222
  * ---
43
223
  *
44
224
  * @example
45
225
  * ```ts
46
- * Random.Integer(5); // 0, 1, 2, 3, 4
226
+ * Random.Integer(5); // [0, 5)
47
227
  * ```
48
228
  *
49
229
  * ---
@@ -55,13 +235,14 @@ export default class Random
55
235
  public static Integer(max: number): number;
56
236
 
57
237
  /**
58
- * Generates a random integer value between `min` (included) and `max` (excluded).
238
+ * Generates a random integer value between `min` (included) and `max` (excluded).
239
+ * See also {@link Random.integer} for the seeded & deterministic counterpart.
59
240
  *
60
241
  * ---
61
242
  *
62
243
  * @example
63
244
  * ```ts
64
- * Random.Integer(2, 7); // 2, 3, 4, 5, 6
245
+ * Random.Integer(2, 7); // [2, 7)
65
246
  * ```
66
247
  *
67
248
  * ---
@@ -74,19 +255,18 @@ export default class Random
74
255
  public static Integer(min: number, max: number): number;
75
256
  public static Integer(min: number, max?: number): number
76
257
  {
77
- if (max === undefined) { return Math.floor(Math.random() * min); }
78
-
79
- return Math.floor(Math.random() * (max - min) + min);
258
+ return Random._Integer(Math.random, min, max);
80
259
  }
81
260
 
82
261
  /**
83
- * Generates a random decimal value between `0` (included) and `1` (excluded).
262
+ * Generates a random decimal value between `0` (included) and `1` (excluded).
263
+ * See also {@link Random.decimal} for the seeded & deterministic counterpart.
84
264
  *
85
265
  * ---
86
266
  *
87
267
  * @example
88
268
  * ```ts
89
- * Random.Decimal(); // 0.123456789
269
+ * Random.Decimal(); // e.g. 0.123456789
90
270
  * ```
91
271
  *
92
272
  * ---
@@ -96,13 +276,14 @@ export default class Random
96
276
  public static Decimal(): number;
97
277
 
98
278
  /**
99
- * Generates a random decimal value between `0` (included) and `max` (excluded).
279
+ * Generates a random decimal value between `0` (included) and `max` (excluded).
280
+ * See also {@link Random.decimal} for the seeded & deterministic counterpart.
100
281
  *
101
282
  * ---
102
283
  *
103
284
  * @example
104
285
  * ```ts
105
- * Random.Decimal(5); // 2.3456789
286
+ * Random.Decimal(5); // e.g. 2.3456789
106
287
  * ```
107
288
  *
108
289
  * ---
@@ -114,13 +295,14 @@ export default class Random
114
295
  public static Decimal(max: number): number;
115
296
 
116
297
  /**
117
- * Generates a random decimal value between `min` (included) and `max` (excluded).
298
+ * Generates a random decimal value between `min` (included) and `max` (excluded).
299
+ * See also {@link Random.decimal} for the seeded & deterministic counterpart.
118
300
  *
119
301
  * ---
120
302
  *
121
303
  * @example
122
304
  * ```ts
123
- * Random.Decimal(2, 7); // 4.56789
305
+ * Random.Decimal(2, 7); // e.g. 4.56789
124
306
  * ```
125
307
  *
126
308
  * ---
@@ -128,19 +310,26 @@ export default class Random
128
310
  * @param min The minimum value (included).
129
311
  * @param max The maximum value (excluded).
130
312
  *
131
- * @returns A random decimal value
313
+ * @returns A random decimal value.
132
314
  */
133
315
  public static Decimal(min: number, max: number): number;
134
316
  public static Decimal(min?: number, max?: number): number
135
317
  {
136
- if (min === undefined) { return Math.random(); }
137
- if (max === undefined) { return (Math.random() * min); }
138
-
139
- return (Math.random() * (max - min) + min);
318
+ return Random._Decimal(Math.random, min, max);
140
319
  }
141
320
 
142
321
  /**
143
- * Picks a random valid index from a given array of elements.
322
+ * Picks a random valid index from a given array of elements.
323
+ * See also {@link Random.index} for the seeded & deterministic counterpart.
324
+ *
325
+ * ---
326
+ *
327
+ * @example
328
+ * ```ts
329
+ * const elements = ["a", "b", "c"];
330
+ *
331
+ * Random.Index(elements); // 0, 1, or 2
332
+ * ```
144
333
  *
145
334
  * ---
146
335
  *
@@ -155,13 +344,21 @@ export default class Random
155
344
  */
156
345
  public static Index<T>(elements: readonly T[]): number
157
346
  {
158
- if (elements.length === 0) { throw new ValueException("You must provide at least one element."); }
159
-
160
- return this.Integer(elements.length);
347
+ return Random._Index(Math.random, elements);
161
348
  }
162
349
 
163
350
  /**
164
- * Picks a random element from a given array of elements.
351
+ * Picks a random element from a given array of elements.
352
+ * See also {@link Random.choice} for the seeded & deterministic counterpart.
353
+ *
354
+ * ---
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * const elements = ["a", "b", "c"];
359
+ *
360
+ * Random.Choice(elements); // "a", "b", or "c"
361
+ * ```
165
362
  *
166
363
  * ---
167
364
  *
@@ -176,11 +373,12 @@ export default class Random
176
373
  */
177
374
  public static Choice<T>(elements: readonly T[]): T
178
375
  {
179
- return elements[Random.Index(elements)];
376
+ return Random._Choice(Math.random, elements);
180
377
  }
181
378
 
182
379
  /**
183
- * Picks a random sample of elements from a given array without replacement.
380
+ * Picks a random sample of elements from a given array without replacement.
381
+ * See also {@link Random.sample} for the seeded & deterministic counterpart.
184
382
  *
185
383
  * Uses the Fisher-Yates shuffle algorithm for uniform sampling,
186
384
  * which is O(count) instead of O(n log n) for a full shuffle.
@@ -189,7 +387,7 @@ export default class Random
189
387
  *
190
388
  * @example
191
389
  * ```ts
192
- * Random.Sample([1, 2, 3, 4, 5], 3); // e.g., [4, 1, 5]
390
+ * Random.Sample([1, 2, 3, 4, 5], 3); // e.g. [4, 1, 5]
193
391
  * ```
194
392
  *
195
393
  * ---
@@ -211,7 +409,8 @@ export default class Random
211
409
  public static Sample<T>(elements: readonly T[], count: number): T[];
212
410
 
213
411
  /**
214
- * Picks a weighted random sample of elements from a given array without replacement.
412
+ * Picks a weighted random sample of elements from a given array without replacement.
413
+ * See also {@link Random.sample} for the seeded & deterministic counterpart.
215
414
  *
216
415
  * Uses the Efraimidis-Spirakis algorithm for weighted sampling.
217
416
  * Elements with higher weights have a higher probability of being selected.
@@ -221,7 +420,7 @@ export default class Random
221
420
  * @example
222
421
  * ```ts
223
422
  * // Element "a" is 3x more likely to be picked than "b" or "c"
224
- * Random.Sample(["a", "b", "c"], 2, [3, 1, 1]);
423
+ * Random.Sample(["a", "b", "c"], 2, [3, 1, 1]); // e.g. ["a", "c"]
225
424
  * ```
226
425
  *
227
426
  * ---
@@ -249,88 +448,12 @@ export default class Random
249
448
  public static Sample<T>(elements: readonly T[], count: number, weights: readonly number[]): T[];
250
449
  public static Sample<T>(elements: readonly T[], count: number, weights?: readonly number[]): T[]
251
450
  {
252
- const length = elements.length;
253
-
254
- if (length === 0) { throw new ValueException("You must provide at least one element."); }
255
- if (count < 0) { throw new ValueException("Count must be non-negative."); }
256
- if (count > length) { throw new ValueException("Count cannot exceed the number of elements."); }
257
-
258
- if (count === 0) { return []; }
259
-
260
- if (weights === undefined)
261
- {
262
- const pool = Array.from(elements);
263
- const result: T[] = new Array(count);
264
-
265
- for (let index = 0; index < count; index += 1)
266
- {
267
- const randomIndex = this.Integer(index, length);
268
-
269
- result[index] = pool[randomIndex];
270
- pool[randomIndex] = pool[index];
271
- }
272
-
273
- return result;
274
- }
275
-
276
- if (weights.length !== length)
277
- {
278
- throw new ValueException("Weights array must have the same length as elements array.");
279
- }
280
-
281
- const keys: ({ index: number, key: number })[] = new Array(length);
282
- for (let index = 0; index < length; index += 1)
283
- {
284
- if (weights[index] <= 0)
285
- {
286
- throw new ValueException(`Weight for element #${index} must be greater than zero.`);
287
- }
288
-
289
- keys[index] = { index: index, key: Math.pow(Math.random(), 1 / weights[index]) };
290
- }
291
-
292
- keys.sort((a, b) => b.key - a.key);
293
-
294
- const result: T[] = new Array(count);
295
- for (let index = 0; index < count; index += 1)
296
- {
297
- result[index] = elements[keys[index].index];
298
- }
299
-
300
- return result;
301
- }
302
-
303
- static #Split(total: number, parts: number): number[]
304
- {
305
- const cuts: number[] = new Array(parts - 1);
306
- for (let index = 0; index < cuts.length; index += 1)
307
- {
308
- cuts[index] = Math.random() * total;
309
- }
310
-
311
- cuts.sort((a, b) => (a - b));
312
-
313
- const boundaries = [0, ...cuts, total];
314
- const values: number[] = new Array(parts);
315
-
316
- for (let index = 0; index < parts; index += 1)
317
- {
318
- values[index] = Math.floor(boundaries[index + 1] - boundaries[index]);
319
- }
320
-
321
- let remainder = total - values.reduce((sum, val) => (sum + val), 0);
322
- while (remainder > 0)
323
- {
324
- values[this.Integer(parts)] += 1;
325
-
326
- remainder -= 1;
327
- }
328
-
329
- return values;
451
+ return Random._Sample(Math.random, elements, count, weights);
330
452
  }
331
453
 
332
454
  /**
333
- * Splits a total amount into a given number of randomly balanced integer parts that sum to the total.
455
+ * Splits a total amount into a given number of randomly balanced integer parts that sum to the total.
456
+ * See also {@link Random.split} for the seeded & deterministic counterpart.
334
457
  *
335
458
  * Uses random cut-points to generate a uniform distribution of parts.
336
459
  *
@@ -338,8 +461,8 @@ export default class Random
338
461
  *
339
462
  * @example
340
463
  * ```ts
341
- * Random.Split(100, 3); // [28, 41, 31]
342
- * Random.Split(10, 4); // [3, 1, 4, 2]
464
+ * Random.Split(100, 3); // e.g. [28, 41, 31]
465
+ * Random.Split(10, 4); // e.g. [3, 1, 4, 2]
343
466
  * ```
344
467
  *
345
468
  * ---
@@ -359,7 +482,8 @@ export default class Random
359
482
  public static Split(total: number, parts: number): number[];
360
483
 
361
484
  /**
362
- * Splits an iterable of elements into a given number of randomly balanced groups.
485
+ * Splits an iterable of elements into a given number of randomly balanced groups.
486
+ * See also {@link Random.split} for the seeded & deterministic counterpart.
363
487
  *
364
488
  * The elements are distributed into groups whose sizes are
365
489
  * determined by a random split of the total number of elements.
@@ -368,9 +492,9 @@ export default class Random
368
492
  *
369
493
  * @example
370
494
  * ```ts
371
- * Random.Split([1, 2, 3, 4, 5], 2); // [[1, 2], [3, 4, 5]]
372
- * Random.Split([1, 2, 3, 4, 5], 2); // [[1, 2, 3, 4], [5]]
373
- * Random.Split("abcdef", 3); // [["a"], ["b", "c", "d"], ["e", "f"]]
495
+ * Random.Split([1, 2, 3, 4, 5], 2); // e.g. [[1, 2], [3, 4, 5]]
496
+ * Random.Split([1, 2, 3, 4, 5], 2); // e.g. [[1, 2, 3, 4], [5]]
497
+ * Random.Split("abcdef", 3); // e.g. [["a"], ["b", "c", "d"], ["e", "f"]]
374
498
  * ```
375
499
  *
376
500
  * ---
@@ -393,39 +517,398 @@ export default class Random
393
517
  public static Split<T>(elements: Iterable<T>, groups: number): T[][];
394
518
  public static Split<T>(totalOrElements: number | Iterable<T>, parts: number): number[] | T[][]
395
519
  {
396
- if (parts < 1) { throw new ValueException("The number of splits must be greater than zero."); }
520
+ return Random._Split(Math.random, totalOrElements, parts);
521
+ }
397
522
 
398
- if (typeof totalOrElements === "number")
399
- {
400
- if (totalOrElements < 0) { throw new ValueException("The total must be a non-negative number."); }
523
+ /**
524
+ * Creates a new seedable {@link Random} generator instance.
525
+ *
526
+ * The returned instance exposes the same API as the static {@link Random} class,
527
+ * but produces deterministic sequences driven by the given seed.
528
+ * Two instances built with the same seed will emit the same values in the same order.
529
+ *
530
+ * Internally, values are produced by a Mulberry32 PRNG.
531
+ *
532
+ * ---
533
+ *
534
+ * @example
535
+ * ```ts
536
+ * const rng = Random.FromSeed(42); // deterministic — same seed, same sequence
537
+ *
538
+ * rng.integer(100); // 60
539
+ * rng.decimal(); // 0.44829055899754167
540
+ * ```
541
+ *
542
+ * ---
543
+ *
544
+ * @param seed The 32-bit integer seed used to initialize the generator.
545
+ *
546
+ * @returns A new {@link Random} instance bound to the given seed.
547
+ */
548
+ public static FromSeed(seed: number): Random
549
+ {
550
+ return new Random(seed);
551
+ }
401
552
 
402
- return this.#Split(totalOrElements, parts);
403
- }
553
+ private readonly _next: () => number;
404
554
 
405
- const elements = Array.from(totalOrElements);
406
- const length = elements.length;
555
+ private constructor(seed: number)
556
+ {
557
+ this._next = Random.#Mulberry32(seed);
558
+ }
407
559
 
408
- if (length === 0) { throw new ValueException("You must provide at least one element."); }
409
- if (parts > length)
410
- {
411
- throw new ValueException("The number of splits cannot exceed the number of elements.");
412
- }
560
+ /**
561
+ * Generates a random boolean value.
562
+ * See also {@link Random.Boolean} for the static & non-deterministic counterpart.
563
+ *
564
+ * ---
565
+ *
566
+ * @example
567
+ * ```ts
568
+ * const rng = Random.FromSeed(...);
569
+ *
570
+ * if (rng.boolean())
571
+ * {
572
+ * // Do something...
573
+ * }
574
+ * ```
575
+ *
576
+ * ---
577
+ *
578
+ * @param ratio
579
+ * The probability of generating `true`.
580
+ *
581
+ * It must be included between `0` and `1`. Default is `0.5`.
582
+ *
583
+ * @returns A random boolean value.
584
+ */
585
+ public boolean(ratio = 0.5): boolean
586
+ {
587
+ return Random._Boolean(this._next, ratio);
588
+ }
413
589
 
414
- const sizes = this.#Split(length, parts);
415
- const groups: T[][] = new Array(parts);
590
+ /**
591
+ * Generates a random integer value between `0` (included) and `max` (excluded).
592
+ * See also {@link Random.Integer} for the static & non-deterministic counterpart.
593
+ *
594
+ * ---
595
+ *
596
+ * @example
597
+ * ```ts
598
+ * const rng = Random.FromSeed(...);
599
+ *
600
+ * rng.integer(5); // [0, 5)
601
+ * ```
602
+ *
603
+ * ---
604
+ *
605
+ * @param max The maximum value (excluded).
606
+ *
607
+ * @returns A random integer value.
608
+ */
609
+ public integer(max: number): number;
416
610
 
417
- let offset = 0;
418
- for (let index = 0; index < parts; index += 1)
419
- {
420
- groups[index] = elements.slice(offset, offset + sizes[index]);
611
+ /**
612
+ * Generates a random integer value between `min` (included) and `max` (excluded).
613
+ * See also {@link Random.Integer} for the static & non-deterministic counterpart.
614
+ *
615
+ * ---
616
+ *
617
+ * @example
618
+ * ```ts
619
+ * const rng = Random.FromSeed(...);
620
+ *
621
+ * rng.integer(2, 7); // [2, 7)
622
+ * ```
623
+ *
624
+ * ---
625
+ *
626
+ * @param min The minimum value (included).
627
+ * @param max The maximum value (excluded).
628
+ *
629
+ * @returns A random integer value.
630
+ */
631
+ public integer(min: number, max: number): number;
632
+ public integer(min: number, max?: number): number
633
+ {
634
+ return Random._Integer(this._next, min, max);
635
+ }
421
636
 
422
- offset += sizes[index];
423
- }
637
+ /**
638
+ * Generates a random decimal value between `0` (included) and `1` (excluded).
639
+ * See also {@link Random.Decimal} for the static & non-deterministic counterpart.
640
+ *
641
+ * ---
642
+ *
643
+ * @example
644
+ * ```ts
645
+ * const rng = Random.FromSeed(...);
646
+ *
647
+ * rng.decimal(); // e.g. 0.123456789
648
+ * ```
649
+ *
650
+ * ---
651
+ *
652
+ * @returns A random decimal value.
653
+ */
654
+ public decimal(): number;
424
655
 
425
- return groups;
656
+ /**
657
+ * Generates a random decimal value between `0` (included) and `max` (excluded).
658
+ * See also {@link Random.Decimal} for the static & non-deterministic counterpart.
659
+ *
660
+ * ---
661
+ *
662
+ * @example
663
+ * ```ts
664
+ * const rng = Random.FromSeed(...);
665
+ *
666
+ * rng.decimal(5); // e.g. 2.3456789
667
+ * ```
668
+ *
669
+ * ---
670
+ *
671
+ * @param max The maximum value (excluded).
672
+ *
673
+ * @returns A random decimal value.
674
+ */
675
+ public decimal(max: number): number;
676
+
677
+ /**
678
+ * Generates a random decimal value between `min` (included) and `max` (excluded).
679
+ * See also {@link Random.Decimal} for the static & non-deterministic counterpart.
680
+ *
681
+ * ---
682
+ *
683
+ * @example
684
+ * ```ts
685
+ * const rng = Random.FromSeed(...);
686
+ *
687
+ * rng.decimal(2, 7); // e.g. 4.56789
688
+ * ```
689
+ *
690
+ * ---
691
+ *
692
+ * @param min The minimum value (included).
693
+ * @param max The maximum value (excluded).
694
+ *
695
+ * @returns A random decimal value.
696
+ */
697
+ public decimal(min: number, max: number): number;
698
+ public decimal(min?: number, max?: number): number
699
+ {
700
+ return Random._Decimal(this._next, min, max);
701
+ }
702
+
703
+ /**
704
+ * Picks a random valid index from a given array of elements.
705
+ * See also {@link Random.Index} for the static & non-deterministic counterpart.
706
+ *
707
+ * ---
708
+ *
709
+ * @example
710
+ * ```ts
711
+ * const rng = Random.FromSeed(...);
712
+ *
713
+ * rng.index(["a", "b", "c"]); // 0, 1, or 2
714
+ * ```
715
+ *
716
+ * ---
717
+ *
718
+ * @template T The type of the elements in the array.
719
+ *
720
+ * @param elements
721
+ * The array of elements to pick from.
722
+ *
723
+ * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
724
+ *
725
+ * @returns A valid random index from the given array.
726
+ */
727
+ public index<T>(elements: readonly T[]): number
728
+ {
729
+ return Random._Index(this._next, elements);
730
+ }
731
+
732
+ /**
733
+ * Picks a random element from a given array of elements.
734
+ * See also {@link Random.Choice} for the static & non-deterministic counterpart.
735
+ *
736
+ * ---
737
+ *
738
+ * @example
739
+ * ```ts
740
+ * const rng = Random.FromSeed(...);
741
+ *
742
+ * rng.choice(["a", "b", "c"]); // "a", "b", or "c"
743
+ * ```
744
+ *
745
+ * ---
746
+ *
747
+ * @template T The type of the elements in the array.
748
+ *
749
+ * @param elements
750
+ * The array of elements to pick from.
751
+ *
752
+ * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
753
+ *
754
+ * @returns A random element from the given array.
755
+ */
756
+ public choice<T>(elements: readonly T[]): T
757
+ {
758
+ return Random._Choice(this._next, elements);
759
+ }
760
+
761
+ /**
762
+ * Picks a random sample of elements from a given array without replacement.
763
+ * See also {@link Random.Sample} for the static & non-deterministic counterpart.
764
+ *
765
+ * Uses the Fisher-Yates shuffle algorithm for uniform sampling,
766
+ * which is O(count) instead of O(n log n) for a full shuffle.
767
+ *
768
+ * ---
769
+ *
770
+ * @example
771
+ * ```ts
772
+ * const rng = Random.FromSeed(...);
773
+ *
774
+ * rng.sample([1, 2, 3, 4, 5], 3); // e.g. [4, 1, 5]
775
+ * ```
776
+ *
777
+ * ---
778
+ *
779
+ * @template T The type of the elements in the array.
780
+ *
781
+ * @param elements
782
+ * The array of elements to sample from.
783
+ *
784
+ * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
785
+ *
786
+ * @param count
787
+ * The number of elements to sample.
788
+ *
789
+ * It must be between `0` and `elements.length`. Otherwise, a {@link ValueException} will be thrown.
790
+ *
791
+ * @returns An array containing the randomly sampled elements.
792
+ */
793
+ public sample<T>(elements: readonly T[], count: number): T[];
794
+
795
+ /**
796
+ * Picks a weighted random sample of elements from a given array without replacement.
797
+ * See also {@link Random.Sample} for the static & non-deterministic counterpart.
798
+ *
799
+ * Uses the Efraimidis-Spirakis algorithm for weighted sampling.
800
+ * Elements with higher weights have a higher probability of being selected.
801
+ *
802
+ * ---
803
+ *
804
+ * @example
805
+ * ```ts
806
+ * const rng = Random.FromSeed(...);
807
+ *
808
+ * // Element "a" is 3x more likely to be picked than "b" or "c"
809
+ * rng.sample(["a", "b", "c"], 2, [3, 1, 1]); // e.g. ["a", "c"]
810
+ * ```
811
+ *
812
+ * ---
813
+ *
814
+ * @template T The type of the elements in the array.
815
+ *
816
+ * @param elements
817
+ * The array of elements to sample from.
818
+ *
819
+ * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
820
+ *
821
+ * @param count
822
+ * The number of elements to sample.
823
+ *
824
+ * It must be between `0` and `elements.length`. Otherwise, a {@link ValueException} will be thrown.
825
+ *
826
+ * @param weights
827
+ * The weights associated with each element.
828
+ *
829
+ * It must have the same length as the elements array.
830
+ * All weights must be greater than zero. Otherwise, a {@link ValueException} will be thrown.
831
+ *
832
+ * @returns An array containing the randomly sampled elements.
833
+ */
834
+ public sample<T>(elements: readonly T[], count: number, weights: readonly number[]): T[];
835
+ public sample<T>(elements: readonly T[], count: number, weights?: readonly number[]): T[]
836
+ {
837
+ return Random._Sample(this._next, elements, count, weights);
426
838
  }
427
839
 
428
- private constructor() { /* ... */ }
840
+ /**
841
+ * Splits a total amount into a given number of randomly balanced integer parts that sum to the total.
842
+ * See also {@link Random.Split} for the static & non-deterministic counterpart.
843
+ *
844
+ * Uses random cut-points to generate a uniform distribution of parts.
845
+ *
846
+ * ---
847
+ *
848
+ * @example
849
+ * ```ts
850
+ * const rng = Random.FromSeed(...);
851
+ *
852
+ * rng.split(100, 3); // e.g. [28, 41, 31]
853
+ * rng.split(10, 4); // e.g. [3, 1, 4, 2]
854
+ * ```
855
+ *
856
+ * ---
857
+ *
858
+ * @param total
859
+ * The total amount to split.
860
+ *
861
+ * It must be non-negative. Otherwise, a {@link ValueException} will be thrown.
862
+ *
863
+ * @param parts
864
+ * The number of parts to split the total into.
865
+ *
866
+ * It must be at least `1`. Otherwise, a {@link ValueException} will be thrown.
867
+ *
868
+ * @returns An array of integers that sum to the given total.
869
+ */
870
+ public split(total: number, parts: number): number[];
871
+
872
+ /**
873
+ * Splits an iterable of elements into a given number of randomly balanced groups.
874
+ * See also {@link Random.Split} for the static & non-deterministic counterpart.
875
+ *
876
+ * The elements are distributed into groups whose sizes are
877
+ * determined by a random split of the total number of elements.
878
+ *
879
+ * ---
880
+ *
881
+ * @example
882
+ * ```ts
883
+ * const rng = Random.FromSeed(...);
884
+ *
885
+ * rng.split([1, 2, 3, 4, 5], 2); // e.g. [[1, 2], [3, 4, 5]]
886
+ * rng.split([1, 2, 3, 4, 5], 2); // e.g. [[1, 2, 3, 4], [5]]
887
+ * rng.split("abcdef", 3); // e.g. [["a"], ["b", "c", "d"], ["e", "f"]]
888
+ * ```
889
+ *
890
+ * ---
891
+ *
892
+ * @template T The type of the elements in the iterable.
893
+ *
894
+ * @param elements
895
+ * The iterable of elements to split into groups.
896
+ *
897
+ * It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
898
+ *
899
+ * @param groups
900
+ * The number of groups to split the elements into.
901
+ *
902
+ * It must be between `1` and the number of elements.
903
+ * Otherwise, a {@link ValueException} will be thrown.
904
+ *
905
+ * @returns An array of arrays, each containing a subset of the original elements.
906
+ */
907
+ public split<T>(elements: Iterable<T>, groups: number): T[][];
908
+ public split<T>(totalOrElements: number | Iterable<T>, parts: number): number[] | T[][]
909
+ {
910
+ return Random._Split(this._next, totalOrElements, parts);
911
+ }
429
912
 
430
913
  public readonly [Symbol.toStringTag]: string = "Random";
431
914
  }