UNPKG

@drew887121/iteratorfuncs

Version:

A small package to make working with iterators easier

251 lines (239 loc) 9.58 kB
/** * IteratorFuncs a simple single file package to make working with JS iterators easier. * * The documentation lives online [here](https://drew887.github.io/iteratorFuncs/modules.html). * * If you've ever had a generator, or a Set, or a Map that you've needed to map/filter/reduce over in JS, you've been hit * by the pain of having to either convert to an Array with `Array.from` and then use the method you need, or write a * for...of loop and do your logic in there. * * While the for...of loop is almost certainly the "proper" or "js way" of doing things, and is what you normally get as * an answer when searching for this, it can be a little disappointing or jarring for those of us wanting a more fp like * approach that js normally lets you take. * * So this package is simply to help facilitate these patterns for IterableIterators. * * tldr: ```typescript const set = new Set([1, 2, 3, 4, 5]); const mappedAndFiltered = mapIterator(set, (item) => item * 2) .filter((item) => item > 5); console.log(Array.from(mappedAndFiltered)); // logs out [6, 8, 10] ``` * * @packageDocumentation */ // ============= Non Greedy Versions ================ /** * An AugmentedIterator represents one of the IterableIterators we return. * This abstract class is exported only to provide type/intellisense information, * and should be only implemented internally. */ export class AugmentedIterator { [Symbol.iterator]() { return this; } map(mapper) { return mapIterator(this, mapper); } filter(filter) { return filterIterator(this, filter); } reduce(reducer, initial) { return reduceIterator(this, reducer, initial); } } /** * This is a private class for encapsulating the logic for reducing. */ class _ReducingIterator extends AugmentedIterator { constructor(iterable, reducer, state) { super(); this.reducer = reducer; this.state = state; this.iterable = iterable[Symbol.iterator](); } next(...args) { const result = this.iterable.next(...args); if (!result.done) { this.state = this.reducer(this.state, result.value); return Object.assign(Object.assign({}, result), { value: this.state }); } return result; } } /** * This is a private class for encapsulating the logic for mapping. */ class _MappingIterator extends AugmentedIterator { constructor(iterable, mapper) { super(); this.mapper = mapper; this.iterable = iterable[Symbol.iterator](); } next(...args) { const result = this.iterable.next(...args); if (!result.done) { return Object.assign(Object.assign({}, result), { value: this.mapper(result.value) }); } return result; } } /** * This is a private class for encapsulating the logic for filtering. */ class _FilteringIterator extends AugmentedIterator { constructor(iterable, filterer) { super(); this.filterer = filterer; this.iterable = iterable[Symbol.iterator](); } next(...args) { let result = this.iterable.next(...args); while (!result.done && !this.filterer(result.value)) { result = this.iterable.next(...args); } return result; } } /** * Given an Iterable or IterableIterator, a reducer, and an initial value, return a new IterableIterator that yields * values that are automatically piped through said reducer. * @param {Iterable} iterator - The iterator you wish to reduce over * @param {Function} reducer - A function to use to reduce the elements iterator produces * @param initial The initial value to pass to reducer with the first element * @typeParam TValue - The value returned from the iterator * @typeParam TReducerReturn - The return type of your reducer function * @typeParam TReturn - If your iterator has a return type it must be the same as TIteratorValue, otherwise undefined * @typeParam TNext - The type your iterator is expecting as what's passed to its next function. For generators this is the type returned after a yield. * * @example const set: Set<number> = new Set([1, 2, 3, 4, 5]); const summingIterator = reduceIterator( set.values(), (carry: number, item: number): number => { return carry + item; }, 0, ); // summingIterator will now yield the sum for each value seen thus far const finalValues = Array.from(summingIterator); // finalValues will now be [1,3,6,10,15]; */ export function reduceIterator(iterator, reducer, initial) { return new _ReducingIterator(iterator, reducer, initial); } /** * Given an IteratorArg and a mapping function, returns a new IterableIterator that yields values from the initial iterator * but passed through the mapping function. * @param {Iterable} iterator - The iterator you wish to map elements from * @param {Function} mapper - A function to use to map the elements iterator produces * @typeParam TValue - The value returned from the iterator * @typeParam TMapperReturn - The return type of your reducer function * @typeParam TReturn - If your iterator has a return type it must be the same as TIteratorValue, otherwise undefined * @typeParam TNext - The type your iterator is expecting as what's passed to its next function. For generators this is the type returned after a yield. * @example const set = new Set([1, 2, 3, 4, 5]); const mapped = mapIterator(set, (item) => item * 2); console.log(Array.from(mapped)); // logs out [2, 4, 6, 8, 10] */ export function mapIterator(iterator, mapper) { // Because we can't guarantee a 0 value for the types, we can't just fall back to reduce. return new _MappingIterator(iterator, mapper); } /** * Given an IteratorArg and a filtering function it will return a new IterableIterator that when polled will eagerly pull * values from iterator and pass them to filterer until filterer returns true for an item, or we reach the end of iterator; * it will then yield this value itself. * * **NOTE:** if iterator relies on values being passed to next, the returned IterableIterator will re-use the same next * until a value passes the filterer. * value until an item is raised that passes the filterer. This can lead to strange behaviours. * @param {Iterable} iterator - The iterator you wish to map elements from * @param {Function} filterer - Only values for which this function returns true are yielded. * @typeParam TValue - The value returned from the iterator * @typeParam TReturn - If your iterator has a return type it must be the same as TIteratorValue, otherwise undefined * @typeParam TNext - The type your iterator is expecting as what's passed to its next function. For generators this is the type returned after a yield. * * @example const set = new Set([1, 2, 3, 4, 5]); const filtered = filterIterator(set, (item) => item % 2 === 0); console.log(Array.from(filtered)); // logs out [2, 4] */ export function filterIterator(iterator, filterer) { return new _FilteringIterator(iterator, filterer); } // ============= Greedy Versions ================ /** * Given an iterator, eagerly reduce over its results until it finishes and return the result * @param {Iterable} iterator - The iterator you wish to reduce over * @param {Function} reducer - A function to use to reduce the elements iterator produces * @param initial The initial value to pass to reducer with the first element. Also returned if iterator never yields a value * @typeParam TValue - The type of values that iterator will yield and that initial must be * @typeParam TReturn - The type of values that reducer will return * * @example const set: Set<number> = new Set([1, 2, 3, 4, 5]); const sum = eagerlyReduceIterator( set.values(), (carry: number, item: number): number => { return carry + item; }, 0, ); // sum is now 15 */ export function eagerlyReduceIterator(iterator, reducer, initial) { let result = initial; for (const a of iterator) { result = reducer(result, a); } return result; } /** * Eagerly Map over an iterator and return an array of results * @param {Iterable} iterator - The iterator you wish to map over * @param {Function} mapper - The function you want to use to process any elements yielded from iterator * @typeParam TValue - The type of values that iterator will yield * @typeParam TReturn - The type of values that mapper will return * * @example const set: Set<number> = new Set([1, 2, 3, 4, 5]); const squaredValues = mapIteratorToArray( set.values(), (item: number): number => { return item * item; }, ); */ export function mapIteratorToArray(iterator, mapper) { return eagerlyReduceIterator(iterator, (carry, item) => { carry.push(mapper(item)); return carry; }, []); } /** * Eagerly Filter over an iterator and return an array of results * @param {Iterable} iterator - The iterator you wish to filter over * @param {Function} filter - Your filtering function * @typeParam TValue - The type of values that iterator will yield * * @example const set: Set<number> = new Set([1, 2, 3, 4, 5]); const evenValues = filterIteratorToArray( set.values(), (item: number): boolean => { return item % 2 === 0; }, ); // evenValues = [2, 4]; */ export function filterIteratorToArray(iterator, filter) { return eagerlyReduceIterator(iterator, (carry, item) => { if (filter(item)) { carry.push(item); } return carry; }, []); }