@byloth/core
Version:
An unopinionated collection of useful functions and classes that I use widely in all my projects. 🔧
1,043 lines (992 loc) • 37 kB
text/typescript
import { ValueException } from "../exceptions/index.js";
import { SmartIterator } from "../iterators/index.js";
import type { GeneratorFunction } from "../iterators/types.js";
import AggregatedIterator from "./aggregated-iterator.js";
import type { KeyedIteratee, KeyedReducer, KeyedTypeGuardPredicate } from "./types.js";
/**
* A class representing an aggregated iterator that has been reduced in a lazy and optimized way.
*
* It's part of the {@link AggregatedIterator} and {@link AggregatedAsyncIterator} implementations,
* providing a way to reduce them into a single value or another aggregated iterable.
* For this reason, it isn't recommended to instantiate this class directly
* (although it's still possible), but rather use the reducing methods provided by the aggregated iterators.
*
* It isn't directly iterable, just like its parent class, and needs to specify on what you want to iterate.
* See the {@link ReducedIterator.keys}, {@link ReducedIterator.entries}
* & {@link ReducedIterator.values} methods.
* It does, however, provide the same set of methods to perform
* operations and transformation on the elements of the iterator,
* having also the knowledge and context of the groups to which
* they belong, allowing to handle them in a grouped manner.
*
* This is particularly useful when you have group elements and
* need perform specific operations on the reduced elements.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .count();
*
* console.log(results.toObject()); // { odd: 4, even: 4 }
* ```
*
* ---
*
* @template K The type of the key used to group the elements.
* @template T The type of the elements in the iterator.
*/
export default class ReducedIterator<K extends PropertyKey, T>
{
/**
* The internal {@link SmartIterator} object that holds the reduced elements.
*/
protected readonly _elements: SmartIterator<[K, T]>;
/**
* Initializes a new instance of the {@link ReducedIterator} class.
*
* ---
*
* @example
* ```ts
* const results = new ReducedIterator<string, number>([["A", 1], ["B", 2], ["C", 4]]);
* ```
*
* ---
*
* @param iterable A reduced iterable object.
*/
public constructor(iterable: Iterable<[K, T]>);
/**
* Initializes a new instance of the {@link ReducedIterator} class.
*
* ---
*
* @example
* ```ts
* const results = new ReducedIterator<string, number>({
* _index: 0,
* next: () =>
* {
* if (this._index >= 3) { return { done: true, value: undefined }; }
* this._index += 1;
*
* return { done: false, value: [["A", "B", "C"][this._index], (this._index + 1)] };
* }
* });
* ```
*
* ---
*
* @param iterator An reduced iterator object.
*/
public constructor(iterator: Iterator<[K, T]>);
/**
* Initializes a new instance of the {@link ReducedIterator} class.
*
* ---
*
* @example
* ```ts
* import { range, Random } from "@byloth/core";
*
* const results = new ReducedIterator<string, number>(function* ()
* {
* for (const index of range(3))
* {
* yield [["A", "B", "C"][index], (index + 1)];
* }
* });
* ```
*
* ---
*
* @param generatorFn A generator function that produces the reduced elements.
*/
public constructor(generatorFn: GeneratorFunction<[K, T]>);
/**
* Initializes a new instance of the {@link ReducedIterator} class.
*
* ---
*
* @example
* ```ts
* const results = new ReducedIterator(reducedValues);
* ```
*
* ---
*
* @param argument An iterable, iterator or generator function that produces the reduced elements.
*/
public constructor(argument: Iterable<[K, T]> | Iterator<[K, T]> | GeneratorFunction<[K, T]>);
public constructor(argument: Iterable<[K, T]> | Iterator<[K, T]> | GeneratorFunction<[K, T]>)
{
this._elements = new SmartIterator(argument);
}
/**
* Determines whether all elements of the reduced iterator satisfy the given condition.
* See also {@link ReducedIterator.some}.
*
* This method will iterate over all the elements of the iterator checking if they satisfy the condition.
* Once a single element doesn't satisfy the condition, the method will return `false` immediately.
*
* This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
* For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
* Consider using {@link ReducedIterator.find} instead.
*
* If the iterator is infinite and every element satisfies the condition, the method will never return.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .every((key, value) => value > 0);
*
* console.log(results); // true
* ```
*
* ---
*
* @param predicate The condition to check for each element of the iterator.
*
* @returns `true` if all elements satisfy the condition, `false` otherwise.
*/
public every(predicate: KeyedIteratee<K, T, boolean>): boolean
{
for (const [index, [key, element]] of this._elements.enumerate())
{
if (!(predicate(key, element, index))) { return false; }
}
return true;
}
/**
* Determines whether any element of the reduced iterator satisfies the given condition.
* See also {@link ReducedIterator.every}.
*
* This method will iterate over all the elements of the iterator checking if they satisfy the condition.
* Once a single element satisfies the condition, the method will return `true` immediately.
*
* This may lead to an unknown final state of the iterator, which may be entirely or partially consumed.
* For this reason, it's recommended to consider it as consumed in any case and to not use it anymore.
* Consider using {@link ReducedIterator.find} instead.
*
* If the iterator is infinite and no element satisfies the condition, the method will never return.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .some((key, value) => value > 0);
*
* console.log(results); // true
* ```
*
* ---
*
* @param predicate The condition to check for each element of the iterator.
*
* @returns `true` if any element satisfies the condition, `false` otherwise.
*/
public some(predicate: KeyedIteratee<K, T, boolean>): boolean
{
for (const [index, [key, element]] of this._elements.enumerate())
{
if (predicate(key, element, index)) { return true; }
}
return false;
}
/**
* Filters the elements of the reduced iterator using a given condition.
*
* This method will iterate over all the elements of the iterator checking if they satisfy the condition.
* If the condition is met, the element will be included in the new iterator.
*
* Since the iterator is lazy, the filtering process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .filter((key, value) => value > 0);
*
* console.log(results.toObject()); // { odd: 4, even: 16 }
* ```
*
* ---
*
* @param predicate The condition to check for each element of the iterator.
*
* @returns A new {@link ReducedIterator} containing only the elements that satisfy the condition.
*/
public filter(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, T>;
/**
* Filters the elements of the reduced iterator using a given type guard predicate.
*
* This method will iterate over all the elements of the iterator checking if they satisfy the condition.
* If the condition is met, the element will be included in the new iterator.
*
* Since the iterator is lazy, the filtering process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number | string>([-3, -1, "0", "2", 3, 5, "6", "8"])
* .groupBy((value) => Number(value) % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .filter<number>((key, value) => typeof value === "number");
*
* console.log(results.toObject()); // { odd: 4 }
* ```
*
* ---
*
* @template S
* The type of the elements that satisfy the condition.
* This allows the type-system to infer the correct type of the iterator.
*
* It must be a subtype of the original type of the elements.
*
* @param predicate The type guard condition to check for each element of the iterator.
*
* @returns A new {@link ReducedIterator} containing only the elements that satisfy the condition.
*/
public filter<S extends T>(predicate: KeyedTypeGuardPredicate<K, T, S>): ReducedIterator<K, S>;
public filter(predicate: KeyedIteratee<K, T, boolean>): ReducedIterator<K, T>
{
const elements = this._elements.enumerate();
return new ReducedIterator(function* ()
{
for (const [index, [key, element]] of elements)
{
if (predicate(key, element, index)) { yield [key, element]; }
}
});
}
/**
* Maps the elements of the reduced iterator using a given transformation function.
*
* This method will iterate over all the elements of the iterator applying the transformation function.
* The result of the transformation will be included in the new iterator.
*
* Since the iterator is lazy, the mapping process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .map((key, value) => value * 2);
*
* console.log(results.toObject()); // { odd: 8, even: 32 }
* ```
*
* ---
*
* @template V The type of the elements after the transformation.
*
* @param iteratee The transformation function to apply to each element of the iterator.
*
* @returns A new {@link ReducedIterator} containing the transformed elements.
*/
public map<V>(iteratee: KeyedIteratee<K, T, V>): ReducedIterator<K, V>
{
const elements = this._elements.enumerate();
return new ReducedIterator(function* ()
{
for (const [index, [key, element]] of elements)
{
yield [key, iteratee(key, element, index)];
}
});
}
/**
* Reduces the elements of the reduced iterator using a given reducer function.
* This method will consume the entire iterator in the process.
*
* It will iterate over all the elements of the iterator applying the reducer function.
* The result of each iteration will be passed as the accumulator to the next one.
*
* The first accumulator value will be the first element of the iterator.
* The last accumulator value will be the final result of the reduction.
*
* Also note that:
* - If an empty iterator is provided, a {@link ValueException} will be thrown.
* - If the iterator is infinite, the method will never return.
*
* ---
*
* @example
* ```ts
* const result = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .reduce((key, accumulator, value) => accumulator + value);
*
* console.log(result); // 20
* ```
*
* ---
*
* @param reducer The reducer function to apply to the elements of the iterator.
*
* @returns The final value after reducing all the elements of the iterator.
*/
public reduce(reducer: KeyedReducer<K, T, T>): T;
/**
* Reduces the elements of the reduced iterator using a given reducer function.
* This method will consume the entire iterator in the process.
*
* It will iterate over all the elements of the iterator applying the reducer function.
* The result of each iteration will be passed as the accumulator to the next one.
*
* The first accumulator value will be the provided initial value.
* The last accumulator value will be the final result of the reduction.
*
* If the iterator is infinite, the method will never return.
*
* ---
*
* @example
* ```ts
* const result = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .reduce((key, { value }, currentValue) => ({ value: value + currentValue }), { value: 0 });
*
* console.log(result); // { value: 20 }
* ```
*
* ---
*
* @template A The type of the accumulator value which will also be the type of the final result of the reduction.
*
* @param reducer The reducer function to apply to the elements of the iterator.
* @param initialValue The initial value of the accumulator.
*
* @returns The final result of the reduction.
*/
public reduce<A>(reducer: KeyedReducer<K, T, A>, initialValue: A): A;
public reduce<A>(reducer: KeyedReducer<K, T, A>, initialValue?: A): A
{
let index = 0;
let accumulator = initialValue;
if (accumulator === undefined)
{
const result = this._elements.next();
if (result.done) { throw new ValueException("Cannot reduce an empty iterator without an initial value."); }
accumulator = (result.value[1] as unknown) as A;
index += 1;
}
for (const [key, element] of this._elements)
{
accumulator = reducer(key, accumulator, element, index);
index += 1;
}
return accumulator;
}
/**
* Flattens the elements of the reduced iterator using a given transformation function.
*
* This method will iterate over all the elements of the iterator applying the transformation function.
* The result of each transformation will be flattened into the new iterator.
*
* Since the iterator is lazy, the flattening process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator.concat([value]), () => [])
* .flatMap((key, value) => value);
*
* console.log(results.toObject()); // { odd: [-3, -1, 3, 5], even: [0, 2, 6, 8] }
* ```
*
* ---
*
* @template V The type of the elements after the transformation.
*
* @param iteratee The transformation function to apply to each element of the iterator.
*
* @returns A new {@link AggregatedIterator} containing the flattened elements.
*/
public flatMap<V>(iteratee: KeyedIteratee<K, T, V | readonly V[]>): AggregatedIterator<K, V>
{
const elements = this._elements.enumerate();
return new AggregatedIterator(function* ()
{
for (const [index, [key, element]] of elements)
{
const values = iteratee(key, element, index);
if (values instanceof Array)
{
for (const value of values) { yield [key, value]; }
}
else { yield [key, values]; }
}
});
}
/**
* Drops a given number of elements at the beginning of the reduced iterator.
* The remaining elements will be included in the new iterator.
* See also {@link ReducedIterator.take}.
*
* Since the iterator is lazy, the dropping process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* Only the dropped elements will be consumed in the process.
* The rest of the iterator will be consumed once the new iterator is.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator.concat(value), () => [])
* .drop(1);
*
* console.log(results.toObject()); // { even: [0, 2, 6, 8] }
* ```
*
* ---
*
* @param count The number of elements to drop.
*
* @returns A new {@link ReducedIterator} containing the remaining elements.
*/
public drop(count: number): ReducedIterator<K, T>
{
const elements = this._elements.enumerate();
return new ReducedIterator(function* ()
{
for (const [index, [key, element]] of elements)
{
if (index >= count) { yield [key, element]; }
}
});
}
/**
* Takes a given number of elements at the beginning of the reduced iterator.
* The elements will be included in the new iterator.
* See also {@link ReducedIterator.drop}.
*
* Since the iterator is lazy, the taking process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* Only the taken elements will be consumed from the original reduced iterator.
* The rest of the original reduced iterator will be available for further consumption.
*
* ---
*
* @example
* ```ts
* const reduced = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator.concat(value), () => []);
*
* const results = iterator.take(1);
*
* console.log(results.toObject()); // { odd: [-3, -1, 3, 5] }
* console.log(reduced.toObject()); // { even: [0, 2, 6, 8] }
* ```
*
* ---
*
* @param limit The number of elements to take.
*
* @returns A new {@link ReducedIterator} containing the taken elements.
*/
public take(limit: number): ReducedIterator<K, T>
{
const elements = this._elements.enumerate();
return new ReducedIterator(function* ()
{
for (const [index, [key, element]] of elements)
{
if (index >= limit) { break; }
yield [key, element];
}
});
}
/**
* Finds the first element of the reduced iterator that satisfies the given condition.
*
* This method will iterate over all the elements of the iterator checking if they satisfy the condition.
* The first element that satisfies the condition will be returned immediately.
*
* Only the elements that are necessary to find the first
* satisfying one will be consumed from the original iterator.
* The rest of the iterator will be available for further consumption.
*
* Also note that:
* - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
* - If the iterator is infinite and no element satisfies the condition, the method will never return.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -3, -1, 0, 1, 2, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .find((key, value) => value > 0);
*
* console.log(results); // 16
* ```
*
* ---
*
* @param predicate The condition to check for each element of the iterator.
*
* @returns The first element that satisfies the condition, `undefined` otherwise.
*/
public find(predicate: KeyedIteratee<K, T, boolean>): T | undefined;
/**
* Finds the first element of the reduced iterator that satisfies the given type guard predicate.
*
* This method will iterate over all the elements of the iterator checking if they satisfy the condition.
* The first element that satisfies the condition will be returned immediately.
*
* Only the elements that are necessary to find the first
* satisfying one will be consumed from the original iterator.
* The rest of the iterator will be available for further consumption.
*
* Also note that:
* - If no element satisfies the condition, `undefined` will be returned once the entire iterator is consumed.
* - If the iterator is infinite and no element satisfies the condition, the method will never return.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number | string>(["-3", -3, "-1", 0, 1, 2, "5", 6, 8])
* .groupBy((value) => Number(value) % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .find<number>((key, value) => typeof value === "number");
*
* console.log(results); // 16
* ```
*
* ---
*
* @template S
* The type of the elements that satisfy the condition.
* This allows the type-system to infer the correct type of the result.
*
* It must be a subtype of the original type of the elements.
*
* @param predicate The type guard condition to check for each element of the iterator.
*
* @returns The first element that satisfies the condition, `undefined` otherwise.
*/
public find<S extends T>(predicate: KeyedTypeGuardPredicate<K, T, S>): S | undefined;
public find(predicate: KeyedIteratee<K, T, boolean>): T | undefined
{
for (const [index, [key, element]] of this._elements.enumerate())
{
if (predicate(key, element, index)) { return element; }
}
return undefined;
}
/**
* Enumerates the elements of the reduced iterator.
* Each element is paired with its index in a new iterator.
*
* Since the iterator is lazy, the enumeration process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const results = new ReducedIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .enumerate();
*
* console.log(results.toObject()); // [[0, 4], [1, 16]]
* ```
*
* ---
*
* @returns A new {@link ReducedIterator} object containing the enumerated elements.
*/
public enumerate(): ReducedIterator<K, [number, T]>
{
return this.map((_, element, index) => [index, element]);
}
/**
* Removes all duplicate elements from the reduced iterator.
* The first occurrence of each element will be kept.
*
* Since the iterator is lazy, the deduplication process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const results = new ReducedIterator<number>([-3, -1, 0, 2, 3, 6, -3, -1, 1, 5, 6, 8, 7, 2])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .map((key, value) => Math.abs(value))
* .reduce((key, accumulator, value) => accumulator + value)
* .unique();
*
* console.log(results.toObject()); // { odd: 24 }
*
* @returns A new {@link ReducedIterator} containing only the unique elements.
*/
public unique(): ReducedIterator<K, T>
{
const elements = this._elements;
return new ReducedIterator(function* ()
{
const values = new Set<T>();
for (const [key, element] of elements)
{
if (values.has(element)) { continue; }
values.add(element);
yield [key, element];
}
});
}
/**
* Counts the number of elements in the reduced iterator.
* This method will consume the entire iterator in the process.
*
* If the iterator is infinite, the method will never return.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .count();
*
* console.log(results); // 2
* ```
*
* ---
*
* @returns The number of elements in the iterator.
*/
public count(): number
{
let index = 0;
for (const _ of this._elements) { index += 1; }
return index;
}
/**
* Iterates over all elements of the reduced iterator.
* The elements are passed to the function along with their key and index.
*
* This method will consume the entire iterator in the process.
* If the iterator is infinite, the method will never return.
*
* ---
*
* @example
* ```ts
* const reduced = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value);
*
* reduced.forEach((key, value, index) =>
* {
* console.log(`#${index} - ${key}: ${value}`); // "#0 - odd: 4", "#1 - even: 16"
* });
* ```
*
* ---
*
* @param iteratee The function to apply to each element of the reduced iterator.
*/
public forEach(iteratee: KeyedIteratee<K, T>): void
{
for (const [index, [key, element]] of this._elements.enumerate())
{
iteratee(key, element, index);
}
}
/**
* Reaggregates the elements of the reduced iterator.
* The elements are grouped by a new key computed by the given iteratee function.
*
* Since the iterator is lazy, the reorganizing process will
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const results = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, -6, -8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .reorganizeBy((key, value) => value > 0 ? "positive" : "negative");
*
* console.log(results.toObject()); // { positive: 4, negative: -12 }
* ```
*
* ---
*
* @template J The type of the new keys used to group the elements.
*
* @param iteratee The function to determine the new key of each element of the iterator.
*
* @returns A new {@link AggregatedIterator} containing the elements reorganized by the new keys.
*/
public reorganizeBy<J extends PropertyKey>(iteratee: KeyedIteratee<K, T, J>): AggregatedIterator<J, T>
{
const elements = this._elements.enumerate();
return new AggregatedIterator(function* ()
{
for (const [index, [key, element]] of elements)
{
yield [iteratee(key, element, index), element];
}
});
}
/**
* An utility method that returns a new {@link SmartIterator}
* object containing all the keys of the iterator.
*
* Since the iterator is lazy, the keys will be extracted
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const keys = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .keys();
*
* console.log(keys.toArray()); // ["odd", "even"]
* ```
*
* ---
*
* @returns A new {@link SmartIterator} containing all the keys of the iterator.
*/
public keys(): SmartIterator<K>
{
const elements = this._elements;
return new SmartIterator<K>(function* ()
{
for (const [key] of elements)
{
yield key;
}
});
}
/**
* An utility method that returns a new {@link SmartIterator}
* object containing all the entries of the iterator.
* Each entry is a tuple containing the key and the element.
*
* Since the iterator is lazy, the entries will be extracted
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const entries = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .entries();
*
* console.log(entries.toArray()); // [["odd", 4], ["even", 16]]
* ```
*
* ---
*
* @returns A new {@link SmartIterator} containing all the entries of the iterator.
*/
public entries(): SmartIterator<[K, T]>
{
return this._elements;
}
/**
* An utility method that returns a new {@link SmartIterator}
* object containing all the values of the iterator.
*
* Since the iterator is lazy, the values will be extracted
* be executed once the resulting iterator is materialized.
*
* A new iterator will be created, holding the reference to the original one.
* This means that the original iterator won't be consumed until the
* new one is and that consuming one of them will consume the other as well.
*
* ---
*
* @example
* ```ts
* const values = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value)
* .values();
*
* console.log(values.toArray()); // [4, 16]
* ```
*
* ---
*
* @returns A new {@link SmartIterator} containing all the values of the iterator.
*/
public values(): SmartIterator<T>
{
const elements = this._elements;
return new SmartIterator<T>(function* ()
{
for (const [_, element] of elements)
{
yield element;
}
});
}
/**
* Materializes the iterator into an array.
* This method will consume the entire iterator in the process.
*
* If the iterator is infinite, the method will never return.
*
* ---
*
* @example
* ```ts
* const reduced = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value);
*
* console.log(reduced.toArray()); // [4, 16]
* ```
*
* ---
*
* @returns The {@link Array} containing all elements of the iterator.
*/
public toArray(): T[]
{
return Array.from(this.values());
}
/**
* Materializes the iterator into a map.
* This method will consume the entire iterator in the process.
*
* If the iterator is infinite, the method will never return.
*
* ---
*
* @example
* ```ts
* const reduced = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value);
*
* console.log(reduced.toMap()); // Map(2) { "odd" => 4, "even" => 16 }
* ```
*
* ---
*
* @returns The {@link Map} containing all elements of the iterator.
*/
public toMap(): Map<K, T>
{
return new Map(this.entries());
}
/**
* Materializes the iterator into an object.
* This method will consume the entire iterator in the process.
*
* If the iterator is infinite, the method will never return.
*
* ---
*
* @example
* ```ts
* const reduced = new SmartIterator<number>([-3, -1, 0, 2, 3, 5, 6, 8])
* .groupBy((value) => value % 2 === 0 ? "even" : "odd")
* .reduce((key, accumulator, value) => accumulator + value);
*
* console.log(reduced.toObject()); // { odd: 4, even: 16 }
* ```
*
* ---
*
* @returns The {@link Object} containing all elements of the iterator.
*/
public toObject(): Record<K, T>
{
return Object.fromEntries(this.entries()) as Record<K, T>;
}
public readonly [Symbol.toStringTag]: string = "ReducedIterator";
}