@byloth/core 2.2.3 → 2.2.5

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.
@@ -580,7 +580,7 @@ export default class SmartIterator<T, R = void, N = undefined> implements Iterat
580
580
  * const iterator = new SmartIterator<number>([-2, -1, 0, 1, 2]);
581
581
  * const result = iterator.take(3);
582
582
  *
583
- * console.log(result.toArray()); // [-2, -1, 0]
583
+ * console.log(result.toArray()); // [-2, -1, 0]
584
584
  * console.log(iterator.toArray()); // [1, 2]
585
585
  * ```
586
586
  *
@@ -17,12 +17,12 @@ import type { FulfilledHandler, PromiseExecutor, RejectedHandler } from "./types
17
17
  * setTimeout(() => resolve("Hello, World!"), 1_000);
18
18
  * });
19
19
  *
20
- * console.log(promise.isPending); // true
20
+ * console.log(promise.isPending); // true
21
21
  * console.log(promise.isFulfilled); // false
22
22
  *
23
23
  * console.log(await promise); // "Hello, World!"
24
24
  *
25
- * console.log(promise.isPending); // false
25
+ * console.log(promise.isPending); // false
26
26
  * console.log(promise.isFulfilled); // true
27
27
  * ```
28
28
  *
@@ -39,14 +39,14 @@ export default class SmartPromise<T = void> implements Promise<T>
39
39
  *
40
40
  * @example
41
41
  * ```ts
42
- * const request = fetch("https://api.example.com/data");
43
- * const smartRequest = SmartPromise.FromPromise(request);
42
+ * const response = fetch("https://api.example.com/data");
43
+ * const smartResponse = SmartPromise.FromPromise(response);
44
44
  *
45
- * console.log(request.isPending); // Throws an error: `isPending` is not a property of `Promise`.
46
- * console.log(smartRequest.isPending); // true
45
+ * console.log(response.isPending); // Throws an error: `isPending` is not a property of `Promise`.
46
+ * console.log(smartResponse.isPending); // true
47
47
  *
48
- * const response = await request;
49
- * console.log(smartRequest.isFulfilled); // true
48
+ * const response = await response;
49
+ * console.log(smartResponse.isFulfilled); // true
50
50
  * ```
51
51
  *
52
52
  * ---
@@ -323,8 +323,8 @@ export default class SmartPromise<T = void> implements Promise<T>
323
323
  *
324
324
  *
325
325
  * promise
326
- * .then(() => console.log("OK!")) // Logs "OK!" if the promise is fulfilled.
327
- * .catch(() => console.log("KO!")) // Logs "KO!" if the promise is rejected.
326
+ * .then(() => console.log("OK!")) // Logs "OK!" if the promise is fulfilled.
327
+ * .catch(() => console.log("KO!")) // Logs "KO!" if the promise is rejected.
328
328
  * .finally(() => console.log("Done!")); // Always logs "Done!".
329
329
  * ```
330
330
  *
@@ -20,7 +20,7 @@ import type { MaybePromise, PromiseExecutor } from "./types.js";
20
20
  * }, 5_000);
21
21
  *
22
22
  * promise
23
- * .then((result) => console.log(result)) // "Hello, World!"
23
+ * .then((result) => console.log(result)) // "Hello, World!"
24
24
  * .catch((error) => console.error(error)); // "Uncaught TimeoutException: The operation has timed out."
25
25
  * ```
26
26
  *
@@ -5,5 +5,5 @@ export { delay, nextAnimationFrame, yieldToEventLoop } from "./async.js";
5
5
  export { dateDifference, dateRange, dateRound, getWeek, TimeUnit, WeekDay } from "./date.js";
6
6
  export { loadScript } from "./dom.js";
7
7
  export { chain, count, enumerate, range, shuffle, unique, zip } from "./iterator.js";
8
- export { average, hash, sum } from "./math.js";
8
+ export { average, clamp, hash, sum } from "./math.js";
9
9
  export { capitalize } from "./string.js";
package/src/utils/math.ts CHANGED
@@ -9,7 +9,7 @@ import { zip } from "./iterator.js";
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * average([1, 2, 3, 4, 5]); // 3
12
+ * average([1, 2, 3, 4, 5]); // 3
13
13
  * average([6, 8.5, 4], [3, 2, 1]); // 6.5
14
14
  * ```
15
15
  *
@@ -71,6 +71,39 @@ export function average<T extends number>(values: Iterable<T>, weights?: Iterabl
71
71
  return _sum / _count;
72
72
  }
73
73
 
74
+ /**
75
+ * Clamps a given value between a minimum and a maximum bound.
76
+ *
77
+ * ---
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * clamp(5, 0, 10); // 5
82
+ * clamp(-3, 0, 10); // 0
83
+ * clamp(15, 0, 10); // 10
84
+ * ```
85
+ *
86
+ * ---
87
+ *
88
+ * @param value The value to clamp.
89
+ * @param min The minimum bound.
90
+ * @param max The maximum bound.
91
+ *
92
+ * @returns The clamped value between the specified bounds.
93
+ */
94
+ export function clamp(value: number, min: number, max: number): number
95
+ {
96
+ if (min > max)
97
+ {
98
+ throw new ValueException("The minimum bound must be less than or equal to the maximum bound.");
99
+ }
100
+
101
+ if (value < min) { return min; }
102
+ if (value > max) { return max; }
103
+
104
+ return value;
105
+ }
106
+
74
107
  /**
75
108
  * An utility function to compute the hash of a given string.
76
109
  *
@@ -83,7 +116,7 @@ export function average<T extends number>(values: Iterable<T>, weights?: Iterabl
83
116
  * @example
84
117
  * ```ts
85
118
  * hash("Hello, world!"); // -1880044555
86
- * hash("How are you?"); // 1761539132
119
+ * hash("How are you?"); // 1761539132
87
120
  * ```
88
121
  *
89
122
  * ---
@@ -259,7 +259,7 @@ export default class Random
259
259
 
260
260
  if (weights === undefined)
261
261
  {
262
- const pool = [...elements];
262
+ const pool = Array.from(elements);
263
263
  const result: T[] = new Array(count);
264
264
 
265
265
  for (let index = 0; index < count; index += 1)
@@ -300,6 +300,131 @@ export default class Random
300
300
  return result;
301
301
  }
302
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
+
303
428
  private constructor() { /* ... */ }
304
429
 
305
430
  public readonly [Symbol.toStringTag]: string = "Random";