@byloth/core 2.0.0-rc.9 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/core.js +3371 -608
  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 +13 -10
  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 +765 -21
  10. package/src/models/aggregators/aggregated-iterator.ts +698 -22
  11. package/src/models/aggregators/reduced-iterator.ts +699 -10
  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 +139 -4
  16. package/src/models/callbacks/switchable-callback.ts +138 -4
  17. package/src/models/callbacks/types.ts +16 -0
  18. package/src/models/exceptions/core.ts +112 -3
  19. package/src/models/exceptions/index.ts +340 -13
  20. package/src/models/index.ts +4 -8
  21. package/src/models/iterators/smart-async-iterator.ts +687 -22
  22. package/src/models/iterators/smart-iterator.ts +631 -21
  23. package/src/models/iterators/types.ts +268 -9
  24. package/src/models/json/json-storage.ts +388 -110
  25. package/src/models/json/types.ts +10 -1
  26. package/src/models/promises/deferred-promise.ts +75 -5
  27. package/src/models/promises/index.ts +1 -3
  28. package/src/models/promises/smart-promise.ts +232 -4
  29. package/src/models/promises/timed-promise.ts +38 -1
  30. package/src/models/promises/types.ts +84 -2
  31. package/src/models/timers/clock.ts +91 -19
  32. package/src/models/timers/countdown.ts +152 -22
  33. package/src/models/timers/game-loop.ts +243 -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 +75 -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 +109 -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,136 @@ 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
+ * ```ts
47
+ * const iterator = new AggregatedIterator<string, number>([["A", 1], ["B", 2], ["A", 3], ["C", 4], ["B", 5]]);
48
+ * ```
49
+ *
50
+ * @param iterable The iterable to aggregate.
51
+ */
11
52
  public constructor(iterable: Iterable<[K, T]>);
53
+
54
+ /**
55
+ * Initializes a new instance of the {@link AggregatedIterator} class.
56
+ *
57
+ * ```ts
58
+ * import { Random } from "@byloth/core";
59
+ *
60
+ * const iterator = new AggregatedIterator<string, number>({
61
+ * _index: 0,
62
+ * next: () =>
63
+ * {
64
+ * if (this._index >= 5) { return { done: true, value: undefined }; }
65
+ * this._index += 1;
66
+ *
67
+ * return { done: false, value: [Random.Choice(["A", "B", "C"]), (this._index + 1)] };
68
+ * }
69
+ * });
70
+ * ```
71
+ *
72
+ * @param iterator The iterator to aggregate.
73
+ */
12
74
  public constructor(iterator: Iterator<[K, T]>);
75
+
76
+ /**
77
+ * Initializes a new instance of the {@link AggregatedIterator} class.
78
+ *
79
+ * ```ts
80
+ * import { range, Random } from "@byloth/core";
81
+ *
82
+ * const iterator = new AggregatedIterator<string, number>(function* ()
83
+ * {
84
+ * for (const index of range(5))
85
+ * {
86
+ * yield [Random.Choice(["A", "B", "C"]), (index + 1)];
87
+ * }
88
+ * });
89
+ * ```
90
+ *
91
+ * @param generatorFn The generator function to aggregate.
92
+ */
13
93
  public constructor(generatorFn: GeneratorFunction<[K, T]>);
94
+
95
+ /**
96
+ * Initializes a new instance of the {@link AggregatedIterator} class.
97
+ *
98
+ * ```ts
99
+ * const iterator = new AggregatedIterator(keyedValues);
100
+ * ```
101
+ *
102
+ * @param argument The iterable, iterator or generator function to aggregate.
103
+ */
14
104
  public constructor(argument: IteratorLike<[K, T]> | GeneratorFunction<[K, T]>);
15
105
  public constructor(argument: IteratorLike<[K, T]> | GeneratorFunction<[K, T]>)
16
106
  {
17
107
  this._elements = new SmartIterator(argument);
18
108
  }
19
109
 
110
+ /**
111
+ * Determines whether all elements of each group of the iterator satisfy a given condition.
112
+ * See also {@link AggregatedIterator.some}.
113
+ * This method will consume the entire iterator in the process.
114
+ *
115
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
116
+ * Once a single element of one group doesn't satisfy the condition,
117
+ * the result for the respective group will be `false`.
118
+ *
119
+ * Eventually, it will return a new {@link ReducedIterator}
120
+ * object that will contain all the boolean results for each group.
121
+ * If the iterator is infinite, the method will never return.
122
+ *
123
+ * ```ts
124
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
125
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
126
+ * .every((key, value) => value >= 0);
127
+ *
128
+ * console.log(results.toObject()); // { odd: false, even: true }
129
+ * ```
130
+ *
131
+ * @param predicate The condition to check for each element of the iterator.
132
+ *
133
+ * @returns A new {@link ReducedIterator} containing the boolean results for each group.
134
+ */
20
135
  public every(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, boolean>
21
136
  {
22
137
  const values = new Map<K, [number, boolean]>();
@@ -35,6 +150,32 @@ export default class AggregatedIterator<K extends PropertyKey, T>
35
150
  for (const [key, [_, result]] of values) { yield [key, result]; }
36
151
  });
37
152
  }
153
+
154
+ /**
155
+ * Determines whether any elements of each group of the iterator satisfy a given condition.
156
+ * See also {@link AggregatedIterator.every}.
157
+ * This method will consume the entire iterator in the process.
158
+ *
159
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
160
+ * Once a single element of one group satisfies the condition,
161
+ * the result for the respective group will be `true`.
162
+ *
163
+ * Eventually, it will return a new {@link ReducedIterator}
164
+ * object that will contain all the boolean results for each group.
165
+ * If the iterator is infinite, the method will never return.
166
+ *
167
+ * ```ts
168
+ * const results = new SmartIterator<number>([-5, -4, -3, -2, -1, 0])
169
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
170
+ * .some((key, value) => value >= 0);
171
+ *
172
+ * console.log(results.toObject()); // { odd: false, even: true }
173
+ * ```
174
+ *
175
+ * @param predicate The condition to check for each element of the iterator.
176
+ *
177
+ * @returns A {@link ReducedIterator} containing the boolean results for each group.
178
+ */
38
179
  public some(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, boolean>
39
180
  {
40
181
  const values = new Map<K, [number, boolean]>();
@@ -54,8 +195,65 @@ export default class AggregatedIterator<K extends PropertyKey, T>
54
195
  });
55
196
  }
56
197
 
198
+ /**
199
+ * Filters the elements of the iterator using a given condition.
200
+ *
201
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
202
+ * If the condition is met, the element will be included in the new iterator.
203
+ *
204
+ * Since the iterator is lazy, the filtering process will
205
+ * be executed once the resulting iterator is materialized.
206
+ *
207
+ * A new iterator will be created, holding the reference to the original one.
208
+ * This means that the original iterator won't be consumed until the
209
+ * new one is and that consuming one of them will consume the other as well.
210
+ *
211
+ * ```ts
212
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
213
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
214
+ * .filter((key, value) => value >= 0);
215
+ *
216
+ * console.log(results.toObject()); // { odd: [3, 5], even: [0, 2, 6, 8] }
217
+ * ```
218
+ *
219
+ * @param predicate The condition to check for each element of the iterator.
220
+ *
221
+ * @returns A new {@link AggregatedIterator} containing only the elements that satisfy the condition.
222
+ */
57
223
  public filter(predicate: KeyedIteratee<K, T, boolean>): AggregatedIterator<K, T>;
58
- public filter<S extends T>(predicate: KeyedTypeGuardIteratee<K, T, S>): AggregatedIterator<K, S>;
224
+
225
+ /**
226
+ * Filters the elements of the iterator using a given condition.
227
+ *
228
+ * This method will iterate over all elements of the iterator checking if they satisfy the condition.
229
+ * If the condition is met, the element will be included in the new iterator.
230
+ *
231
+ * Since the iterator is lazy, the filtering process will
232
+ * be executed once the resulting iterator is materialized.
233
+ *
234
+ * A new iterator will be created, holding the reference to the original one.
235
+ * This means that the original iterator won't be consumed until the
236
+ * new one is and that consuming one of them will consume the other as well.
237
+ *
238
+ * ```ts
239
+ * const results = new SmartIterator<number | string>([-3, "-1", 0, "2", "3", 5, 6, "8"])
240
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
241
+ * .filter<number>((key, value) => typeof value === "number");
242
+ *
243
+ * console.log(results.toObject()); // { odd: [-3, 5], even: [0, 6] }
244
+ * ```
245
+ *
246
+ * @template S
247
+ * The type of the elements that satisfy the condition.
248
+ * This allows the type-system to infer the correct type of the new iterator.
249
+ *
250
+ * It must be a subtype of the original type of the elements.
251
+ *
252
+ * @param predicate The type guard condition to check for each element of the iterator.
253
+ *
254
+ * @returns A new {@link AggregatedIterator} containing only the elements that satisfy the condition.
255
+ */
256
+ public filter<S extends T>(predicate: KeyedTypeGuardPredicate<K, T, S>): AggregatedIterator<K, S>;
59
257
  public filter(predicate: KeyedIteratee<K, T, boolean>): AggregatedIterator<K, T>
60
258
  {
61
259
  const elements = this._elements;
@@ -63,17 +261,43 @@ export default class AggregatedIterator<K extends PropertyKey, T>
63
261
  return new AggregatedIterator(function* ()
64
262
  {
65
263
  const indexes = new Map<K, number>();
66
-
67
264
  for (const [key, element] of elements)
68
265
  {
69
266
  const index = indexes.get(key) ?? 0;
70
-
71
267
  if (predicate(key, element, index)) { yield [key, element]; }
72
268
 
73
269
  indexes.set(key, index + 1);
74
270
  }
75
271
  });
76
272
  }
273
+
274
+ /**
275
+ * Maps the elements of the iterator using a given transformation function.
276
+ *
277
+ * This method will iterate over all elements of the iterator applying the transformation function.
278
+ * The result of each transformation will be included in the new iterator.
279
+ *
280
+ * Since the iterator is lazy, the mapping process will
281
+ * be executed once the resulting iterator is materialized.
282
+ *
283
+ * A new iterator will be created, holding the reference to the original one.
284
+ * This means that the original iterator won't be consumed until the
285
+ * new one is and that consuming one of them will consume the other as well.
286
+ *
287
+ * ```ts
288
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
289
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
290
+ * .map((key, value) => Math.abs(value));
291
+ *
292
+ * console.log(results.toObject()); // { odd: [3, 1, 3, 5], even: [0, 2, 6, 8] }
293
+ * ```
294
+ *
295
+ * @template V The type of the elements after the transformation.
296
+ *
297
+ * @param iteratee The transformation function to apply to each element of the iterator.
298
+ *
299
+ * @returns A new {@link AggregatedIterator} containing the transformed elements.
300
+ */
77
301
  public map<V>(iteratee: KeyedIteratee<K, T, V>): AggregatedIterator<K, V>
78
302
  {
79
303
  const elements = this._elements;
@@ -81,20 +305,106 @@ export default class AggregatedIterator<K extends PropertyKey, T>
81
305
  return new AggregatedIterator(function* ()
82
306
  {
83
307
  const indexes = new Map<K, number>();
84
-
85
308
  for (const [key, element] of elements)
86
309
  {
87
310
  const index = indexes.get(key) ?? 0;
88
-
89
311
  yield [key, iteratee(key, element, index)];
90
312
 
91
313
  indexes.set(key, index + 1);
92
314
  }
93
315
  });
94
316
  }
317
+
318
+ /**
319
+ * Reduces the elements of the iterator using a given reducer function.
320
+ * This method will consume the entire iterator in the process.
321
+ *
322
+ * It will iterate over all elements of the iterator applying the reducer function.
323
+ * The result of each iteration will be passed as the accumulator to the next one.
324
+ *
325
+ * The first accumulator value will be the first element of the iterator.
326
+ * The last accumulator value will be the final result of the reduction.
327
+ *
328
+ * Eventually, it will return a new {@link ReducedIterator}
329
+ * object that will contain all the reduced results for each group.
330
+ * If the iterator is infinite, the method will never return.
331
+ *
332
+ * ```ts
333
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
334
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
335
+ * .reduce((key, accumulator, value) => accumulator + value);
336
+ *
337
+ * console.log(results.toObject()); // { odd: 4, even: 16 }
338
+ * ```
339
+ *
340
+ * @param reducer The reducer function to apply to each element of the iterator.
341
+ *
342
+ * @returns A new {@link ReducedIterator} containing the reduced results for each group.
343
+ */
95
344
  public reduce(reducer: KeyedReducer<K, T, T>): ReducedIterator<K, T>;
345
+
346
+ /**
347
+ * Reduces the elements of the iterator using a given reducer function.
348
+ * This method will consume the entire iterator in the process.
349
+ *
350
+ * It will iterate over all elements of the iterator applying the reducer function.
351
+ * The result of each iteration will be passed as the accumulator to the next one.
352
+ *
353
+ * The first accumulator value will be the provided initial value.
354
+ * The last accumulator value will be the final result of the reduction.
355
+ *
356
+ * Eventually, it will return a new {@link ReducedIterator}
357
+ * object that will contain all the reduced results for each group.
358
+ * If the iterator is infinite, the method will never return.
359
+ *
360
+ * ```ts
361
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
362
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
363
+ * .reduce((key, accumulator, value) => accumulator + value, 0);
364
+ *
365
+ * console.log(results.toObject()); // { odd: 4, even: 16 }
366
+ * ```
367
+ *
368
+ * @template A The type of the accumulator value which will also be the type of the final result of the reduction.
369
+ *
370
+ * @param reducer The reducer function to apply to each element of the iterator.
371
+ * @param initialValue The initial value of the accumulator.
372
+ *
373
+ * @returns A new {@link ReducedIterator} containing the reduced results for each group.
374
+ */
375
+ public reduce<A extends PropertyKey>(reducer: KeyedReducer<K, T, A>, initialValue: A): ReducedIterator<K, A>;
376
+
377
+ /**
378
+ * Reduces the elements of the iterator using a given reducer function.
379
+ * This method will consume the entire iterator in the process.
380
+ *
381
+ * It will iterate over all elements of the iterator applying the reducer function.
382
+ * The result of each iteration will be passed as the accumulator to the next one.
383
+ *
384
+ * The first accumulator value will be the provided initial value by the given function.
385
+ * The last accumulator value will be the final result of the reduction.
386
+ *
387
+ * Eventually, it will return a new {@link ReducedIterator}
388
+ * object that will contain all the reduced results for each group.
389
+ * If the iterator is infinite, the method will never return.
390
+ *
391
+ * ```ts
392
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
393
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
394
+ * .reduce((key, { value }, currentValue) => ({ value: value + currentValue }), (key) => ({ value: 0 }));
395
+ *
396
+ * console.log(results.toObject()); // { odd: { value: 4 }, even: { value: 16 } }
397
+ * ```
398
+ *
399
+ * @template A The type of the accumulator value which will also be the type of the final result of the reduction.
400
+ *
401
+ * @param reducer The reducer function to apply to each element of the iterator.
402
+ * @param initialValue The function that provides the initial value for the accumulator.
403
+ *
404
+ * @returns A new {@link ReducedIterator} containing the reduced results for each group.
405
+ */
96
406
  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>
407
+ public reduce<A>(reducer: KeyedReducer<K, T, A>, initialValue?: A | ((key: K) => A)): ReducedIterator<K, A>
98
408
  {
99
409
  const values = new Map<K, [number, A]>();
100
410
 
@@ -107,7 +417,9 @@ export default class AggregatedIterator<K extends PropertyKey, T>
107
417
  else if (initialValue !== undefined)
108
418
  {
109
419
  index = 0;
110
- accumulator = initialValue(key);
420
+
421
+ if (initialValue instanceof Function) { accumulator = initialValue(key); }
422
+ else { accumulator = initialValue; }
111
423
  }
112
424
  else
113
425
  {
@@ -125,26 +437,84 @@ export default class AggregatedIterator<K extends PropertyKey, T>
125
437
  });
126
438
  }
127
439
 
128
- public flatMap<V>(iteratee: KeyedIteratee<K, T, Iterable<V>>): AggregatedIterator<K, V>
440
+ /**
441
+ * Flattens the elements of the iterator using a given transformation function.
442
+ *
443
+ * This method will iterate over all elements of the iterator applying the transformation function.
444
+ * The result of each transformation will be included in the new iterator.
445
+ *
446
+ * Since the iterator is lazy, the flattening process will
447
+ * be executed once the resulting iterator is materialized.
448
+ *
449
+ * A new iterator will be created, holding the reference to the original one.
450
+ * This means that the original iterator won't be consumed until the
451
+ * new one is and that consuming one of them will consume the other as well.
452
+ *
453
+ * ```ts
454
+ * const results = new SmartIterator<number[]>([[-3, -1], 0, 2, 3, 5, [6, 8]])
455
+ * .groupBy((values) =>
456
+ * {
457
+ * const value = values instanceof Array ? values[0] : values;
458
+ * return value % 2 === 0 ? "even" : "odd";
459
+ * })
460
+ * .flatMap((key, values) => values);
461
+ *
462
+ * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
463
+ * ```
464
+ *
465
+ * @template V The type of the elements after the transformation.
466
+ *
467
+ * @param iteratee The transformation function to apply to each element of the iterator.
468
+ *
469
+ * @returns A new {@link AggregatedIterator} containing the transformed elements.
470
+ */
471
+ public flatMap<V>(iteratee: KeyedIteratee<K, T, V | readonly V[]>): AggregatedIterator<K, V>
129
472
  {
130
473
  const elements = this._elements;
131
474
 
132
475
  return new AggregatedIterator(function* ()
133
476
  {
134
477
  const indexes = new Map<K, number>();
135
-
136
478
  for (const [key, element] of elements)
137
479
  {
138
480
  const index = indexes.get(key) ?? 0;
139
481
  const values = iteratee(key, element, index);
140
482
 
141
- for (const value of values) { yield [key, value]; }
483
+ if (values instanceof Array)
484
+ {
485
+ for (const value of values) { yield [key, value]; }
486
+ }
487
+ else { yield [key, values]; }
142
488
 
143
489
  indexes.set(key, index + 1);
144
490
  }
145
491
  });
146
492
  }
147
493
 
494
+ /**
495
+ * Drops a given number of elements from the beginning of each group of the iterator.
496
+ * The remaining elements will be included in the new iterator.
497
+ * See also {@link AggregatedIterator.take}.
498
+ *
499
+ * Since the iterator is lazy, the dropping process will
500
+ * be executed once the resulting iterator is materialized.
501
+ *
502
+ * A new iterator will be created, holding the reference to the original one.
503
+ * This means that the original iterator won't be consumed until the
504
+ * new one is and that consuming one of them will consume the other as well.
505
+ *
506
+ * ```ts
507
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
508
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
509
+ * .drop(2);
510
+ *
511
+ * console.log(results.toObject()); // { odd: [3, 5], even: [6, 8] }
512
+ * ```
513
+ *
514
+ * @param count The number of elements to drop from the beginning of each group.
515
+ *
516
+ * @returns A new {@link AggregatedIterator} containing the remaining elements.
517
+ */
148
518
  public drop(count: number): AggregatedIterator<K, T>
149
519
  {
150
520
  const elements = this._elements;
@@ -152,7 +522,6 @@ export default class AggregatedIterator<K extends PropertyKey, T>
152
522
  return new AggregatedIterator(function* ()
153
523
  {
154
524
  const indexes = new Map<K, number>();
155
-
156
525
  for (const [key, element] of elements)
157
526
  {
158
527
  const index = indexes.get(key) ?? 0;
@@ -167,6 +536,31 @@ export default class AggregatedIterator<K extends PropertyKey, T>
167
536
  }
168
537
  });
169
538
  }
539
+
540
+ /**
541
+ * Takes a given number of elements from the beginning of each group of the iterator.
542
+ * The elements will be included in the new iterator.
543
+ * See also {@link AggregatedIterator.drop}.
544
+ *
545
+ * Since the iterator is lazy, the taking process will
546
+ * be executed once the resulting iterator is materialized.
547
+ *
548
+ * A new iterator will be created, holding the reference to the original one.
549
+ * This means that the original iterator won't be consumed until the
550
+ * new one is and that consuming one of them will consume the other as well.
551
+ *
552
+ * ```ts
553
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
554
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
555
+ * .take(2);
556
+ *
557
+ * console.log(results.toObject()); // { odd: [-3, -1], even: [0, 2] }
558
+ * ```
559
+ *
560
+ * @param count The number of elements to take from the beginning of each group.
561
+ *
562
+ * @returns A new {@link AggregatedIterator} containing the taken elements.
563
+ */
170
564
  public take(limit: number): AggregatedIterator<K, T>
171
565
  {
172
566
  const elements = this._elements;
@@ -174,12 +568,10 @@ export default class AggregatedIterator<K extends PropertyKey, T>
174
568
  return new AggregatedIterator(function* ()
175
569
  {
176
570
  const indexes = new Map<K, number>();
177
-
178
571
  for (const [key, element] of elements)
179
572
  {
180
573
  const index = indexes.get(key) ?? 0;
181
574
  if (index >= limit) { continue; }
182
-
183
575
  yield [key, element];
184
576
 
185
577
  indexes.set(key, index + 1);
@@ -187,8 +579,63 @@ export default class AggregatedIterator<K extends PropertyKey, T>
187
579
  });
188
580
  }
189
581
 
582
+ /**
583
+ * Finds the first element of each group of the iterator that satisfies a given condition.
584
+ * This method will consume the entire iterator in the process.
585
+ *
586
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
587
+ * Once the first element of one group satisfies the condition,
588
+ * the result for the respective group will be the element itself.
589
+ *
590
+ * Eventually, it will return a new {@link ReducedIterator}
591
+ * object that will contain the first element that satisfies the condition for each group.
592
+ * If the iterator is infinite, the method will never return.
593
+ *
594
+ * ```ts
595
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
596
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
597
+ * .find((key, value) => value > 0);
598
+ *
599
+ * console.log(results.toObject()); // { odd: 3, even: 2 }
600
+ * ```
601
+ *
602
+ * @param predicate The condition to check for each element of the iterator.
603
+ *
604
+ * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group.
605
+ */
190
606
  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>;
607
+
608
+ /**
609
+ * Finds the first element of each group of the iterator that satisfies a given condition.
610
+ * This method will consume the entire iterator in the process.
611
+ *
612
+ * It will iterate over all elements of the iterator checking if they satisfy the condition.
613
+ * Once the first element of one group satisfies the condition,
614
+ * the result for the respective group will be the element itself.
615
+ *
616
+ * Eventually, it will return a new {@link ReducedIterator}
617
+ * object that will contain the first element that satisfies the condition for each group.
618
+ * If the iterator is infinite, the method will never return.
619
+ *
620
+ * ```ts
621
+ * const results = new SmartIterator<number | string>([-3, "-1", 0, "2", "3", 5, 6, "8"])
622
+ * .groupBy((value) => Number(value) % 2 === 0 ? "even" : "odd")
623
+ * .find<number>((key, value) => typeof value === "number");
624
+ *
625
+ * console.log(results.toObject()); // { odd: -3, even: 0 }
626
+ * ```
627
+ *
628
+ * @template S
629
+ * The type of the elements that satisfy the condition.
630
+ * This allows the type-system to infer the correct type of the new iterator.
631
+ *
632
+ * It must be a subtype of the original type of the elements.
633
+ *
634
+ * @param predicate The type guard condition to check for each element of the iterator.
635
+ *
636
+ * @returns A new {@link ReducedIterator} containing the first element that satisfies the condition for each group.
637
+ */
638
+ public find<S extends T>(predicate: KeyedTypeGuardPredicate<K, T, S>): ReducedIterator<K, S | undefined>;
192
639
  public find(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, T | undefined>
193
640
  {
194
641
  const values = new Map<K, [number, T | undefined]>();
@@ -209,10 +656,53 @@ export default class AggregatedIterator<K extends PropertyKey, T>
209
656
  });
210
657
  }
211
658
 
659
+ /**
660
+ * Enumerates the elements of the iterator.
661
+ * Each element is paired with its index within the group in a new iterator.
662
+ *
663
+ * Since the iterator is lazy, the enumeration process will
664
+ * be executed once the resulting iterator is materialized.
665
+ *
666
+ * A new iterator will be created, holding the reference to the original one.
667
+ * This means that the original iterator won't be consumed until the
668
+ * new one is and that consuming one of them will consume the other as well.
669
+ *
670
+ * ```ts
671
+ * const results = new SmartIterator<number>([-3, 0, 2, -1, 3])
672
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
673
+ * .enumerate();
674
+ *
675
+ * console.log(results.toObject()); // { odd: [[0, -3], [1, -1], [2, 3]], even: [[0, 0], [1, 2]] }
676
+ * ```
677
+ *
678
+ * @returns A new {@link AggregatedIterator} containing the enumerated elements.
679
+ */
212
680
  public enumerate(): AggregatedIterator<K, [number, T]>
213
681
  {
214
682
  return this.map((_, value, index) => [index, value]);
215
683
  }
684
+
685
+ /**
686
+ * Removes all duplicate elements from within each group of the iterator.
687
+ * The first occurrence of each element will be included in the new iterator.
688
+ *
689
+ * Since the iterator is lazy, the deduplication process will
690
+ * be executed once the resulting iterator is materialized.
691
+ *
692
+ * A new iterator will be created, holding the reference to the original one.
693
+ * This means that the original iterator won't be consumed until the
694
+ * new one is and that consuming one of them will consume the other as well.
695
+ *
696
+ * ```ts
697
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 6, -3, -1, 0, 5, 6, 8, 0, 2])
698
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
699
+ * .unique();
700
+ *
701
+ * console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
702
+ * ```
703
+ *
704
+ * @returns A new {@link AggregatedIterator} containing only the unique elements.
705
+ */
216
706
  public unique(): AggregatedIterator<K, T>
217
707
  {
218
708
  const elements = this._elements;
@@ -220,11 +710,9 @@ export default class AggregatedIterator<K extends PropertyKey, T>
220
710
  return new AggregatedIterator(function* ()
221
711
  {
222
712
  const keys = new Map<K, Set<T>>();
223
-
224
713
  for (const [key, element] of elements)
225
714
  {
226
715
  const values = keys.get(key) ?? new Set<T>();
227
-
228
716
  if (values.has(element)) { continue; }
229
717
 
230
718
  values.add(element);
@@ -235,6 +723,22 @@ export default class AggregatedIterator<K extends PropertyKey, T>
235
723
  });
236
724
  }
237
725
 
726
+ /**
727
+ * Counts the number of elements within each group of the iterator.
728
+ * This method will consume the entire iterator in the process.
729
+ *
730
+ * If the iterator is infinite, the method will never return.
731
+ *
732
+ * ```ts
733
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
734
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
735
+ * .count();
736
+ *
737
+ * console.log(results.toObject()); // { odd: 4, even: 4 }
738
+ * ```
739
+ *
740
+ * @returns A new {@link ReducedIterator} containing the number of elements for each group.
741
+ */
238
742
  public count(): ReducedIterator<K, number>
239
743
  {
240
744
  const counters = new Map<K, number>();
@@ -252,20 +756,101 @@ export default class AggregatedIterator<K extends PropertyKey, T>
252
756
  });
253
757
  }
254
758
 
759
+ /**
760
+ * Iterates over the elements of the iterator.
761
+ * The elements are passed to the given iteratee function along with their key and index within the group.
762
+ *
763
+ * This method will consume the entire iterator in the process.
764
+ * If the iterator is infinite, the method will never return.
765
+ *
766
+ * ```ts
767
+ * const aggregator = new SmartIterator<number>([-3, 0, 2, -1, 3])
768
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
769
+ *
770
+ * aggregator.forEach((key, value, index) =>
771
+ * {
772
+ * console.log(`${index}: ${value}`); // "0: -3", "0: 0", "1: 2", "1: -1", "2: 3"
773
+ * };
774
+ * ```
775
+ *
776
+ * @param iteratee The function to execute for each element of the iterator.
777
+ */
255
778
  public forEach(iteratee: KeyedIteratee<K, T>): void
256
779
  {
257
780
  const indexes = new Map<K, number>();
258
-
259
781
  for (const [key, element] of this._elements)
260
782
  {
261
783
  const index = indexes.get(key) ?? 0;
262
-
263
784
  iteratee(key, element, index);
264
785
 
265
786
  indexes.set(key, index + 1);
266
787
  }
267
788
  }
268
789
 
790
+ /**
791
+ * Changes the key of each element on which the iterator is aggregated.
792
+ * The new key is determined by the given iteratee function.
793
+ *
794
+ * Since the iterator is lazy, the reorganization process will
795
+ * be executed once the resulting iterator is materialized.
796
+ *
797
+ * A new iterator will be created, holding the reference to the original one.
798
+ * This means that the original iterator won't be consumed until the
799
+ * new one is and that consuming one of them will consume the other as well.
800
+ *
801
+ * ```ts
802
+ * const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
803
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
804
+ * .map((key, value, index) => index % 2 === 0 ? value : -value)
805
+ * .reorganizeBy((key, value) => value >= 0 ? "+" : "-");
806
+ *
807
+ * console.log(results.toObject()); // { "+": [1, 0, 3, 6], "-": [-3, -2, -5, -8] }
808
+ * ```
809
+ *
810
+ * @template J The type of the new key.
811
+ *
812
+ * @param iteratee The function to determine the new key for each element of the iterator.
813
+ *
814
+ * @returns A new {@link AggregatedIterator} containing the elements reorganized by the new keys.
815
+ */
816
+ public reorganizeBy<J extends PropertyKey>(iteratee: KeyedIteratee<K, T, J>): AggregatedIterator<J, T>
817
+ {
818
+ const elements = this._elements;
819
+
820
+ return new AggregatedIterator(function* ()
821
+ {
822
+ const indexes = new Map<K, number>();
823
+ for (const [key, element] of elements)
824
+ {
825
+ const index = indexes.get(key) ?? 0;
826
+ yield [iteratee(key, element, index), element];
827
+
828
+ indexes.set(key, index + 1);
829
+ }
830
+ });
831
+ }
832
+
833
+ /**
834
+ * An utility method that returns a new {@link SmartIterator}
835
+ * object containing all the keys of the iterator.
836
+ *
837
+ * Since the iterator is lazy, the keys will be extracted
838
+ * be executed once the resulting iterator is materialized.
839
+ *
840
+ * A new iterator will be created, holding the reference to the original one.
841
+ * This means that the original iterator won't be consumed until the
842
+ * new one is and that consuming one of them will consume the other as well.
843
+ *
844
+ * ```ts
845
+ * const keys = new SmartIterator([-3, Symbol(), "A", { }, null, [1 , 2, 3], false])
846
+ * .groupBy((value) => typeof value)
847
+ * .keys();
848
+ *
849
+ * console.log(keys.toArray()); // ["number", "symbol", "string", "object", "boolean"]
850
+ * ```
851
+ *
852
+ * @returns A new {@link SmartIterator} containing all the keys of the iterator.
853
+ */
269
854
  public keys(): SmartIterator<K>
270
855
  {
271
856
  const elements = this._elements;
@@ -273,7 +858,6 @@ export default class AggregatedIterator<K extends PropertyKey, T>
273
858
  return new SmartIterator<K>(function* ()
274
859
  {
275
860
  const keys = new Set<K>();
276
-
277
861
  for (const [key] of elements)
278
862
  {
279
863
  if (keys.has(key)) { continue; }
@@ -283,10 +867,55 @@ export default class AggregatedIterator<K extends PropertyKey, T>
283
867
  }
284
868
  });
285
869
  }
286
- public items(): SmartIterator<[K, T]>
870
+
871
+ /**
872
+ * An utility method that returns a new {@link SmartIterator}
873
+ * object containing all the entries of the iterator.
874
+ * Each entry is a tuple containing the key and the element.
875
+ *
876
+ * Since the iterator is lazy, the entries will be extracted
877
+ * be executed once the resulting iterator is materialized.
878
+ *
879
+ * A new iterator will be created, holding the reference to the original one.
880
+ * This means that the original iterator won't be consumed until the
881
+ * new one is and that consuming one of them will consume the other as well.
882
+ *
883
+ * ```ts
884
+ * const entries = new SmartIterator<number>([-3, 0, 2, -1, 3])
885
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
886
+ * .entries();
887
+ *
888
+ * console.log(entries.toArray()); // [["odd", -3], ["even", 0], ["even", 2], ["odd", -1], ["odd", 3]]
889
+ * ```
890
+ *
891
+ * @returns A new {@link SmartIterator} containing all the entries of the iterator.
892
+ */
893
+ public entries(): SmartIterator<[K, T]>
287
894
  {
288
895
  return this._elements;
289
896
  }
897
+
898
+ /**
899
+ * An utility method that returns a new {@link SmartIterator}
900
+ * object containing all the values of the iterator.
901
+ *
902
+ * Since the iterator is lazy, the values will be extracted
903
+ * be executed once the resulting iterator is materialized.
904
+ *
905
+ * A new iterator will be created, holding the reference to the original one.
906
+ * This means that the original iterator won't be consumed until the
907
+ * new one is and that consuming one of them will consume the other as well.
908
+ *
909
+ * ```ts
910
+ * const values = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
911
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd")
912
+ * .values();
913
+ *
914
+ * console.log(values.toArray()); // [-3, -1, 0, 2, 3, 5, 6, 8]
915
+ * ```
916
+ *
917
+ * @returns A new {@link SmartIterator} containing all the values of the iterator.
918
+ */
290
919
  public values(): SmartIterator<T>
291
920
  {
292
921
  const elements = this._elements;
@@ -297,12 +926,43 @@ export default class AggregatedIterator<K extends PropertyKey, T>
297
926
  });
298
927
  }
299
928
 
929
+ /**
930
+ * Materializes the iterator into an array of arrays.
931
+ * This method will consume the entire iterator in the process.
932
+ *
933
+ * If the iterator is infinite, the method will never return.
934
+ *
935
+ * ```ts
936
+ * const aggregator = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
937
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
938
+ *
939
+ * console.log(aggregator.toArray()); // [[-3, -1, 3, 5], [0, 2, 6, 8]]
940
+ * ```
941
+ *
942
+ * @returns An {@link Array} of arrays containing the elements of the iterator.
943
+ */
300
944
  public toArray(): T[][]
301
945
  {
302
946
  const map = this.toMap();
303
947
 
304
948
  return Array.from(map.values());
305
949
  }
950
+
951
+ /**
952
+ * Materializes the iterator into a map.
953
+ * This method will consume the entire iterator in the process.
954
+ *
955
+ * If the iterator is infinite, the method will never return.
956
+ *
957
+ * ```ts
958
+ * const aggregator = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
959
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
960
+ *
961
+ * console.log(aggregator.toMap()); // Map(2) { "odd" => [-3, -1, 3, 5], "even" => [0, 2, 6, 8] }
962
+ * ```
963
+ *
964
+ * @returns A {@link Map} containing the elements of the iterator.
965
+ */
306
966
  public toMap(): Map<K, T[]>
307
967
  {
308
968
  const groups = new Map<K, T[]>();
@@ -317,6 +977,22 @@ export default class AggregatedIterator<K extends PropertyKey, T>
317
977
 
318
978
  return groups;
319
979
  }
980
+
981
+ /**
982
+ * Materializes the iterator into an object.
983
+ * This method will consume the entire iterator in the process.
984
+ *
985
+ * If the iterator is infinite, the method will never return.
986
+ *
987
+ * ```ts
988
+ * const aggregator = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
989
+ * .groupBy((value) => value % 2 === 0 ? "even" : "odd");
990
+ *
991
+ * console.log(aggregator.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
992
+ * ```
993
+ *
994
+ * @returns An {@link Object} containing the elements of the iterator.
995
+ */
320
996
  public toObject(): Record<K, T[]>
321
997
  {
322
998
  const groups = { } as Record<K, T[]>;