@byloth/core 2.2.2 → 2.2.4
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/dist/core.cjs +1 -1
- package/dist/core.cjs.map +1 -1
- package/dist/core.esm.js +550 -232
- package/dist/core.esm.js.map +1 -1
- package/dist/core.global.js +1 -1
- package/dist/core.global.js.map +1 -1
- package/dist/core.umd.cjs +1 -1
- package/dist/core.umd.cjs.map +1 -1
- package/package.json +8 -8
- package/src/index.ts +3 -1
- package/src/models/aggregators/index.ts +3 -5
- package/src/models/callbacks/callback-chain.ts +8 -1
- package/src/models/callbacks/index.ts +4 -6
- package/src/models/callbacks/publisher.ts +8 -2
- package/src/models/collections/array-view.ts +348 -0
- package/src/models/collections/index.ts +3 -4
- package/src/models/collections/map-view.ts +5 -3
- package/src/models/collections/set-view.ts +6 -4
- package/src/models/exceptions/index.ts +51 -10
- package/src/models/index.ts +2 -1
- package/src/models/iterators/index.ts +2 -4
- package/src/models/json/index.ts +1 -3
- package/src/models/promises/index.ts +4 -6
- package/src/models/timers/index.ts +3 -5
- package/src/utils/index.ts +2 -4
- package/src/utils/random.ts +246 -0
package/src/utils/random.ts
CHANGED
|
@@ -179,6 +179,252 @@ export default class Random
|
|
|
179
179
|
return elements[Random.Index(elements)];
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Picks a random sample of elements from a given array without replacement.
|
|
184
|
+
*
|
|
185
|
+
* Uses the Fisher-Yates shuffle algorithm for uniform sampling,
|
|
186
|
+
* which is O(count) instead of O(n log n) for a full shuffle.
|
|
187
|
+
*
|
|
188
|
+
* ---
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```ts
|
|
192
|
+
* Random.Sample([1, 2, 3, 4, 5], 3); // e.g., [4, 1, 5]
|
|
193
|
+
* ```
|
|
194
|
+
*
|
|
195
|
+
* ---
|
|
196
|
+
*
|
|
197
|
+
* @template T The type of the elements in the array.
|
|
198
|
+
*
|
|
199
|
+
* @param elements
|
|
200
|
+
* The array of elements to sample from.
|
|
201
|
+
*
|
|
202
|
+
* It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
|
|
203
|
+
*
|
|
204
|
+
* @param count
|
|
205
|
+
* The number of elements to sample.
|
|
206
|
+
*
|
|
207
|
+
* It must be between `0` and `elements.length`. Otherwise, a {@link ValueException} will be thrown.
|
|
208
|
+
*
|
|
209
|
+
* @returns An array containing the randomly sampled elements.
|
|
210
|
+
*/
|
|
211
|
+
public static Sample<T>(elements: readonly T[], count: number): T[];
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Picks a weighted random sample of elements from a given array without replacement.
|
|
215
|
+
*
|
|
216
|
+
* Uses the Efraimidis-Spirakis algorithm for weighted sampling.
|
|
217
|
+
* Elements with higher weights have a higher probability of being selected.
|
|
218
|
+
*
|
|
219
|
+
* ---
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```ts
|
|
223
|
+
* // Element "a" is 3x more likely to be picked than "b" or "c"
|
|
224
|
+
* Random.Sample(["a", "b", "c"], 2, [3, 1, 1]);
|
|
225
|
+
* ```
|
|
226
|
+
*
|
|
227
|
+
* ---
|
|
228
|
+
*
|
|
229
|
+
* @template T The type of the elements in the array.
|
|
230
|
+
*
|
|
231
|
+
* @param elements
|
|
232
|
+
* The array of elements to sample from.
|
|
233
|
+
*
|
|
234
|
+
* It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
|
|
235
|
+
*
|
|
236
|
+
* @param count
|
|
237
|
+
* The number of elements to sample.
|
|
238
|
+
*
|
|
239
|
+
* It must be between `0` and `elements.length`. Otherwise, a {@link ValueException} will be thrown.
|
|
240
|
+
*
|
|
241
|
+
* @param weights
|
|
242
|
+
* The weights associated with each element.
|
|
243
|
+
*
|
|
244
|
+
* It must have the same length as the elements array.
|
|
245
|
+
* All weights must be greater than zero. Otherwise, a {@link ValueException} will be thrown.
|
|
246
|
+
*
|
|
247
|
+
* @returns An array containing the randomly sampled elements.
|
|
248
|
+
*/
|
|
249
|
+
public static Sample<T>(elements: readonly T[], count: number, weights: readonly number[]): T[];
|
|
250
|
+
public static Sample<T>(elements: readonly T[], count: number, weights?: readonly number[]): T[]
|
|
251
|
+
{
|
|
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;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Splits a total amount into a given number of randomly balanced integer parts that sum to the total.
|
|
334
|
+
*
|
|
335
|
+
* Uses random cut-points to generate a uniform distribution of parts.
|
|
336
|
+
*
|
|
337
|
+
* ---
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* Random.Split(100, 3); // [28, 41, 31]
|
|
342
|
+
* Random.Split(10, 4); // [3, 1, 4, 2]
|
|
343
|
+
* ```
|
|
344
|
+
*
|
|
345
|
+
* ---
|
|
346
|
+
*
|
|
347
|
+
* @param total
|
|
348
|
+
* The total amount to split.
|
|
349
|
+
*
|
|
350
|
+
* It must be non-negative. Otherwise, a {@link ValueException} will be thrown.
|
|
351
|
+
*
|
|
352
|
+
* @param parts
|
|
353
|
+
* The number of parts to split the total into.
|
|
354
|
+
*
|
|
355
|
+
* It must be at least `1`. Otherwise, a {@link ValueException} will be thrown.
|
|
356
|
+
*
|
|
357
|
+
* @returns An array of integers that sum to the given total.
|
|
358
|
+
*/
|
|
359
|
+
public static Split(total: number, parts: number): number[];
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Splits an iterable of elements into a given number of randomly balanced groups.
|
|
363
|
+
*
|
|
364
|
+
* The elements are distributed into groups whose sizes are
|
|
365
|
+
* determined by a random split of the total number of elements.
|
|
366
|
+
*
|
|
367
|
+
* ---
|
|
368
|
+
*
|
|
369
|
+
* @example
|
|
370
|
+
* ```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"]]
|
|
374
|
+
* ```
|
|
375
|
+
*
|
|
376
|
+
* ---
|
|
377
|
+
*
|
|
378
|
+
* @template T The type of the elements in the iterable.
|
|
379
|
+
*
|
|
380
|
+
* @param elements
|
|
381
|
+
* The iterable of elements to split into groups.
|
|
382
|
+
*
|
|
383
|
+
* It must contain at least one element. Otherwise, a {@link ValueException} will be thrown.
|
|
384
|
+
*
|
|
385
|
+
* @param groups
|
|
386
|
+
* The number of groups to split the elements into.
|
|
387
|
+
*
|
|
388
|
+
* It must be between `1` and the number of elements.
|
|
389
|
+
* Otherwise, a {@link ValueException} will be thrown.
|
|
390
|
+
*
|
|
391
|
+
* @returns An array of arrays, each containing a subset of the original elements.
|
|
392
|
+
*/
|
|
393
|
+
public static Split<T>(elements: Iterable<T>, groups: number): T[][];
|
|
394
|
+
public static Split<T>(totalOrElements: number | Iterable<T>, parts: number): number[] | T[][]
|
|
395
|
+
{
|
|
396
|
+
if (parts < 1) { throw new ValueException("The number of splits must be greater than zero."); }
|
|
397
|
+
|
|
398
|
+
if (typeof totalOrElements === "number")
|
|
399
|
+
{
|
|
400
|
+
if (totalOrElements < 0) { throw new ValueException("The total must be a non-negative number."); }
|
|
401
|
+
|
|
402
|
+
return this.#Split(totalOrElements, parts);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const elements = Array.from(totalOrElements);
|
|
406
|
+
const length = elements.length;
|
|
407
|
+
|
|
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
|
+
}
|
|
413
|
+
|
|
414
|
+
const sizes = this.#Split(length, parts);
|
|
415
|
+
const groups: T[][] = new Array(parts);
|
|
416
|
+
|
|
417
|
+
let offset = 0;
|
|
418
|
+
for (let index = 0; index < parts; index += 1)
|
|
419
|
+
{
|
|
420
|
+
groups[index] = elements.slice(offset, offset + sizes[index]);
|
|
421
|
+
|
|
422
|
+
offset += sizes[index];
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return groups;
|
|
426
|
+
}
|
|
427
|
+
|
|
182
428
|
private constructor() { /* ... */ }
|
|
183
429
|
|
|
184
430
|
public readonly [Symbol.toStringTag]: string = "Random";
|