UNPKG

doddle

Version:

Tiny yet feature-packed (async) iteration toolkit.

1,049 lines 39.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ASeqOperator = exports.ASeq = void 0; const index_js_1 = require("../doddle/index.js"); const error_js_1 = require("../errors/error.js"); const utils_js_1 = require("../utils.js"); const aseq_ctor_js_1 = require("./aseq.ctor.js"); const seq_ctor_js_1 = require("./seq.ctor.js"); const SPECIAL = Symbol("special"); /** * The ASeq class, which wraps an async iterable. * * @category Use */ class ASeq { /** @internal */ constructor() { /** {@link concatMap} */ this.flatMap = this.concatMap; // Class name is used for various checks // Need to make sure it's accessible even while minified (0, error_js_1.loadCheckers)(ASeq.prototype); } /** @internal */ get [Symbol.toStringTag]() { return "ASeq"; } /** @internal */ get _qr() { return this.toArray().pull(); } /** * Calls a side-effect function after all elements have been yielded, but before iteration * finishes. * * ⚠️ If the client stops iterating early, the function won't be called. * * @param action A function to invoke after iteration completes. * @returns A new sequence that acts like `this` but invokes `action` before it finishes. */ after(action) { (0, error_js_1.chk)(this.after).action(action); return (0, exports.ASeqOperator)(this, async function* after(input) { yield* input; await (0, index_js_1.pull)(action()); }); } /** * Reinterprets the declared element type of `this` as another, arbitrary type. * * ℹ️ This is only useful in TypeScript and has no runtime effects. * * @template S The new element type. * @returns The same sequence, but with a different declared type. */ as() { return this; } /** * 🦥**Lazily** gets the element at the given index in `this` sequence, or undefined if the * index is out of bounds. * * ℹ️ Negative indexes count from the end of the sequence.\ * ⚠️ Requires iterating over the sequence up to the given index. * * @param index The index of the item to retrieve. * @returns A 🦥{@link DoddleAsync} that resolves to the item at the given index. */ at(index) { (0, error_js_1.chk)(this.at).index(index); return (0, index_js_1.lazyOperator)(this, async function at(input) { if (index < 0) { return input.take(index).first().pull(); } return input.skip(index).first().pull(); }); } /** * Executes a side effect action once before any elements are yielded, but after iteration has * begun. * * @param action Invokes before any elements are yielded. * @returns A new async sequence that performs `action` before yielding elements. */ before(action) { (0, error_js_1.chk)(this.before).action(action); return (0, exports.ASeqOperator)(this, async function* before(input) { await (0, index_js_1.pull)(action()); yield* input; }); } /** * Caches the elements of `this` sequence as they're iterated over, so that it's evaluated only * once. * * @returns A new sequence with the same elements as the original sequence. */ cache() { const self = this; const _cache = []; let alreadyDone = false; let iterator; let pending; return (0, exports.ASeqOperator)(this, async function* cache() { let i = 0; for (;;) { if (i < _cache.length) { const cur = _cache[i]; yield cur; i++; } else if (!alreadyDone) { iterator ??= (0, utils_js_1._aiter)(self); if (!pending) { pending = (async () => { const { done, value } = await iterator.next(); if (done) { alreadyDone = true; return; } _cache.push(value); pending = undefined; return; })(); } await pending; } else { return; } } }); } catch(handler) { (0, error_js_1.chk)(this.catch).handler(handler); return (0, exports.ASeqOperator)(this, async function* catch_(input) { let i = 0; const iterator = (0, utils_js_1._aiter)(input); for (;;) { try { const result = await iterator.next(); var value = result.value; if (result.done) { return; } yield value; } catch (err) { const error = err; const result = await (0, index_js_1.pull)(handler(error, i)); if (!result) { return; } const pulled = (0, index_js_1.pull)(result); yield* (0, aseq_ctor_js_1.aseq)(pulled); return; } i++; } }); } /** * Splits `this` async sequence into chunks of the given size, optionally applying a projection * to each chunk. * * @param size The size of each chunk. The last chunk may be smaller. * @param projection Optionally, an N-ary projection to apply to each chunk. * @returns A new async sequence of chunks, each containing consecutive elements from the * original. */ chunk(size, projection) { const c = (0, error_js_1.chk)(this.chunk); c.size(size); projection ??= (...chunk) => chunk; c.projection(projection); return (0, exports.ASeqOperator)(this, async function* chunk(input) { let chunk = []; for await (const item of input) { chunk.push(item); if (chunk.length === size) { yield (0, index_js_1.pull)(projection(...chunk)); chunk = []; } } if (chunk.length) { yield (0, index_js_1.pull)(projection(...chunk)); } }); } /** * Returns a new sequence. When iterated, before yielding its first element, it will iterate * over all the elements of `this` and store them in memory. Then it will yield all of them one * by one. * * ℹ️ Used to control side-effects. Makes sure all side-effects execute before continuing to * apply other operators. * * @returns A new sequence with the same elements as this one, but where iteration has already * completed. */ collect() { return (0, exports.ASeqOperator)(this, async function* collect(input) { const everything = []; for await (const element of input) { everything.push(element); } yield* everything; }); } /** * Concatenates one or more sequences to the end of `this`, so that their elements appear in * order. * * @param _inputs The sequential inputs to concatenate to the end of `this`. * @returns A new sequence with the concatenated elements. */ concat(..._inputs) { const inputs = _inputs.map(aseq_ctor_js_1.aseq); return (0, exports.ASeqOperator)(this, async function* concat(input) { for await (const element of input) { yield element; } for (const iterable of inputs) { for await (const element of iterable) { yield element; } } }); } /** * Applies a sequence projection on each element of `this` sequence and concatenates the * resulting sequences. * * @param projection The sequence projection to apply to each element. * @returns A new sequence with the flattened results. */ concatMap(projection) { (0, error_js_1.chk)(this.concatMap).projection(projection); return (0, exports.ASeqOperator)(this, async function* concatMap(input) { let index = 0; for await (const element of input) { for await (const projected of (0, aseq_ctor_js_1.aseq)(await (0, index_js_1.pull)(projection(element, index++)))) { yield (0, index_js_1.pull)(projected); } } }); } /** * Concatenates `this` sequence to the end of one or more other sequences. * * ℹ️ Input sequences are concatenated in the order that they appear. * * @param inputs One or more other sequences. * @returns A new sequence with the concatenated elements. * @see {@link ASeq.concat} */ concatTo(...others) { return (0, aseq_ctor_js_1.aseq)([]).concat(...others, this); } count(predicate) { predicate ??= () => true; predicate = (0, error_js_1.chk)(this.count).predicate(predicate); return (0, index_js_1.lazyOperator)(this, async function count(input) { let index = 0; let count = 0; for await (const element of input) { if (await (0, index_js_1.pull)(predicate(element, index++))) { count++; } } return count; }); } /** * Calls an action function as each element in `this` is iterated over. Calls the function * before or after yielding the element, or both. * * @param action The action function to invoke for each element. * @param stage The **stage** at which to invoke the function. Can be `"before"`, `"after"`, or * `"both"`. * @returns A new sequence that invokes the action function while being iterated. */ each(action, stage = "before") { const c = (0, error_js_1.chk)(this.each); c.action(action); c.stage(stage); const myStage = utils_js_1.orderedStages.indexOf(stage); return (0, exports.ASeqOperator)(this, async function* each(input) { let index = 0; for await (const element of input) { if (myStage & 1 /* Stage.Before */) { await (myStage & 1 /* Stage.Before */ && (0, index_js_1.pull)(action(element, index, "before"))); } yield element; if (myStage & 2 /* Stage.After */) { await (myStage & 2 /* Stage.After */ && (0, index_js_1.pull)(action(element, index, "after"))); } index++; } }); } /** * 🦥**Lazily** checks if all elements in `this` sequence match the given predicate. * * ⚠️ May iterate over the entire sequence. * * @param predicate The predicate. * @returns A 🦥{@link DoddleAsync} that yields `true` if all elements match, or `false` * otherwise. */ every(predicate) { predicate = (0, error_js_1.chk)(this.every).predicate(predicate); return (0, index_js_1.lazyOperator)(this, async function every(input) { let index = 0; for await (const element of input) { if (!(await (0, index_js_1.pull)(predicate(element, index++)))) { return false; } } return true; }); } filter(predicate) { predicate = (0, error_js_1.chk)(this.filter).predicate(predicate); return (0, exports.ASeqOperator)(this, async function* filter(input) { let index = 0; for await (const element of input) { if (await (0, index_js_1.pull)(predicate(element, index++))) { yield element; } } }); } first(predicate, alt) { predicate = predicate || (() => true); (0, error_js_1.chk)(this.first).predicate(predicate); return (0, index_js_1.lazyOperator)(this, async function first(input) { let index = 0; for await (const element of input) { if (await (0, index_js_1.pull)(predicate(element, index++))) { return element; } } return alt; }); } /** * Groups the elements of `this` sequence by key, resulting in a sequence of pairs where the * first element is the key and the second is a sequence of values. * * @param keyProjection The projection used to determine the key for each element. * @returns A sequence of pairs. */ groupBy(keyProjection) { (0, error_js_1.chk)(this.groupBy).keyProjection(keyProjection); return (0, exports.ASeqOperator)(this, async function* groupBy(input) { const map = new Map(); const keys = []; const shared = input .map(async (v) => { const key = (await (0, index_js_1.pull)(keyProjection(v))); const group = map.get(key); if (group) { group.push(v); } else { keys.push(key); map.set(key, [v]); } }) .share(); async function* getGroupIterable(key) { const group = map.get(key); for (let i = 0;; i++) { if (i < group.length) { yield group[i]; continue; } for await (const _ of shared) { if (i < group.length) break; } if (i >= group.length) return; i--; } } for (let i = 0;; i++) { if (i < keys.length) { const key = keys[i]; yield [key, (0, aseq_ctor_js_1.aseq)(() => getGroupIterable(key))]; continue; } for await (const _ of shared) { if (i < keys.length) break; } if (i >= keys.length) return; i--; } }); } includes(..._values) { const values = new Set(_values); return (0, index_js_1.lazyOperator)(this, async function includes(input) { for await (const element of input) { values.delete(element); if (values.size === 0) { return true; } } return false; }); } /** * 🦥**Lazily** joins the elements of `this` sequence into a single string, separated by the * given separator. * * ⚠️ Requires iterating over the entire sequence. * * @param separator The string to use as a separator between elements. * @returns A 🦥{@link DoddleAsync} that resolves to the joined string. */ join(separator = ",") { (0, error_js_1.chk)(this.join).separator(separator); return (0, index_js_1.lazyOperator)(this, async function join(input) { const results = []; for await (const x of input) { results.push(x); } return results.join(separator); }); } last(predicate, alt) { predicate ??= () => true; (0, error_js_1.chk)(this.last).predicate(predicate); return (0, index_js_1.lazyOperator)(this, async function last(input) { let last = alt; let index = 0; for await (const element of input) { if (await (0, index_js_1.pull)(predicate(element, index++))) { last = element; } } return last; }); } /** * Applies a projection to each element of `this` sequence. * * @param projection The projection to apply to each element. * @returns A new sequence with the projected elements. */ map(projection) { (0, error_js_1.chk)(this.map).projection(projection); return (0, exports.ASeqOperator)(this, async function* map(input) { let index = 0; for await (const element of input) { yield (0, index_js_1.pull)(projection(element, index++)); } }); } /** * 🦥**Lazily** finds the maximum element in `this` sequence by key, or the given alternative * value if the sequence is empty. * * @param projection Projects each element into a key so it can be compared. * @param alt The value to return if the sequence is empty. Defaults to `undefined`. */ maxBy(projection, alt) { (0, error_js_1.chk)(this.maxBy).projection(projection); return (0, index_js_1.lazyOperator)(this, async function maxBy(input) { let curMax = alt; let curMaxKey = undefined; let index = 0; for await (const element of input) { const curKey = (await (0, index_js_1.pull)(projection(element, index++))); if (index === 1 || curKey > curMaxKey) { curMax = element; curMaxKey = curKey; continue; } } return curMax; }); } minBy(projection, alt) { (0, error_js_1.chk)(this.minBy).projection(projection); return (0, index_js_1.lazyOperator)(this, async function minBy(input) { let curMin = alt; let curMinKey = undefined; let index = 0; for await (const element of input) { const curKey = (await (0, index_js_1.pull)(projection(element, index++))); if (index === 1 || curKey < curMinKey) { curMin = element; curMinKey = curKey; continue; } } return curMin; }); } orderBy(projection, descending = false) { const c = (0, error_js_1.chk)(this.orderBy); c.projection(projection); c.descending(descending); const compareKey = (0, utils_js_1.createCompareKey)(descending); return (0, exports.ASeqOperator)(this, async function* orderBy(input) { const kvps = []; for await (const element of input) { const key = (await (0, index_js_1.pull)(projection(element))); kvps.push([key, element]); } kvps.sort(compareKey); for (const [_, value] of kvps) { yield value; } }); } /** * Returns a cartesian product of `this` sequence with one or more other sequences, optionally * applying an N-ary projection to each combination of elements. * * The product of `N` sequences is the collection of all possible sets of elements from each * sequence. * * For example, the product of `[1, 2]` and `[3, 4]` is: * * ```ts * ;[ * [1, 3], * [1, 4], * [2, 3], * [2, 4] * ] * ``` * * @example * aseq([1, 2]).product([3, 4]) * // => [[1, 3], [1, 4], [2, 3], [2, 4]] * aseq([]).product([3, 4]) * // => [] * aseq([1, 2]).product([3, 4], (a, b) => a + b) * // => [4, 5, 5, 6] * * @param others One or more sequence-like inputs for the product. * @param projection Optionally, an N-ary projection to apply to each combination of elements. * If not given, each combination is yielded as an array. * @returns A new sequence. */ product(_others, projection) { const others = _others.map(aseq_ctor_js_1.aseq).map(x => x.cache()); projection ??= (...args) => args; (0, error_js_1.chk)(this.product).projection(projection); return (0, exports.ASeqOperator)(this, async function* product(input) { let partialProducts = [[]]; for (const iterable of [input, ...others].reverse()) { const oldPartialProducts = partialProducts; partialProducts = []; for await (const item of iterable) { partialProducts = partialProducts.concat(oldPartialProducts.map(x => [item, ...x])); } } yield* partialProducts.map(x => (0, index_js_1.pull)(projection.apply(null, x))); }); } reduce(reducer, initial) { (0, error_js_1.chk)(this.reduce).reducer(reducer); return (0, index_js_1.lazyOperator)(this, async function reduce(input) { let acc = initial ?? SPECIAL; let index = 0; for await (const element of input) { if (acc === SPECIAL) { acc = element; continue; } acc = (await (0, index_js_1.pull)(reducer(acc, element, index++))); } if (acc === SPECIAL) { throw new error_js_1.DoddleError(error_js_1.reduceOnEmptyError); } return acc; }); } /** * Reverses `this` sequence. * * ⚠️ Requires iterating over the entire sequence. * * @returns A new sequence with the elements in reverse order. */ reverse() { return (0, exports.ASeqOperator)(this, async function* reverse(input) { const elements = []; for await (const element of input) { elements.push(element); } yield* elements.reverse(); }); } scan(reducer, initial) { (0, error_js_1.chk)(this.scan).reducer(reducer); return (0, exports.ASeqOperator)(this, async function* scan(input) { let hasAcc = initial !== undefined; let acc = initial; let index = 0; if (hasAcc) { yield acc; } for await (const element of input) { if (!hasAcc) { acc = element; hasAcc = true; } else { acc = (await (0, index_js_1.pull)(reducer(acc, element, index++))); } yield acc; } }); } seqEquals(input, projection = x => x) { projection ??= x => x; const other = (0, aseq_ctor_js_1.aseq)(input); return (0, index_js_1.lazyOperator)(this, async function seqEquals(input) { const otherIterator = (0, utils_js_1._aiter)(other); try { for await (const element of input) { const otherElement = await otherIterator.next(); const keyThis = await (0, index_js_1.pull)(projection(element)); const keyOther = await (0, index_js_1.pull)(projection(otherElement.value)); if (otherElement.done || keyThis !== keyOther) { return false; } } return !!(await otherIterator.next()).done; } finally { await otherIterator.return?.(); } }); } setEquals(_other, projection = x => x) { projection ??= x => x; const other = (0, aseq_ctor_js_1.aseq)(_other); return (0, index_js_1.lazyOperator)(this, async function setEquals(input) { const set = new Set(); for await (const element of other) { set.add(await (0, index_js_1.pull)(projection(element))); } for await (const element of input) { if (!set.delete(await (0, index_js_1.pull)(projection(element)))) { return false; } } return set.size === 0; }); } /** * Returns a new sequence that shares its iterator state. This allows different loops to iterate * over it, sharing progress. * * ⚠️ Can be iterated over exactly once, and will be empty afterwards. * * @returns A new, shared iterable sequence that can be iterated over exactly once. */ share() { const iter = (0, index_js_1.doddle)(() => (0, utils_js_1._aiter)(this)); return (0, exports.ASeqOperator)(this, async function* share() { while (true) { const { done, value } = await iter.pull().next(); if (done) { return; } yield value; } }); } /** * Shuffles the elements of `this` sequence randomly. * * ⚠️ Requires iterating over the entire sequence. * * @returns A new sequence with the shuffled elements. */ shuffle() { return (0, exports.ASeqOperator)(this, async function* shuffle(input) { const array = await (0, aseq_ctor_js_1.aseq)(input).toArray().pull(); (0, utils_js_1.shuffleArray)(array); yield* array; }); } /** * Skips the first `count` elements of `this` sequence, yielding the rest. * * ℹ️ If `count` is negative, skips the final elements instead (e.g. `skipLast`) * * @param count The number of elements to skip. * @returns A new sequence without the skipped elements. */ skip(count) { (0, error_js_1.chk)(this.skip).count(count); return (0, exports.ASeqOperator)(this, async function* skip(input) { let myCount = count; if (myCount < 0) { myCount = -myCount; yield* (0, aseq_ctor_js_1.aseq)(input) .window(myCount + 1, (...window) => { if (window.length === myCount + 1) { return window[0]; } return SPECIAL; }) .filter(x => x !== SPECIAL); } else { for await (const x of input) { if (myCount > 0) { myCount--; continue; } yield x; } } }); } /** * Skips elements from `this` sequence while the given predicate is true, and yields the rest. * * ℹ️ You can use the `options` argument to skip the first element that returns `false`. * * @param predicate The predicate to determine whether to continue skipping. * @param options Options for skipping behavior. * @returns A new sequence without the skipped elements. */ skipWhile(predicate, options) { predicate = (0, error_js_1.chk)(this.skipWhile).predicate(predicate); return (0, exports.ASeqOperator)(this, async function* skipWhile(input) { let prevMode = 0 /* SkippingMode.None */; let index = 0; for await (const element of input) { if (prevMode === 2 /* SkippingMode.NotSkipping */) { yield element; continue; } const newSkipping = await (0, index_js_1.pull)(predicate(element, index++)); if (!newSkipping) { if (prevMode !== 1 /* SkippingMode.Skipping */ || !options?.skipFinal) { yield element; } } prevMode = newSkipping ? 1 /* SkippingMode.Skipping */ : 2 /* SkippingMode.NotSkipping */; } }); } /** * 🦥**Lazily** checks if any element in `this` sequence matches the given predicate, by * iterating over it until a match is found. * * @param predicate The predicate to match the element. * @returns A 🦥{@link DoddleAsync} that resolves to `true` if any element matches, or `false` * otherwise. */ some(predicate) { predicate = (0, error_js_1.chk)(this.some).predicate(predicate); return (0, index_js_1.lazyOperator)(this, async function some(input) { let index = 0; for await (const element of input) { if (await (0, index_js_1.pull)(predicate(element, index++))) { return true; } } return false; }); } /** * 🦥**Lazily** sums the elements of `this` sequence by iterating over it, applying the given * projection to each element. * * @param projection The projection function to apply to each element. * @returns A 🦥{@link DoddleAsync} that resolves to the sum of the projected elements. */ sumBy(projection) { (0, error_js_1.chk)(this.sumBy).projection(projection); return (0, index_js_1.lazyOperator)(this, async function sumBy(input) { let cur = 0; let index = 0; for await (const element of input) { cur += (await (0, index_js_1.pull)(projection(element, index++))); } return cur; }); } /** * Yields the first `count` elements of `this` sequence. * * ℹ️ If `count` is negative, yields the last `-count` elements instead.\ * ℹ️ If the sequence is smaller than `count`, it yields all elements. * * @param count The number of elements to yield. * @returns A new sequence with the yielded elements. */ take(count) { (0, error_js_1.chk)(this.take).count(count); return (0, exports.ASeqOperator)(this, async function* take(input) { let myCount = count; if (myCount === 0) { return; } if (myCount < 0) { myCount = -myCount; const results = (await (0, aseq_ctor_js_1.aseq)(input) .concat([SPECIAL]) .window(myCount + 1, (...window) => { if (window[window.length - 1] === SPECIAL) { window.pop(); return window; } return undefined; }) .filter(x => x !== undefined) .first() .pull()); yield* results; } else { for await (const element of input) { yield element; myCount--; if (myCount <= 0) { return; } } } }); } /** * Yields the first elements of `this` sequence while the given predicate is true and skips the * rest. * * ℹ️ If the sequence is too small, the result will be empty.\ * ℹ️ The `options` argument lets you keep the first element for which the predicate returns * `false`. * * @param predicate The predicate to determine whether to continue yielding. * @param options Extra options. * @returns A new sequence with the yielded elements. */ takeWhile(predicate, specifier) { (0, error_js_1.chk)(this.takeWhile).predicate(predicate); return (0, exports.ASeqOperator)(this, async function* takeWhile(input) { let index = 0; for await (const element of input) { if (await (0, index_js_1.pull)(predicate(element, index++))) { yield element; } else { if (specifier?.takeFinal) { yield element; } return; } } }); } /** * 🦥**Lazily** converts `this` sequence into an array. * * ⚠️ Has to iterate over the entire sequence. * * @returns A 🦥{@link DoddleAsync} that resolves to an array of the elements in the sequence. */ toArray() { return (0, index_js_1.lazyOperator)(this, async function toArray(input) { const result = []; for await (const element of input) { result.push(element); } return result; }); } /** * Returns `this` sequence as an {@link AsyncIterable}. * * @returns An {@link AsyncIterable} of the elements in this sequence. */ toIterable() { return this; } /** * 🦥**Lazily** converts `this` sequence into a Map. * * ⚠️ Has to iterate over the entire sequence. * * @param kvpProjection A function that takes an element and returns a key-value pair. * @returns A 🦥{@link DoddleAsync} that resolves to a Map of the elements in the sequence. */ toMap(kvpProjection) { kvpProjection = (0, error_js_1.chk)(this.toMap).kvpProjection(kvpProjection); return (0, index_js_1.lazyOperator)(this, async function toMap(input) { const m = new Map(); let index = 0; for await (const element of input) { const [key, value] = (await (0, index_js_1.pull)(kvpProjection(element, index++))); m.set(key, value); } return m; }); } /** * 🦥**Lazily** converts `this` sequence into a plain JS object. Uses the given `kvpProjection` * to determine each key-value pair. * * @param kvpProjection A function that takes an element and returns a key-value pair. Each key * must be a valid PropertyKey. * @returns A 🦥{@link DoddleAsync} that resolves to a plain JS object. */ toRecord(kvpProjection) { (0, error_js_1.chk)(this.toRecord).kvpProjection(kvpProjection); return (0, index_js_1.lazyOperator)(this, async function toObject(input) { const o = {}; let index = 0; for await (const element of input) { const [key, value] = (await (0, index_js_1.pull)(kvpProjection(element, index++))); o[key] = value; } return o; }); } /** * **Lazily** converts `this` async sequence into a synchronous {@link Seq} sequence. * * ⚠️ Has to iterate over the entire sequence. * * @returns A 🦥{@link DoddleAsync} that resolves to a synchronous {@link Seq} sequence. */ toSeq() { return (0, index_js_1.lazyOperator)(this, async function toSeq(input) { const all = await (0, aseq_ctor_js_1.aseq)(input).toArray().pull(); return (0, seq_ctor_js_1.seq)(all); }); } /** * 🦥**Lazily** converts `this` sequence into a Set. * * ⚠️ Has to iterate over the entire sequence. * * @returns A 🦥{@link DoddleAsync} that resolves to a Set of the elements in the sequence. */ toSet() { return (0, index_js_1.lazyOperator)(this, async function toSet(input) { const result = new Set(); for await (const element of input) { result.add(element); } return result; }); } /** * Filters out duplicate elements from `this` sequence, optionally using a key projection. * * ℹ️ **Doesn't** need to iterate over the entire sequence.\ * ⚠️ Needs to cache the sequence as it's iterated over. * * @param keyProjection A function that takes an element and returns a key used to check for * uniqueness. * @returns A sequence of unique elements. */ uniq(keyProjection = x => x) { (0, error_js_1.chk)(this.uniq).projection(keyProjection); return (0, exports.ASeqOperator)(this, async function* uniq(input) { const seen = new Set(); for await (const element of input) { const key = await (0, index_js_1.pull)(keyProjection(element)); if (!seen.has(key)) { seen.add(key); yield element; } } }); } /** * Splits `this` async sequence into overlapping windows of fixed size, optionally applying a * projection to each window. * * @param size The size of each window. The last window may be smaller. * @param projection Optionally, a function to project each window to a value or promise of a * value. * @returns A new async sequence of windowed values or projected results. */ window(size, projection) { const c = (0, error_js_1.chk)(this.window); c.size(size); projection ??= (...window) => window; c.projection(projection); return (0, exports.ASeqOperator)(this, async function* window(input) { const buffer = Array(size); let i = 0; for await (const item of input) { buffer[i++ % size] = item; if (i >= size) { yield (0, index_js_1.pull)(projection(...buffer.slice(i % size), ...buffer.slice(0, i % size))); } } if (i > 0 && i < size) { yield (0, index_js_1.pull)(projection(...buffer.slice(0, i))); } }); } zip(_others, projection) { const others = _others.map(aseq_ctor_js_1.aseq); projection ??= (...args) => args; (0, error_js_1.chk)(this.zip).projection(projection); return (0, exports.ASeqOperator)(this, async function* zip(input) { const iterators = [input, ...others].map(utils_js_1._aiter); while (true) { const pResults = iterators.map(async (iter, i) => { if (!iter) { return undefined; } const result = await iter.next(); if (result.done) { await iterators[i]?.return?.(); iterators[i] = undefined; return undefined; } return result; }); const results = await Promise.all(pResults); if (results.every(r => !r)) { break; } yield (0, index_js_1.pull)(projection(...results.map(r => r?.value))); } }); } } exports.ASeq = ASeq; /** @internal */ const ASeqOperator = function aseq(operand, impl) { const myASeq = Object.assign(new ASeq(), [impl.name, operand]); return Object.defineProperty(myASeq, Symbol.asyncIterator, { get: () => impl.bind(myASeq, myASeq[1]) }); }; exports.ASeqOperator = ASeqOperator; //# sourceMappingURL=aseq.class.js.map