@byloth/core 2.0.0-rc.9 → 2.0.1

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 (47) hide show
  1. package/dist/core.js +4087 -621
  2. package/dist/core.js.map +1 -1
  3. package/dist/core.umd.cjs +2 -2
  4. package/dist/core.umd.cjs.map +1 -1
  5. package/package.json +17 -13
  6. package/src/core/types.ts +41 -0
  7. package/src/helpers.ts +11 -2
  8. package/src/index.ts +12 -9
  9. package/src/models/aggregators/aggregated-async-iterator.ts +920 -21
  10. package/src/models/aggregators/aggregated-iterator.ts +838 -22
  11. package/src/models/aggregators/reduced-iterator.ts +827 -11
  12. package/src/models/aggregators/types.ts +153 -10
  13. package/src/models/callbacks/callable-object.ts +42 -6
  14. package/src/models/callbacks/index.ts +2 -2
  15. package/src/models/callbacks/publisher.ts +160 -4
  16. package/src/models/callbacks/switchable-callback.ts +230 -23
  17. package/src/models/callbacks/types.ts +16 -0
  18. package/src/models/exceptions/core.ts +132 -3
  19. package/src/models/exceptions/index.ts +405 -13
  20. package/src/models/index.ts +4 -8
  21. package/src/models/iterators/smart-async-iterator.ts +827 -22
  22. package/src/models/iterators/smart-iterator.ts +755 -20
  23. package/src/models/iterators/types.ts +268 -9
  24. package/src/models/json/json-storage.ts +508 -110
  25. package/src/models/json/types.ts +10 -1
  26. package/src/models/promises/deferred-promise.ts +85 -5
  27. package/src/models/promises/index.ts +1 -3
  28. package/src/models/promises/smart-promise.ts +272 -4
  29. package/src/models/promises/timed-promise.ts +43 -1
  30. package/src/models/promises/types.ts +84 -2
  31. package/src/models/timers/clock.ts +109 -19
  32. package/src/models/timers/countdown.ts +176 -21
  33. package/src/models/timers/game-loop.ts +266 -0
  34. package/src/models/timers/index.ts +2 -1
  35. package/src/models/types.ts +6 -5
  36. package/src/utils/async.ts +43 -0
  37. package/src/utils/curve.ts +85 -0
  38. package/src/utils/date.ts +204 -10
  39. package/src/utils/dom.ts +16 -2
  40. package/src/utils/index.ts +3 -2
  41. package/src/utils/iterator.ts +200 -17
  42. package/src/utils/math.ts +55 -3
  43. package/src/utils/random.ts +139 -2
  44. package/src/utils/string.ts +11 -0
  45. package/src/models/game-loop.ts +0 -83
  46. package/src/models/promises/long-running-task.ts +0 -294
  47. package/src/models/promises/thenable.ts +0 -97
@@ -2,21 +2,161 @@ import { SmartIterator } from "../iterators/index.js";
2
2
  import type { GeneratorFunction, IteratorLike } from "../iterators/types.js";
3
3
 
4
4
  import ReducedIterator from "./reduced-iterator.js";
5
- import type { KeyedIteratee, KeyedTypeGuardIteratee, KeyedReducer } from "./types.js";
6
-
5
+ import type { KeyedIteratee, KeyedTypeGuardPredicate, KeyedReducer } from "./types.js";
6
+
7
+ /**
8
+ * A class representing an iterator that aggregates elements in a lazy and optimized way.
9
+ *
10
+ * It's part of the {@link SmartIterator} implementation, providing a way to group elements of an iterable by key.
11
+ * For this reason, it isn't recommended to instantiate this class directly
12
+ * (although it's still possible), but rather use the {@link SmartIterator.groupBy} method.
13
+ *
14
+ * It isn't directly iterable like its parent class but rather needs to specify on what you want to iterate.
15
+ * See the {@link AggregatedIterator.keys}, {@link AggregatedIterator.entries}
16
+ * & {@link AggregatedIterator.values} methods.
17
+ * It does, however, provide the same set of methods to perform
18
+ * operations and transformation on the elements of the iterator,
19
+ * having also the knowledge and context of the groups to which
20
+ * they belong, allowing to handle them in a grouped manner.
21
+ *
22
+ * This is particularly useful when you need to group elements and
23
+ * then perform specific operations on the groups themselves.
24
+ *
25
+ * ```ts
26
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
27
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
28
+ * .count();
29
+ *
30
+ * console.log(results.toObject()); // { odd: 4, even: 4 }
31
+ * ```
32
+ *
33
+ * @template K The type of the keys used to group the elements.
34
+ * @template T The type of the elements to aggregate.
35
+ */
7
36
  export default class AggregatedIterator<K extends PropertyKey, T>
8
37
  {
38
+ /**
39
+ * The internal {@link SmartIterator} object that holds the elements to aggregate.
40
+ */
9
41
  protected _elements: SmartIterator<[K, T]>;
10
42
 
43
+ /**
44
+ * Initializes a new instance of the {@link AggregatedIterator} class.
45
+ *
46
+ * ---
47
+ *
48
+ * @example
49
+ * ```ts
50
+ * const iterator = new AggregatedIterator<string, number>([["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]);
51
+ * ```
52
+ *
53
+ * ---
54
+ *
55
+ * @param iterable The iterable to aggregate.
56
+ */
11
57
  public constructor(iterable: Iterable<[K, T]>);
58
+
59
+ /**
60
+ * Initializes a new instance of the {@link AggregatedIterator} class.
61
+ *
62
+ * ---
63
+ *
64
+ * @example
65
+ * ```ts
66
+ * import { Random } from "@byloth/core";
67
+ *
68
+ * const iterator = new AggregatedIterator<string, number>({
69
+ * _index: 0,
70
+ * next: () =>
71
+ * {
72
+ * if (this._index >= 5) { return { done: true, value: undefined }; }
73
+ * this._index += 1;
74
+ *
75
+ * return { done: false, value: [Random.Choice(["A", "B", "C"]), (this._index + 1)] };
76
+ * }
77
+ * });
78
+ * ```
79
+ *
80
+ * ---
81
+ *
82
+ * @param iterator The iterator to aggregate.
83
+ */
12
84
  public constructor(iterator: Iterator<[K, T]>);
85
+
86
+ /**
87
+ * Initializes a new instance of the {@link AggregatedIterator} class.
88
+ *
89
+ * ---
90
+ *
91
+ * @example
92
+ * ```ts
93
+ * import { range, Random } from "@byloth/core";
94
+ *
95
+ * const iterator = new AggregatedIterator<string, number>(function* ()
96
+ * {
97
+ * for (const index of range(5))
98
+ * {
99
+ * yield [Random.Choice(["A", "B", "C"]), (index + 1)];
100
+ * }
101
+ * });
102
+ * ```
103
+ *
104
+ * ---
105
+ *
106
+ * @param generatorFn The generator function to aggregate.
107
+ */
13
108
  public constructor(generatorFn: GeneratorFunction<[K, T]>);
109
+
110
+ /**
111
+ * Initializes a new instance of the {@link AggregatedIterator} class.
112
+ *
113
+ * ---
114
+ *
115
+ * @example
116
+ * ```ts
117
+ * const iterator = new AggregatedIterator(keyedValues);
118
+ * ```
119
+ *
120
+ * ---
121
+ *
122
+ * @param argument The iterable, iterator or generator function to aggregate.
123
+ */
14
124
  public constructor(argument: IteratorLike<[K, T]> | GeneratorFunction<[K, T]>);
15
125
  public constructor(argument: IteratorLike<[K, T]> | GeneratorFunction<[K, T]>)
16
126
  {
17
127
  this._elements = new SmartIterator(argument);
18
128
  }
19
129
 
130
+ /**
131
+ * Determines whether all elements of each group of the iterator satisfy a given condition.
132
+ * See also {@link AggregatedIterator.some}.
133
+ * This method will consume the entire iterator in the process.
134
+ *
135
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
136
+ * Once a single element of one group doesn't satisfy the condition,
137
+ * the result for the respective group will be `false`.
138
+ *
139
+ * Eventually, it will return a new {@link ReducedIterator}
140
+ * object that will contain all the boolean results for each group.
141
+ * If the iterator is infinite, the method will never return.
142
+ *
143
+ * ---
144
+ *
145
+ * @example
146
+ * ```ts
147
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
148
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
149
+ * .every((key, value) => value >= 0);
150
+ *
151
+ * console.log(results.toObject()); // { odd: false, even: true }
152
+ * ```
153
+ *
154
+ * ---
155
+ *
156
+ * @param predicate The condition to check for each element of the iterator.
157
+ *
158
+ * @returns A new {@link ReducedIterator} containing the boolean results for each group.
159
+ */
20
160
  public every(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, boolean>
21
161
  {
22
162
  const values = new Map<K, [number, boolean]>();
@@ -35,6 +175,37 @@ export default class AggregatedIterator<K extends PropertyKey, T>
35
175
  for (const [key, [_, result]] of values) { yield [key, result]; }
36
176
  });
37
177
  }
178
+
179
+ /**
180
+ * Determines whether any elements of each group of the iterator satisfy a given condition.
181
+ * See also {@link AggregatedIterator.every}.
182
+ * This method will consume the entire iterator in the process.
183
+ *
184
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
185
+ * Once a single element of one group satisfies the condition,
186
+ * the result for the respective group will be `true`.
187
+ *
188
+ * Eventually, it will return a new {@link ReducedIterator}
189
+ * object that will contain all the boolean results for each group.
190
+ * If the iterator is infinite, the method will never return.
191
+ *
192
+ * ---
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * const results = new SmartIterator<number>([-5, -4, -3, -2, -1, 0])
197
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
198
+ * .some((key, value) => value >= 0);
199
+ *
200
+ * console.log(results.toObject()); // { odd: false, even: true }
201
+ * ```
202
+ *
203
+ * ---
204
+ *
205
+ * @param predicate The condition to check for each element of the iterator.
206
+ *
207
+ * @returns A {@link ReducedIterator} containing the boolean results for each group.
208
+ */
38
209
  public some(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, boolean>
39
210
  {
40
211
  const values = new Map<K, [number, boolean]>();
@@ -54,8 +225,75 @@ export default class AggregatedIterator<K extends PropertyKey, T>
54
225
  });
55
226
  }
56
227
 
228
+ /**
229
+ * Filters the elements of the iterator using a given condition.
230
+ *
231
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
232
+ * If the condition is met, the element will be included in the new iterator.
233
+ *
234
+ * Since the iterator is lazy, the filtering process will
235
+ * be executed once the resulting iterator is materialized.
236
+ *
237
+ * A new iterator will be created, holding the reference to the original one.
238
+ * This means that the original iterator won't be consumed until the
239
+ * new one is and that consuming one of them will consume the other as well.
240
+ *
241
+ * ---
242
+ *
243
+ * @example
244
+ * ```ts
245
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
246
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
247
+ * .filter((key, value) => value >= 0);
248
+ *
249
+ * console.log(results.toObject()); // { odd: [3, 5], even: [0, 2, 6, 8] }
250
+ * ```
251
+ *
252
+ * ---
253
+ *
254
+ * @param predicate The condition to check for each element of the iterator.
255
+ *
256
+ * @returns A new {@link AggregatedIterator} containing only the elements that satisfy the condition.
257
+ */
57
258
  public filter(predicate: KeyedIteratee<K, T, boolean>): AggregatedIterator<K, T>;
58
- public filter<S extends T>(predicate: KeyedTypeGuardIteratee<K, T, S>): AggregatedIterator<K, S>;
259
+
260
+ /**
261
+ * Filters the elements of the iterator using a given condition.
262
+ *
263
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
264
+ * If the condition is met, the element will be included in the new iterator.
265
+ *
266
+ * Since the iterator is lazy, the filtering process will
267
+ * be executed once the resulting iterator is materialized.
268
+ *
269
+ * A new iterator will be created, holding the reference to the original one.
270
+ * This means that the original iterator won't be consumed until the
271
+ * new one is and that consuming one of them will consume the other as well.
272
+ *
273
+ * ---
274
+ *
275
+ * @example
276
+ * ```ts
277
+ * const results = new SmartIterator<number | string>([-3, "-1", 0, "2", "3", 5, 6, "8"])
278
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
279
+ * .filter<number>((key, value) => typeof value === "number");
280
+ *
281
+ * console.log(results.toObject()); // { odd: [-3, 5], even: [0, 6] }
282
+ * ```
283
+ *
284
+ * ---
285
+ *
286
+ * @template S
287
+ * The type of the elements that satisfy the condition.
288
+ * This allows the type-system to infer the correct type of the new iterator.
289
+ *
290
+ * It must be a subtype of the original type of the elements.
291
+ *
292
+ * @param predicate The type guard condition to check for each element of the iterator.
293
+ *
294
+ * @returns A new {@link AggregatedIterator} containing only the elements that satisfy the condition.
295
+ */
296
+ public filter<S extends T>(predicate: KeyedTypeGuardPredicate<K, T, S>): AggregatedIterator<K, S>;
59
297
  public filter(predicate: KeyedIteratee<K, T, boolean>): AggregatedIterator<K, T>
60
298
  {
61
299
  const elements = this._elements;
@@ -63,17 +301,48 @@ export default class AggregatedIterator<K extends PropertyKey, T>
63
301
  return new AggregatedIterator(function* ()
64
302
  {
65
303
  const indexes = new Map<K, number>();
66
-
67
304
  for (const [key, element] of elements)
68
305
  {
69
306
  const index = indexes.get(key) ?? 0;
70
-
71
307
  if (predicate(key, element, index)) { yield [key, element]; }
72
308
 
73
309
  indexes.set(key, index + 1);
74
310
  }
75
311
  });
76
312
  }
313
+
314
+ /**
315
+ * Maps the elements of the iterator using a given transformation function.
316
+ *
317
+ * This method will iterate over all elements of the iterator applying the transformation function.
318
+ * The result of each transformation will be included in the new iterator.
319
+ *
320
+ * Since the iterator is lazy, the mapping process will
321
+ * be executed once the resulting iterator is materialized.
322
+ *
323
+ * A new iterator will be created, holding the reference to the original one.
324
+ * This means that the original iterator won't be consumed until the
325
+ * new one is and that consuming one of them will consume the other as well.
326
+ *
327
+ * ---
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
332
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
333
+ * .map((key, value) => Math.abs(value));
334
+ *
335
+ * console.log(results.toObject()); // { odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }
336
+ * ```
337
+ *
338
+ * ---
339
+ *
340
+ * @template V The type of the elements after the transformation.
341
+ *
342
+ * @param iteratee The transformation function to apply to each element of the iterator.
343
+ *
344
+ * @returns A new {@link AggregatedIterator} containing the transformed elements.
345
+ */
77
346
  public map<V>(iteratee: KeyedIteratee<K, T, V>): AggregatedIterator<K, V>
78
347
  {
79
348
  const elements = this._elements;
@@ -81,20 +350,121 @@ export default class AggregatedIterator<K extends PropertyKey, T>
81
350
  return new AggregatedIterator(function* ()
82
351
  {
83
352
  const indexes = new Map<K, number>();
84
-
85
353
  for (const [key, element] of elements)
86
354
  {
87
355
  const index = indexes.get(key) ?? 0;
88
-
89
356
  yield [key, iteratee(key, element, index)];
90
357
 
91
358
  indexes.set(key, index + 1);
92
359
  }
93
360
  });
94
361
  }
362
+
363
+ /**
364
+ * Reduces the elements of the iterator using a given reducer function.
365
+ * This method will consume the entire iterator in the process.
366
+ *
367
+ * It will iterate over all elements of the iterator applying the reducer function.
368
+ * The result of each iteration will be passed as the accumulator to the next one.
369
+ *
370
+ * The first accumulator value will be the first element of the iterator.
371
+ * The last accumulator value will be the final result of the reduction.
372
+ *
373
+ * Eventually, it will return a new {@link ReducedIterator}
374
+ * object that will contain all the reduced results for each group.
375
+ * If the iterator is infinite, the method will never return.
376
+ *
377
+ * ---
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
382
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
383
+ * .reduce((key, accumulator, value) => accumulator + value);
384
+ *
385
+ * console.log(results.toObject()); // { odd: 4, even: 16 }
386
+ * ```
387
+ *
388
+ * ---
389
+ *
390
+ * @param reducer The reducer function to apply to each element of the iterator.
391
+ *
392
+ * @returns A new {@link ReducedIterator} containing the reduced results for each group.
393
+ */
95
394
  public reduce(reducer: KeyedReducer<K, T, T>): ReducedIterator<K, T>;
395
+
396
+ /**
397
+ * Reduces the elements of the iterator using a given reducer function.
398
+ * This method will consume the entire iterator in the process.
399
+ *
400
+ * It will iterate over all elements of the iterator applying the reducer function.
401
+ * The result of each iteration will be passed as the accumulator to the next one.
402
+ *
403
+ * The first accumulator value will be the provided initial value.
404
+ * The last accumulator value will be the final result of the reduction.
405
+ *
406
+ * Eventually, it will return a new {@link ReducedIterator}
407
+ * object that will contain all the reduced results for each group.
408
+ * If the iterator is infinite, the method will never return.
409
+ *
410
+ * ---
411
+ *
412
+ * @example
413
+ * ```ts
414
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
415
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
416
+ * .reduce((key, accumulator, value) => accumulator + value, 0);
417
+ *
418
+ * console.log(results.toObject()); // { odd: 4, even: 16 }
419
+ * ```
420
+ *
421
+ * ---
422
+ *
423
+ * @template A The type of the accumulator value which will also be the type of the final result of the reduction.
424
+ *
425
+ * @param reducer The reducer function to apply to each element of the iterator.
426
+ * @param initialValue The initial value of the accumulator.
427
+ *
428
+ * @returns A new {@link ReducedIterator} containing the reduced results for each group.
429
+ */
430
+ public reduce<A extends PropertyKey>(reducer: KeyedReducer<K, T, A>, initialValue: A): ReducedIterator<K, A>;
431
+
432
+ /**
433
+ * Reduces the elements of the iterator using a given reducer function.
434
+ * This method will consume the entire iterator in the process.
435
+ *
436
+ * It will iterate over all elements of the iterator applying the reducer function.
437
+ * The result of each iteration will be passed as the accumulator to the next one.
438
+ *
439
+ * The first accumulator value will be the provided initial value by the given function.
440
+ * The last accumulator value will be the final result of the reduction.
441
+ *
442
+ * Eventually, it will return a new {@link ReducedIterator}
443
+ * object that will contain all the reduced results for each group.
444
+ * If the iterator is infinite, the method will never return.
445
+ *
446
+ * ---
447
+ *
448
+ * @example
449
+ * ```ts
450
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
451
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
452
+ * .reduce((key, { value }, currentValue) => ({ value: value + currentValue }), (key) => ({ value: 0 }));
453
+ *
454
+ * console.log(results.toObject()); // { odd: { value: 4 }, even: { value: 16 } }
455
+ * ```
456
+ *
457
+ * ---
458
+ *
459
+ * @template A The type of the accumulator value which will also be the type of the final result of the reduction.
460
+ *
461
+ * @param reducer The reducer function to apply to each element of the iterator.
462
+ * @param initialValue The function that provides the initial value for the accumulator.
463
+ *
464
+ * @returns A new {@link ReducedIterator} containing the reduced results for each group.
465
+ */
96
466
  public reduce<A>(reducer: KeyedReducer<K, T, A>, initialValue: (key: K) => A): ReducedIterator<K, A>;
97
- public reduce<A>(reducer: KeyedReducer<K, T, A>, initialValue?: (key: K) => A): ReducedIterator<K, A>
467
+ public reduce<A>(reducer: KeyedReducer<K, T, A>, initialValue?: A | ((key: K) => A)): ReducedIterator<K, A>
98
468
  {
99
469
  const values = new Map<K, [number, A]>();
100
470
 
@@ -107,7 +477,9 @@ export default class AggregatedIterator<K extends PropertyKey, T>
107
477
  else if (initialValue !== undefined)
108
478
  {
109
479
  index = 0;
110
- accumulator = initialValue(key);
480
+
481
+ if (initialValue instanceof Function) { accumulator = initialValue(key); }
482
+ else { accumulator = initialValue; }
111
483
  }
112
484
  else
113
485
  {
@@ -125,26 +497,94 @@ export default class AggregatedIterator<K extends PropertyKey, T>
125
497
  });
126
498
  }
127
499
 
128
- public flatMap<V>(iteratee: KeyedIteratee<K, T, Iterable<V>>): AggregatedIterator<K, V>
500
+ /**
501
+ * Flattens the elements of the iterator using a given transformation function.
502
+ *
503
+ * This method will iterate over all elements of the iterator applying the transformation function.
504
+ * The result of each transformation will be included in the new iterator.
505
+ *
506
+ * Since the iterator is lazy, the flattening process will
507
+ * be executed once the resulting iterator is materialized.
508
+ *
509
+ * A new iterator will be created, holding the reference to the original one.
510
+ * This means that the original iterator won't be consumed until the
511
+ * new one is and that consuming one of them will consume the other as well.
512
+ *
513
+ * ---
514
+ *
515
+ * @example
516
+ * ```ts
517
+ * const results = new SmartIterator<number[]>([[-3, -1], 0, 2, 3, 5, [6, 8]])
518
+ * .groupBy((values) =>
519
+ * {
520
+ * const value = values instanceof Array ? values[0] : values;
521
+ * return value % 2 === 0 ? "even" : "odd";
522
+ * })
523
+ * .flatMap((key, values) => values);
524
+ *
525
+ * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
526
+ * ```
527
+ *
528
+ * ---
529
+ *
530
+ * @template V The type of the elements after the transformation.
531
+ *
532
+ * @param iteratee The transformation function to apply to each element of the iterator.
533
+ *
534
+ * @returns A new {@link AggregatedIterator} containing the transformed elements.
535
+ */
536
+ public flatMap<V>(iteratee: KeyedIteratee<K, T, V | readonly V[]>): AggregatedIterator<K, V>
129
537
  {
130
538
  const elements = this._elements;
131
539
 
132
540
  return new AggregatedIterator(function* ()
133
541
  {
134
542
  const indexes = new Map<K, number>();
135
-
136
543
  for (const [key, element] of elements)
137
544
  {
138
545
  const index = indexes.get(key) ?? 0;
139
546
  const values = iteratee(key, element, index);
140
547
 
141
- for (const value of values) { yield [key, value]; }
548
+ if (values instanceof Array)
549
+ {
550
+ for (const value of values) { yield [key, value]; }
551
+ }
552
+ else { yield [key, values]; }
142
553
 
143
554
  indexes.set(key, index + 1);
144
555
  }
145
556
  });
146
557
  }
147
558
 
559
+ /**
560
+ * Drops a given number of elements from the beginning of each group of the iterator.
561
+ * The remaining elements will be included in the new iterator.
562
+ * See also {@link AggregatedIterator.take}.
563
+ *
564
+ * Since the iterator is lazy, the dropping process will
565
+ * be executed once the resulting iterator is materialized.
566
+ *
567
+ * A new iterator will be created, holding the reference to the original one.
568
+ * This means that the original iterator won't be consumed until the
569
+ * new one is and that consuming one of them will consume the other as well.
570
+ *
571
+ * ---
572
+ *
573
+ * @example
574
+ * ```ts
575
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
576
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
577
+ * .drop(2);
578
+ *
579
+ * console.log(results.toObject()); // { odd: [3, 5], even: [6, 8] }
580
+ * ```
581
+ *
582
+ * ---
583
+ *
584
+ * @param count The number of elements to drop from the beginning of each group.
585
+ *
586
+ * @returns A new {@link AggregatedIterator} containing the remaining elements.
587
+ */
148
588
  public drop(count: number): AggregatedIterator<K, T>
149
589
  {
150
590
  const elements = this._elements;
@@ -152,7 +592,6 @@ export default class AggregatedIterator<K extends PropertyKey, T>
152
592
  return new AggregatedIterator(function* ()
153
593
  {
154
594
  const indexes = new Map<K, number>();
155
-
156
595
  for (const [key, element] of elements)
157
596
  {
158
597
  const index = indexes.get(key) ?? 0;
@@ -167,6 +606,36 @@ export default class AggregatedIterator<K extends PropertyKey, T>
167
606
  }
168
607
  });
169
608
  }
609
+
610
+ /**
611
+ * Takes a given number of elements from the beginning of each group of the iterator.
612
+ * The elements will be included in the new iterator.
613
+ * See also {@link AggregatedIterator.drop}.
614
+ *
615
+ * Since the iterator is lazy, the taking process will
616
+ * be executed once the resulting iterator is materialized.
617
+ *
618
+ * A new iterator will be created, holding the reference to the original one.
619
+ * This means that the original iterator won't be consumed until the
620
+ * new one is and that consuming one of them will consume the other as well.
621
+ *
622
+ * ---
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
627
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
628
+ * .take(2);
629
+ *
630
+ * console.log(results.toObject()); // { odd: [-3, -1], even: [0, 2] }
631
+ * ```
632
+ *
633
+ * ---
634
+ *
635
+ * @param limit The number of elements to take from the beginning of each group.
636
+ *
637
+ * @returns A new {@link AggregatedIterator} containing the taken elements.
638
+ */
170
639
  public take(limit: number): AggregatedIterator<K, T>
171
640
  {
172
641
  const elements = this._elements;
@@ -174,12 +643,10 @@ export default class AggregatedIterator<K extends PropertyKey, T>
174
643
  return new AggregatedIterator(function* ()
175
644
  {
176
645
  const indexes = new Map<K, number>();
177
-
178
646
  for (const [key, element] of elements)
179
647
  {
180
648
  const index = indexes.get(key) ?? 0;
181
649
  if (index >= limit) { continue; }
182
-
183
650
  yield [key, element];
184
651
 
185
652
  indexes.set(key, index + 1);
@@ -187,8 +654,73 @@ export default class AggregatedIterator<K extends PropertyKey, T>
187
654
  });
188
655
  }
189
656
 
657
+ /**
658
+ * Finds the first element of each group of the iterator that satisfies a given condition.
659
+ * This method will consume the entire iterator in the process.
660
+ *
661
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
662
+ * Once the first element of one group satisfies the condition,
663
+ * the result for the respective group will be the element itself.
664
+ *
665
+ * Eventually, it will return a new {@link ReducedIterator}
666
+ * object that will contain the first element that satisfies the condition for each group.
667
+ * If the iterator is infinite, the method will never return.
668
+ *
669
+ * ---
670
+ *
671
+ * @example
672
+ * ```ts
673
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
674
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
675
+ * .find((key, value) => value > 0);
676
+ *
677
+ * console.log(results.toObject()); // { odd: 3, even: 2 }
678
+ * ```
679
+ *
680
+ * ---
681
+ *
682
+ * @param predicate The condition to check for each element of the iterator.
683
+ *
684
+ * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group.
685
+ */
190
686
  public find(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, T | undefined>;
191
- public find<S extends T>(predicate: KeyedTypeGuardIteratee<K, T, S>): ReducedIterator<K, S | undefined>;
687
+
688
+ /**
689
+ * Finds the first element of each group of the iterator that satisfies a given condition.
690
+ * This method will consume the entire iterator in the process.
691
+ *
692
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
693
+ * Once the first element of one group satisfies the condition,
694
+ * the result for the respective group will be the element itself.
695
+ *
696
+ * Eventually, it will return a new {@link ReducedIterator}
697
+ * object that will contain the first element that satisfies the condition for each group.
698
+ * If the iterator is infinite, the method will never return.
699
+ *
700
+ * ---
701
+ *
702
+ * @example
703
+ * ```ts
704
+ * const results = new SmartIterator<number | string>([-3, "-1", 0, "2", "3", 5, 6, "8"])
705
+ * .groupBy((value) => Number(value) % 2 === 0 ? "even" : "odd")
706
+ * .find<number>((key, value) => typeof value === "number");
707
+ *
708
+ * console.log(results.toObject()); // { odd: -3, even: 0 }
709
+ * ```
710
+ *
711
+ * ---
712
+ *
713
+ * @template S
714
+ * The type of the elements that satisfy the condition.
715
+ * This allows the type-system to infer the correct type of the new iterator.
716
+ *
717
+ * It must be a subtype of the original type of the elements.
718
+ *
719
+ * @param predicate The type guard condition to check for each element of the iterator.
720
+ *
721
+ * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group.
722
+ */
723
+ public find<S extends T>(predicate: KeyedTypeGuardPredicate<K, T, S>): ReducedIterator<K, S | undefined>;
192
724
  public find(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, T | undefined>
193
725
  {
194
726
  const values = new Map<K, [number, T | undefined]>();
@@ -209,10 +741,63 @@ export default class AggregatedIterator<K extends PropertyKey, T>
209
741
  });
210
742
  }
211
743
 
744
+ /**
745
+ * Enumerates the elements of the iterator.
746
+ * Each element is paired with its index within the group in a new iterator.
747
+ *
748
+ * Since the iterator is lazy, the enumeration process will
749
+ * be executed once the resulting iterator is materialized.
750
+ *
751
+ * A new iterator will be created, holding the reference to the original one.
752
+ * This means that the original iterator won't be consumed until the
753
+ * new one is and that consuming one of them will consume the other as well.
754
+ *
755
+ * ---
756
+ *
757
+ * @example
758
+ * ```ts
759
+ * const results = new SmartIterator<number>([-3, 0, 2, -1, 3])
760
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
761
+ * .enumerate();
762
+ *
763
+ * console.log(results.toObject()); // { odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }
764
+ * ```
765
+ *
766
+ * ---
767
+ *
768
+ * @returns A new {@link AggregatedIterator} containing the enumerated elements.
769
+ */
212
770
  public enumerate(): AggregatedIterator<K, [number, T]>
213
771
  {
214
772
  return this.map((_, value, index) => [index, value]);
215
773
  }
774
+
775
+ /**
776
+ * Removes all duplicate elements from within each group of the iterator.
777
+ * The first occurrence of each element will be included in the new iterator.
778
+ *
779
+ * Since the iterator is lazy, the deduplication process will
780
+ * be executed once the resulting iterator is materialized.
781
+ *
782
+ * A new iterator will be created, holding the reference to the original one.
783
+ * This means that the original iterator won't be consumed until the
784
+ * new one is and that consuming one of them will consume the other as well.
785
+ *
786
+ * ---
787
+ *
788
+ * @example
789
+ * ```ts
790
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2])
791
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
792
+ * .unique();
793
+ *
794
+ * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
795
+ * ```
796
+ *
797
+ * ---
798
+ *
799
+ * @returns A new {@link AggregatedIterator} containing only the unique elements.
800
+ */
216
801
  public unique(): AggregatedIterator<K, T>
217
802
  {
218
803
  const elements = this._elements;
@@ -220,11 +805,9 @@ export default class AggregatedIterator<K extends PropertyKey, T>
220
805
  return new AggregatedIterator(function* ()
221
806
  {
222
807
  const keys = new Map<K, Set<T>>();
223
-
224
808
  for (const [key, element] of elements)
225
809
  {
226
810
  const values = keys.get(key) ?? new Set<T>();
227
-
228
811
  if (values.has(element)) { continue; }
229
812
 
230
813
  values.add(element);
@@ -235,6 +818,27 @@ export default class AggregatedIterator<K extends PropertyKey, T>
235
818
  });
236
819
  }
237
820
 
821
+ /**
822
+ * Counts the number of elements within each group of the iterator.
823
+ * This method will consume the entire iterator in the process.
824
+ *
825
+ * If the iterator is infinite, the method will never return.
826
+ *
827
+ * ---
828
+ *
829
+ * @example
830
+ * ```ts
831
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
832
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
833
+ * .count();
834
+ *
835
+ * console.log(results.toObject()); // { odd: 4, even: 4 }
836
+ * ```
837
+ *
838
+ * ---
839
+ *
840
+ * @returns A new {@link ReducedIterator} containing the number of elements for each group.
841
+ */
238
842
  public count(): ReducedIterator<K, number>
239
843
  {
240
844
  const counters = new Map<K, number>();
@@ -252,20 +856,116 @@ export default class AggregatedIterator<K extends PropertyKey, T>
252
856
  });
253
857
  }
254
858
 
859
+ /**
860
+ * Iterates over the elements of the iterator.
861
+ * The elements are passed to the given iteratee function along with their key and index within the group.
862
+ *
863
+ * This method will consume the entire iterator in the process.
864
+ * If the iterator is infinite, the method will never return.
865
+ *
866
+ * ---
867
+ *
868
+ * @example
869
+ * ```ts
870
+ * const aggregator = new SmartIterator<number>([-3, 0, 2, -1, 3])
871
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
872
+ *
873
+ * aggregator.forEach((key, value, index) =>
874
+ * {
875
+ * console.log(`${index}: ${value}`); // "0: -3", "0: 0", "1: 2", "1: -1", "2: 3"
876
+ * };
877
+ * ```
878
+ *
879
+ * ---
880
+ *
881
+ * @param iteratee The function to execute for each element of the iterator.
882
+ */
255
883
  public forEach(iteratee: KeyedIteratee<K, T>): void
256
884
  {
257
885
  const indexes = new Map<K, number>();
258
-
259
886
  for (const [key, element] of this._elements)
260
887
  {
261
888
  const index = indexes.get(key) ?? 0;
262
-
263
889
  iteratee(key, element, index);
264
890
 
265
891
  indexes.set(key, index + 1);
266
892
  }
267
893
  }
268
894
 
895
+ /**
896
+ * Changes the key of each element on which the iterator is aggregated.
897
+ * The new key is determined by the given iteratee function.
898
+ *
899
+ * Since the iterator is lazy, the reorganization process will
900
+ * be executed once the resulting iterator is materialized.
901
+ *
902
+ * A new iterator will be created, holding the reference to the original one.
903
+ * This means that the original iterator won't be consumed until the
904
+ * new one is and that consuming one of them will consume the other as well.
905
+ *
906
+ * ---
907
+ *
908
+ * @example
909
+ * ```ts
910
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
911
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
912
+ * .map((key, value, index) => index % 2 === 0 ? value : -value)
913
+ * .reorganizeBy((key, value) => value >= 0 ? "+" : "-");
914
+ *
915
+ * console.log(results.toObject()); // { "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }
916
+ * ```
917
+ *
918
+ * ---
919
+ *
920
+ * @template J The type of the new key.
921
+ *
922
+ * @param iteratee The function to determine the new key for each element of the iterator.
923
+ *
924
+ * @returns A new {@link AggregatedIterator} containing the elements reorganized by the new keys.
925
+ */
926
+ public reorganizeBy<J extends PropertyKey>(iteratee: KeyedIteratee<K, T, J>): AggregatedIterator<J, T>
927
+ {
928
+ const elements = this._elements;
929
+
930
+ return new AggregatedIterator(function* ()
931
+ {
932
+ const indexes = new Map<K, number>();
933
+ for (const [key, element] of elements)
934
+ {
935
+ const index = indexes.get(key) ?? 0;
936
+ yield [iteratee(key, element, index), element];
937
+
938
+ indexes.set(key, index + 1);
939
+ }
940
+ });
941
+ }
942
+
943
+ /**
944
+ * An utility method that returns a new {@link SmartIterator}
945
+ * object containing all the keys of the iterator.
946
+ *
947
+ * Since the iterator is lazy, the keys will be extracted
948
+ * be executed once the resulting iterator is materialized.
949
+ *
950
+ * A new iterator will be created, holding the reference to the original one.
951
+ * This means that the original iterator won't be consumed until the
952
+ * new one is and that consuming one of them will consume the other as well.
953
+ *
954
+ * ---
955
+ *
956
+ * @example
957
+ * ```ts
958
+ * const keys = new SmartIterator([-3, Symbol(), "A", { }, null, [1 , 2, 3], false])
959
+ * .groupBy((value) => typeof value)
960
+ * .keys();
961
+ *
962
+ * console.log(keys.toArray()); // ["number", "symbol", "string", "object", "boolean"]
963
+ * ```
964
+ *
965
+ * ---
966
+ *
967
+ * @returns A new {@link SmartIterator} containing all the keys of the iterator.
968
+ */
269
969
  public keys(): SmartIterator<K>
270
970
  {
271
971
  const elements = this._elements;
@@ -273,7 +973,6 @@ export default class AggregatedIterator<K extends PropertyKey, T>
273
973
  return new SmartIterator<K>(function* ()
274
974
  {
275
975
  const keys = new Set<K>();
276
-
277
976
  for (const [key] of elements)
278
977
  {
279
978
  if (keys.has(key)) { continue; }
@@ -283,10 +982,65 @@ export default class AggregatedIterator<K extends PropertyKey, T>
283
982
  }
284
983
  });
285
984
  }
286
- public items(): SmartIterator<[K, T]>
985
+
986
+ /**
987
+ * An utility method that returns a new {@link SmartIterator}
988
+ * object containing all the entries of the iterator.
989
+ * Each entry is a tuple containing the key and the element.
990
+ *
991
+ * Since the iterator is lazy, the entries will be extracted
992
+ * be executed once the resulting iterator is materialized.
993
+ *
994
+ * A new iterator will be created, holding the reference to the original one.
995
+ * This means that the original iterator won't be consumed until the
996
+ * new one is and that consuming one of them will consume the other as well.
997
+ *
998
+ * ---
999
+ *
1000
+ * @example
1001
+ * ```ts
1002
+ * const entries = new SmartIterator<number>([-3, 0, 2, -1, 3])
1003
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
1004
+ * .entries();
1005
+ *
1006
+ * console.log(entries.toArray()); // [["odd", -3], ["even", 0], ["even", 2], ["odd", -1], ["odd", 3]]
1007
+ * ```
1008
+ *
1009
+ * ---
1010
+ *
1011
+ * @returns A new {@link SmartIterator} containing all the entries of the iterator.
1012
+ */
1013
+ public entries(): SmartIterator<[K, T]>
287
1014
  {
288
1015
  return this._elements;
289
1016
  }
1017
+
1018
+ /**
1019
+ * An utility method that returns a new {@link SmartIterator}
1020
+ * object containing all the values of the iterator.
1021
+ *
1022
+ * Since the iterator is lazy, the values will be extracted
1023
+ * be executed once the resulting iterator is materialized.
1024
+ *
1025
+ * A new iterator will be created, holding the reference to the original one.
1026
+ * This means that the original iterator won't be consumed until the
1027
+ * new one is and that consuming one of them will consume the other as well.
1028
+ *
1029
+ * ---
1030
+ *
1031
+ * @example
1032
+ * ```ts
1033
+ * const values = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
1034
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
1035
+ * .values();
1036
+ *
1037
+ * console.log(values.toArray()); // [-3, -1, 0, 2, 3, 5, 6, 8]
1038
+ * ```
1039
+ *
1040
+ * ---
1041
+ *
1042
+ * @returns A new {@link SmartIterator} containing all the values of the iterator.
1043
+ */
290
1044
  public values(): SmartIterator<T>
291
1045
  {
292
1046
  const elements = this._elements;
@@ -297,12 +1051,53 @@ export default class AggregatedIterator<K extends PropertyKey, T>
297
1051
  });
298
1052
  }
299
1053
 
1054
+ /**
1055
+ * Materializes the iterator into an array of arrays.
1056
+ * This method will consume the entire iterator in the process.
1057
+ *
1058
+ * If the iterator is infinite, the method will never return.
1059
+ *
1060
+ * ---
1061
+ *
1062
+ * @example
1063
+ * ```ts
1064
+ * const aggregator = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
1065
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
1066
+ *
1067
+ * console.log(aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]]
1068
+ * ```
1069
+ *
1070
+ * ---
1071
+ *
1072
+ * @returns An {@link Array} of arrays containing the elements of the iterator.
1073
+ */
300
1074
  public toArray(): T[][]
301
1075
  {
302
1076
  const map = this.toMap();
303
1077
 
304
1078
  return Array.from(map.values());
305
1079
  }
1080
+
1081
+ /**
1082
+ * Materializes the iterator into a map.
1083
+ * This method will consume the entire iterator in the process.
1084
+ *
1085
+ * If the iterator is infinite, the method will never return.
1086
+ *
1087
+ * ---
1088
+ *
1089
+ * @example
1090
+ * ```ts
1091
+ * const aggregator = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
1092
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
1093
+ *
1094
+ * console.log(aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] }
1095
+ * ```
1096
+ *
1097
+ * ---
1098
+ *
1099
+ * @returns A {@link Map} containing the elements of the iterator.
1100
+ */
306
1101
  public toMap(): Map<K, T[]>
307
1102
  {
308
1103
  const groups = new Map<K, T[]>();
@@ -317,6 +1112,27 @@ export default class AggregatedIterator<K extends PropertyKey, T>
317
1112
 
318
1113
  return groups;
319
1114
  }
1115
+
1116
+ /**
1117
+ * Materializes the iterator into an object.
1118
+ * This method will consume the entire iterator in the process.
1119
+ *
1120
+ * If the iterator is infinite, the method will never return.
1121
+ *
1122
+ * ---
1123
+ *
1124
+ * @example
1125
+ * ```ts
1126
+ * const aggregator = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
1127
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
1128
+ *
1129
+ * console.log(aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
1130
+ * ```
1131
+ *
1132
+ * ---
1133
+ *
1134
+ * @returns An {@link Object} containing the elements of the iterator.
1135
+ */
320
1136
  public toObject(): Record<K, T[]>
321
1137
  {
322
1138
  const groups = { } as Record<K, T[]>;