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
JavaScript
"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;