UNPKG

iterator-helper

Version:

Provide helpers that polyfill all methods defined in [iterator helpers proposal](https://github.com/tc39/proposal-iterator-helpers), both for `Iterator` and `AsyncIterator`, and even more.

555 lines (554 loc) 19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HIterator = void 0; const async_iterator_1 = require("../async-iterator"); const types_1 = require("../types"); class HIterator { constructor(iterator) { this[types_1.IteratorSlot] = iterator !== null && iterator !== void 0 ? iterator : this; } static from(iterator) { if (Symbol.iterator in iterator) { return new HIterator(iterator[Symbol.iterator]()); } return new HIterator(iterator); } next(val) { return this[types_1.IteratorSlot].next(val); } throw(val) { var _a, _b, _c; return (_c = (_b = (_a = this[types_1.IteratorSlot]).throw) === null || _b === void 0 ? void 0 : _b.call(_a, val)) !== null && _c !== void 0 ? _c : { value: undefined, done: true }; } return(val) { var _a, _b, _c; return (_c = (_b = (_a = this[types_1.IteratorSlot]).return) === null || _b === void 0 ? void 0 : _b.call(_a, val)) !== null && _c !== void 0 ? _c : { value: undefined, done: true }; } /** Map each value of iterator to another value via {callback}. */ map(callback) { return new HIterator(HIterator.map.call(this, callback)); } static *map(callback) { const it = this; let value = it.next(); while (!value.done) { const real_value = callback(value.value); const next_value = yield real_value; value = it.next(next_value); } return value.value; } /** Each value is given through {callback}, return `true` if value is needed into returned iterator. */ filter(callback) { return new HIterator(HIterator.filter.call(this, callback)); } static *filter(callback) { const it = this; let value = it.next(); let next_value; while (!value.done) { const real_value = value.value; if (callback(real_value)) { next_value = yield real_value; value = it.next(next_value); } else { value = it.next(next_value); } } return value.value; } /** Find a specific value that returns `true` in {callback}, and return it. Returns `undefined` otherwise. */ find(callback) { const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; if (callback(real_value)) return real_value; value = it.next(); } } /** Return `true` if each value of iterator validate {callback}. */ every(callback) { const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; if (!callback(real_value)) return false; value = it.next(); } return true; } /** Return `true` if one value of iterator validate {callback}. */ some(callback) { const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; if (callback(real_value)) return true; value = it.next(); } return false; } /** Consume iterator and collapse values inside an array. */ toArray(max_count = Infinity) { const values = []; const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; if (max_count <= 0) return values; values.push(real_value); if (max_count !== Infinity) max_count--; value = it.next(); } return values; } /** Create a new iterator that consume {limit} items, then stops. */ take(limit) { limit = Number(limit); if (limit < 0) throw new RangeError('Invalid limit.'); return new HIterator(HIterator.take.call(this, limit)); } static *take(limit) { const it = this; let value = it.next(); let remaining = limit; let next_value; while (!value.done) { const real_value = value.value; if (remaining <= 0) return; next_value = yield real_value; value = it.next(next_value); remaining--; } return value.value; } /** Create a new iterator that skip {limit} items from source iterator, then yield all values. */ drop(limit) { limit = Number(limit); if (limit < 0) throw new RangeError('Invalid limit.'); return new HIterator(HIterator.drop.call(this, limit)); } static *drop(limit) { const it = this; let value = it.next(); let remaining = limit; let next_value; while (!value.done) { const real_value = value.value; if (remaining > 0) { value = it.next(next_value); remaining--; continue; } next_value = yield real_value; value = it.next(next_value); } return value.value; } /** Get a pair [index, value] for each remaining value of iterable. */ asIndexedPairs() { return new HIterator(HIterator.asIndexedPairs.call(this)); } static *asIndexedPairs() { const it = this; let value = it.next(); let index = 0; while (!value.done) { const real_value = value.value; const next_value = yield [index, real_value]; value = it.next(next_value); index++; } return value.value; } /** Like map, but you can return a new iterator that will be flattened. */ flatMap(mapper) { if (typeof mapper !== 'function') { throw new TypeError('Mapper must be a function.'); } return new HIterator(HIterator.flatMap.call(this, mapper)); } static *flatMap(mapper) { const it = this; let value = it.next(); let next_value; while (!value.done) { const real_value = value.value; const mapped = mapper(real_value); if (Symbol.iterator in mapped) { // @ts-ignore next_value = yield* mapped[Symbol.iterator](); } else { // @ts-ignore next_value = yield mapped; } value = it.next(next_value); } return value.value; } /** Accumulate each item inside **acc** for each value **value**. */ reduce(reducer, initial_value) { let acc = initial_value; const it = this; if (acc === undefined) { acc = it.next().value; } let value = it.next(); while (!value.done) { const real_value = value.value; acc = reducer(acc, real_value); value = it.next(); } return acc; } /** Iterate over each value of iterator by calling **callback** for each value. */ forEach(callback) { const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; callback(real_value); value = it.next(); } } /** End the iterator and return the number of remaining items. */ count() { let count = 0; const it = this; let value = it.next(); while (!value.done) { count++; value = it.next(); } return count; } /** Join all the remaining elements of the iterator in a single string with glue {glue}. */ join(string) { let final = ''; let first = true; const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; if (first) { first = false; final += real_value; } else { final += string + real_value; } value = it.next(); } return final; } /** Iterate through current iterator, then through the given iterators in the correct order. */ chain(...iterables) { return new HIterator(HIterator.chain.apply(this, iterables)); } static *chain(...iterables) { yield* this; for (const it of iterables) { if ('next' in it) { yield* it; } else { // If its not an iterable, make it one yield* HIterator.from(it); } } } zip(...others) { return new HIterator(HIterator.zip.apply(this, others)); } static *zip(...others) { const iterators = [this, ...others] .map((e) => Symbol.iterator in e ? e[Symbol.iterator]() : e); let values = iterators.map(e => e.next()); let nextValue; while (values.every(e => !e.done)) { nextValue = yield values.map(e => e.value); values = iterators.map(e => e.next(nextValue)); } } /** Continue iterator until {callback} return a falsy value. */ takeWhile(callback) { return new HIterator(HIterator.takeWhile.call(this, callback)); } static *takeWhile(callback) { const it = this; let value = it.next(); let next_value; while (!value.done) { const real_value = value.value; if (callback(real_value)) next_value = yield real_value; else return; value = it.next(next_value); } return value.value; } /** Skip elements until {callback} return a truthy value. */ dropWhile(callback) { return new HIterator(HIterator.dropWhile.call(this, callback)); } static *dropWhile(callback) { const it = this; let value = it.next(); let next_value; let finished = false; while (!value.done) { const real_value = value.value; if (!finished && callback(real_value)) { value = it.next(next_value); continue; } finished = true; next_value = yield real_value; value = it.next(next_value); } return value.value; } /** Continue iterator until `null` or `undefined` is encountered. */ fuse() { return new HIterator(HIterator.fuse.call(this)); } static *fuse() { const it = this; let value = it.next(); let next_value; while (!value.done) { const real_value = value.value; if (real_value !== undefined && real_value !== null) next_value = yield real_value; else return; value = it.next(next_value); } return value.value; } /** Partition {true} elements to first array, {false} elements to second one. */ partition(callback) { const partition1 = [], partition2 = []; const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; if (callback(real_value)) partition1.push(real_value); else partition2.push(real_value); value = it.next(); } return [partition1, partition2]; } /** Group by objects by key according to returned key for each object. */ groupBy(callback) { const grouped = {}; const it = this; let value = it.next(); while (!value.done) { const realValue = value.value; const key = callback(realValue); if (key in grouped) { grouped[key].push(realValue); } else { grouped[key] = [realValue]; } value = it.next(); } return grouped; } /** Index this iterator objects in a {Map} with key obtained through {keyGetter}. */ toIndexedItems(keyGetter) { const map = new Map(); const it = this; let value = it.next(); while (!value.done) { const realValue = value.value; const key = keyGetter(realValue); map.set(key, realValue); value = it.next(); } return map; } /** * Iterate over items present in both current collection and {otherItems} iterable. * **Warning**: This is a O(n*m) operation and this will consume {otherItems} iterator/iterable! */ intersection(otherItems, isSameItemCallback = Object.is) { return new HIterator(HIterator.intersection.call(this, otherItems, isSameItemCallback)); } static *intersection(otherItems, isSameItemCallback = Object.is) { const otherItemsCollection = HIterator.from(otherItems).toArray(); const it = this; let value = it.next(); let nextValue; while (!value.done) { const realValue = value.value; // Yield realValue if any {item} match {realValue} if (otherItemsCollection.some(item => isSameItemCallback(realValue, item))) { nextValue = yield realValue; } value = it.next(nextValue); } return value.value; } /** * Iterate over items present only in current collection, not in {otherItems} iterable. * **Warning**: This is a O(n*m) operation and this will consume {otherItems} iterator/iterable! */ difference(otherItems, isSameItemCallback = Object.is) { return new HIterator(HIterator.difference.call(this, otherItems, isSameItemCallback)); } static *difference(otherItems, isSameItemCallback = Object.is) { const it = this; let value = it.next(); const otherItemsCollection = HIterator.from(otherItems).toArray(); let nextValue; while (!value.done) { const realValue = value.value; // Exclude real_value if any {item} match {realValue} if (otherItemsCollection.every(item => !isSameItemCallback(realValue, item))) { nextValue = yield realValue; } value = it.next(nextValue); } return value.value; } /** * Iterate over items present only in current collection or only in {otherItems} iterable, but not in both. * **Warning**: This is a O(n*m) operation and this will consume {otherItems} iterator/iterable! */ symmetricDifference(otherItems, isSameItemCallback = Object.is) { return new HIterator(HIterator.symmetricDifference.call(this, otherItems, isSameItemCallback)); } static *symmetricDifference(otherItems, isSameItemCallback = Object.is) { const it = this; let value = it.next(); const otherItemsCollection = HIterator.from(otherItems).toArray(); const presentInBothCollections = new Set(); let nextValue; while (!value.done) { const realValue = value.value; // Try to find same item as current in {other_items_collection} const otherItemIndex = otherItemsCollection.findIndex(item => isSameItemCallback(realValue, item)); if (otherItemIndex !== -1) { presentInBothCollections.add(otherItemsCollection[otherItemIndex]); } else { // No match in other collection, can emit it nextValue = yield realValue; } value = it.next(nextValue); } for (const item of otherItemsCollection) { // Do not emit if {item} is seen in present in both collection items if (presentInBothCollections.has(item)) { continue; } yield item; } return value.value; } /** Find the iterator index of the first element that returns a truthy value, -1 otherwise. */ findIndex(callback) { const it = this; let i = 0; let value = it.next(); while (!value.done) { const real_value = value.value; if (callback(real_value)) return i; value = it.next(); i++; } return -1; } /** Only works if it is a number iterator. Returns the maximum of iterator. */ max() { var _a; let max = -Infinity; const it = this; let value = it.next(); while (!value.done) { const real_value = Number((_a = value.value) !== null && _a !== void 0 ? _a : 0); if (isNaN(real_value)) { throw new RangeError('Iterator should return numbers only, or null or undefined.'); } if (max < real_value) max = real_value; value = it.next(); } return max; } /** Only works if it is a number iterator. Returns the minimum of iterator. */ min() { var _a; let min = Infinity; const it = this; let value = it.next(); while (!value.done) { const real_value = Number((_a = value.value) !== null && _a !== void 0 ? _a : 0); if (isNaN(real_value)) { throw new RangeError('Iterator should return numbers only, or null or undefined.'); } if (min > real_value) min = real_value; value = it.next(); } return min; } /** When iterator ends, go back to the first item then loop. Indefinitively. */ cycle() { return new HIterator(HIterator.cycle.call(this)); } static *cycle() { const values = []; const it = this; let value = it.next(); while (!value.done) { const real_value = value.value; values.push(real_value); const next_value = yield real_value; value = it.next(next_value); } while (true) { yield* values; } } /** Convert current iterator to a wrapped async iterator. */ toAsyncIterator() { return new async_iterator_1.HAsyncIterator(HIterator.toAsyncIterator.call(null, this)); } /** Convert given iterator to a async generator instance. */ static async *toAsyncIterator(iterator) { const it = HIterator.from(iterator); let value = it.next(); let nextValue; while (!value.done) { const realValue = value.value; nextValue = yield Promise.resolve(realValue); value = it.next(nextValue); } return value.value; } [Symbol.iterator]() { return this; } } exports.HIterator = HIterator;