doddle
Version:
Tiny yet feature-packed (async) iteration toolkit.
1,049 lines • 39.5 kB
JavaScript
"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