UNPKG

@rimbu/stream

Version:

Efficient structure representing a sequence of elements, with powerful operations for TypeScript

1,112 lines 45.3 kB
import { __classPrivateFieldGet, __classPrivateFieldSet } from "tslib"; import { RimbuError } from '@rimbu/base'; import { CollectFun, Eq, ErrBase, OptLazy } from '@rimbu/common'; import { Stream } from '@rimbu/stream'; function identity(value) { return value; } /** * Combines multiple reducers in an object's values of the same input type into a single reducer that * forwards each incoming value to all reducers, and when output is requested will return an object containing * the corresponding output of each reducer at the matching object property. */ function combineObj(reducerObj) { return Reducer.create((initHalt) => { const result = {}; let allHalted = true; for (const key in reducerObj) { const instance = reducerObj[key].compile(); allHalted = allHalted && instance.halted; result[key] = instance; } if (allHalted) { initHalt(); } return result; }, (state, elem, index, halt) => { let allHalted = true; for (const key in state) { const reducerInstance = state[key]; if (!reducerInstance.halted) { reducerInstance.next(elem); allHalted = allHalted && reducerInstance.halted; } } if (allHalted) { halt(); } return state; }, (state) => { const result = {}; for (const key in state) { result[key] = state[key].getOutput(); } return result; }); } /** * Combines multiple reducers in an array of the same input type into a single reducer that * forwards each incoming value to all reducers, and when output is requested will return an array containing * the corresponding output of each reducer. */ function combineArr(...reducers) { return Reducer.create((initHalt) => { let allHalted = true; const result = reducers.map((reducer) => { const instance = reducer.compile(); allHalted = allHalted && instance.halted; return instance; }); if (allHalted) { initHalt(); } return result; }, (state, elem, index, halt) => { let allHalted = true; let i = -1; const len = state.length; while (++i < len) { const reducerInstance = state[i]; if (!reducerInstance.halted) { reducerInstance.next(elem); allHalted = allHalted && reducerInstance.halted; } } if (allHalted) { halt(); } return state; }, (state) => state.map((reducerInstance) => reducerInstance.getOutput())); } export var Reducer; (function (Reducer) { var _InstanceImpl_state, _InstanceImpl_index, _InstanceImpl_halted; /** * A base class that can be used to easily create `Reducer` instances. * @typeparam I - the input value type * @typeparam O - the output value type * @typeparam S - the internal state type */ class Base { constructor(init, next, stateToResult) { this.init = init; this.next = next; this.stateToResult = stateToResult; } filterInput(pred, options = {}) { const { negate = false } = options; return create(() => this.compile(), (state, elem, index, halt) => { if (pred(elem, index, halt) !== negate) { state.next(elem); if (state.halted) { halt(); } } return state; }, (state) => state.getOutput()); } mapInput(mapFun) { return create(this.init, (state, elem, index, halt) => this.next(state, mapFun(elem, index), index, halt), this.stateToResult); } flatMapInput(flatMapFun) { return create(() => this.compile(), (state, elem, index, halt) => { if (state.halted) { halt(); return state; } const elems = flatMapFun(elem, index); const iter = Stream.from(elems)[Symbol.iterator](); const done = Symbol(); let value; while (done !== (value = iter.fastNext(done))) { state.next(value); if (state.halted) { halt(); break; } } return state; }, (state) => state.getOutput()); } collectInput(collectFun) { return create(() => this.compile(), (state, elem, index, halt) => { const nextElem = collectFun(elem, index, CollectFun.Skip, halt); if (CollectFun.Skip !== nextElem) { state.next(nextElem); if (state.halted) { halt(); } } return state; }, (state) => state.getOutput()); } mapOutput(mapFun) { return create(this.init, this.next, (state, index, halted) => mapFun(this.stateToResult(state, index, halted), index, halted)); } takeOutput(amount) { if (amount <= 0) { return create((initHalt) => { initHalt(); return this.init(initHalt); }, this.next, this.stateToResult); } return create(this.init, (state, next, index, halt) => { if (index >= amount - 1) { halt(); } return this.next(state, next, index, halt); }, this.stateToResult); } takeOutputUntil(pred, options = {}) { const { negate = false } = options; return create(this.init, (state, next, index, halt) => { const nextState = this.next(state, next, index, halt); const nextOutput = this.stateToResult(nextState, index, false); if (pred(nextOutput, index) !== negate) { halt(); } return nextState; }, this.stateToResult); } takeInput(amount) { if (amount <= 0) { return create(this.init, identity, this.stateToResult); } return this.filterInput((_, i, halt) => { if (i >= amount - 1) { halt(); } return i < amount; }); } dropInput(amount) { if (amount <= 0) { return this; } return this.filterInput((_, i) => i >= amount); } sliceInput(from = 0, amount) { if (undefined === amount) return this.dropInput(from); if (amount <= 0) return create(this.init, identity, this.stateToResult); if (from <= 0) return this.takeInput(amount); return this.takeInput(amount).dropInput(from); } chain(nextReducers) { return Reducer.create((initHalt) => { const iterator = Stream.from(nextReducers)[Symbol.iterator](); let activeInstance = this.compile(); if (undefined !== activeInstance && activeInstance.halted) { let output = activeInstance.getOutput(); do { const creator = iterator.fastNext(); if (undefined === creator) { initHalt(); return { activeInstance, iterator, }; } activeInstance = OptLazy(creator, output).compile(); output = activeInstance.getOutput(); } while (activeInstance.halted); } return { activeInstance, iterator, }; }, (state, next, index, halt) => { state.activeInstance.next(next); while (state.activeInstance.halted) { const output = state.activeInstance.getOutput(); const creator = state.iterator.fastNext(); if (undefined === creator) { halt(); return state; } state.activeInstance = OptLazy(creator, output).compile(); } return state; }, (state) => state.activeInstance.getOutput()); } compile() { return new Reducer.InstanceImpl(this); } } Reducer.Base = Base; /** * The default `Reducer.Impl` implementation. * @typeparam I - the input element type * @typeparam O - the output element type * @typeparam S - the reducer state type */ class InstanceImpl { constructor(reducer) { this.reducer = reducer; _InstanceImpl_state.set(this, void 0); _InstanceImpl_index.set(this, 0); _InstanceImpl_halted.set(this, false); this.halt = () => { __classPrivateFieldSet(this, _InstanceImpl_halted, true, "f"); }; this.next = (value) => { var _a, _b; if (__classPrivateFieldGet(this, _InstanceImpl_halted, "f")) { throw new Reducer.ReducerHaltedError(); } __classPrivateFieldSet(this, _InstanceImpl_state, this.reducer.next(__classPrivateFieldGet(this, _InstanceImpl_state, "f"), value, (__classPrivateFieldSet(this, _InstanceImpl_index, (_b = __classPrivateFieldGet(this, _InstanceImpl_index, "f"), _a = _b++, _b), "f"), _a), this.halt), "f"); }; __classPrivateFieldSet(this, _InstanceImpl_state, reducer.init(this.halt), "f"); } get halted() { return __classPrivateFieldGet(this, _InstanceImpl_halted, "f"); } get index() { return __classPrivateFieldGet(this, _InstanceImpl_index, "f"); } getOutput() { return this.reducer.stateToResult(__classPrivateFieldGet(this, _InstanceImpl_state, "f"), this.index, this.halted); } } _InstanceImpl_state = new WeakMap(), _InstanceImpl_index = new WeakMap(), _InstanceImpl_halted = new WeakMap(); Reducer.InstanceImpl = InstanceImpl; /** * Returns a `Reducer` with the given options: * @param init - the initial state value * @param next - returns the next state value based on the given inputs:<br/> * - current: the current state<br/> * - next: the current input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @param stateToResult - a function that converts the current state to an output value * @typeparam I - the input value type * @typeparam O - the output value type * @typeparam S - the internal state type * @example * ```ts * const evenNumberOfOnes = Reducer.create( * true, * (current, value: number) => (value === 1 ? !current : current), * (state) => (state ? 'even' : 'not even') * ); * const result = Stream.of(1, 2, 3, 2, 1).reduce(evenNumberOfOnes); * console.log(result); * // => 'even' * ``` */ function create(init, next, stateToResult) { return new Reducer.Base(init, next, stateToResult); } Reducer.create = create; /** * Returns a `Reducer` of which the input, state, and output types are the same. * @param init - the initial state value * @param next - returns the next state value based on the given inputs:<br/> * - current: the current state<br/> * - next: the current input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @param stateToResult - (optional) a function that converts the current state to an output value * @typeparam T - the overall value type * @example * ```ts * const sum = Reducer.createMono( * 0, * (current, value) => current + value * ); * const result = Stream.of(1, 2, 3, 2, 1).reduce(sum); * console.log(result); * // => 9 * ``` */ function createMono(init, next, stateToResult) { return create(init, next, stateToResult ?? identity); } Reducer.createMono = createMono; /** * Returns a `Reducer` of which the state and output types are the same. * @param init - the initial state value * @param next - returns the next state value based on the given inputs:<br/> * - current: the current state<br/> * - next: the current input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @param stateToResult - (optional) a function that converts the current state to an output value * @typeparam I - the input value type * @typeparam O - the output value type * @example * ```ts * const boolToString = Reducer.createOutput( * '', * (current, value: boolean) => current + (value ? 'T' : 'F') * ); * const result = Stream.of(true, false, true).reduce(boolToString); * console.log(result); * // => 'TFT' * ``` */ function createOutput(init, next, stateToResult) { return create(init, next, stateToResult ?? identity); } Reducer.createOutput = createOutput; /** * Returns a `Reducer` that uses the given `init` and `next` values to fold the input values into * result values. * @param init - an (optionally lazy) initial result value * @param next - a function taking the following arguments:<br/> * - current - the current result value<br/> * - value - the next input value<br/> * - index: the input index value<br/> * - halt: function that, when called, ensures no more elements are passed to the reducer * @typeparam T - the input type * @typeparam R - the output type */ function fold(init, next) { return Reducer.createOutput(() => OptLazy(init), next); } Reducer.fold = fold; /** * A `Reducer` that sums all given numeric input values. * @example * ```ts * console.log(Stream.range({ amount: 5 }).reduce(Reducer.sum)) * // => 10 * ``` */ Reducer.sum = createMono(() => 0, (state, next) => state + next); /** * A `Reducer` that calculates the product of all given numeric input values. * @example * ```ts * console.log(Stream.range({ start: 1, amount: 5 }).reduce(product)) * // => 120 * ``` */ Reducer.product = createMono(() => 1, (state, next, _, halt) => { if (0 === next) halt(); return state * next; }); /** * A `Reducer` that calculates the average of all given numberic input values. * @example * ```ts * console.log(Stream.range({ amount: 5 }).reduce(Reducer.average)); * // => 2 * ``` */ Reducer.average = createMono(() => 0, (avg, value, index) => avg + (value - avg) / (index + 1)); /** * Returns a `Reducer` that remembers the minimum value of the inputs using the given `compFun` to compare input values * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller * @param otherwise - (default: undefineds) a fallback value when there were no input values given * @typeparam T - the element type * @typeparam O - the fallback value type * @example * ```ts * const stream = Stream.of('abc', 'a', 'abcde', 'ab') * console.log(stream.minBy((s1, s2) => s1.length - s2.length)) * // 'a' * ``` */ Reducer.minBy = (compFun, otherwise) => { const token = Symbol(); return create(() => token, (state, next) => { if (token === state) { return next; } return compFun(state, next) < 0 ? state : next; }, (state) => (token === state ? OptLazy(otherwise) : state)); }; /** * Returns a `Reducer` that remembers the minimum value of the numberic inputs. * @param otherwise - (default: undefined) a fallback value when there were no input values given * @typeparam O - the fallback value type * @example * ```ts * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.min())) * // => 3 * ``` */ // prettier-ignore Reducer.min = (otherwise) => { return create(() => undefined, (state, next) => undefined !== state && state < next ? state : next, (state) => state ?? OptLazy(otherwise)); }; /** * Returns a `Reducer` that remembers the maximum value of the inputs using the given `compFun` to compare input values * @param compFun - a comparison function for two input values, returning 0 when equal, positive when greater, negetive when smaller * @param otherwise - (default: undefined) a fallback value when there were no input values given * @typeparam T - the element type * @typeparam O - the fallback value type * @example * ```ts * const stream = Stream.of('abc', 'a', 'abcde', 'ab') * console.log(stream.maxBy((s1, s2) => s1.length - s2.length)) * // 'abcde' * ``` */ Reducer.maxBy = (compFun, otherwise) => { const token = Symbol(); return create(() => token, (state, next) => { if (token === state) { return next; } return compFun(state, next) > 0 ? state : next; }, (state) => (token === state ? OptLazy(otherwise) : state)); }; /** * Returns a `Reducer` that remembers the maximum value of the numberic inputs. * @param otherwise - (default: undefined) a fallback value when there were no input values given * @typeparam O - the fallback value type * @example * ```ts * console.log(Stream.of(5, 3, 7, 4).reduce(Reducer.max())) * // => 7 * ``` */ // prettier-ignore Reducer.max = (otherwise) => { return create(() => undefined, (state, next) => undefined !== state && state > next ? state : next, (state) => state ?? OptLazy(otherwise)); }; /** * Returns a `Reducer` that joins the given input values into a string using the given options. * @param options - an object containing:<br/> * - sep: (optional) a seperator string value between values in the output<br/> * - start: (optional) a start string to prepend to the output<br/> * - end: (optional) an end string to append to the output<br/> * @typeparam T - the input element type * @example * ```ts * console.log(Stream.of(1, 2, 3).reduce(Reducer.join({ sep: '-' }))) * // => '1-2-3' * ``` */ function join({ sep = '', start = '', end = '', valueToString = String, } = {}) { return create(() => '', (state, next, index) => { const valueString = valueToString(next); if (index <= 0) { return start.concat(valueString); } return state.concat(sep, valueToString(next)); }, (state) => state.concat(end)); } Reducer.join = join; /** * A `Reducer` that remembers the amount of input items provided. * @example * ```ts * const stream = Stream.range({ amount: 10 }) * console.log(stream.reduce(Reducer.count)) * // => 10 * ``` */ Reducer.count = create(() => { // }, identity, (_, index) => index); /** * Returns a `Reducer` that remembers the first input value. * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided * @typeparam T - the input value type * @typeparam O - the fallback value type * @example * ```ts * console.log(Stream.range({ amount: 10 }).reduce(Reducer.first())) * // => 0 * ``` */ Reducer.first = (otherwise) => { return create(() => undefined, (state, next, _, halt) => { halt(); return next; }, (state, index) => (index <= 0 ? OptLazy(otherwise) : state)); }; /** * Returns a `Reducer` that remembers the last input value. * @param otherwise - (default: undefined) a fallback value to output if no input value has been provided * @typeparam T - the input value type * @typeparam O - the fallback value type * @example * ```ts * console.log(Stream.range({ amount: 10 }).reduce(Reducer.last())) * // => 9 * ``` */ Reducer.last = (otherwise) => { return create(() => undefined, (_, next) => next, (state, index) => (index <= 0 ? OptLazy(otherwise) : state)); }; /** * Returns a Reducer that only produces an output value when having receives exactly one * input value, otherwise will return the `otherwise` value or undefined. * @param otherwise - the fallback value to return when more or less than one value is received. * @typeparam T - the element type * @typeparam O - the fallback value type */ Reducer.single = (otherwise) => { return create(() => undefined, (state, next, index, halt) => { if (index > 1) { halt(); } return next; }, (state, index) => (index !== 1 ? OptLazy(otherwise) : state)); }; /** * Returns a `Reducer` that ouputs false as long as no input value satisfies given `pred`, true otherwise. * @typeparam T - the element type * @param pred - a function taking an input value and its index, and returning true if the value satisfies the predicate * @param options - (optional) an object containing the following properties:<br/> * - negate: (default: false) when true will invert the given predicate * @example * ```ts * console.log( * Stream.range({ amount: 10 }).reduce(Reducer.some((v) => v > 5)) * ) * // => true * ``` */ function some(pred, options = {}) { return Reducer.nonEmpty.filterInput(pred, options); } Reducer.some = some; /** * Returns a `Reducer` that ouputs true as long as all input values satisfy the given `pred`, false otherwise. * @typeparam T - the element type * @param pred - a function taking an input value and its index, and returning true if the value satisfies the predicate * @param options - (optional) an object containing the following properties:<br/> * - negate: (default: false) when true will invert the given predicate * @example * ```ts * console.log( * Stream.range({ amount: 10 }).reduce(Reducer.every((v) => v < 5)) * ) * // => false * ``` */ function every(pred, options = {}) { const { negate = false } = options; return Reducer.isEmpty.filterInput(pred, { negate: !negate }); } Reducer.every = every; /** * Returns a `Reducer` that ouputs true when the received elements match the given `other` stream source according to the `eq` instance, false otherwise. * @typeparam T - the element type * @param other - a stream source containg elements to match against * @param options - (optional) an object containing the following properties:<br/> * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements * - negate: (default: false) when true will invert the given predicate */ function equals(other, options = {}) { const { eq = Eq.objectIs, negate = false } = options; const sliceStream = Stream.from(other); const done = Symbol(); return Reducer.create(() => { const iter = sliceStream[Symbol.iterator](); const nextSeq = iter.fastNext(done); return { iter, nextSeq, result: false }; }, (state, next, _, halt) => { if (done === state.nextSeq) { halt(); state.result = false; return state; } if (eq(next, state.nextSeq) === negate) { halt(); state.result = false; return state; } state.nextSeq = state.iter.fastNext(done); if (done === state.nextSeq) { state.result = true; } return state; }, (state, index, halted) => !halted && done === state.nextSeq); } Reducer.equals = equals; /** * Returns a `Reducer` that outputs false as long as the given `elem` has not been encountered the given `amount` of times in the input values, true otherwise. * @typeparam T - the element type * @param elem - the element to search for * @param options - (optional) an object containing the following properties:<br/> * - amount: (detaulf: 1) the amount of elements to find * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements * - negate: (default: false) when true will invert the given predicate * @example * ```ts * console.log(Stream.range({ amount: 10 }).reduce(Reducer.contains(5))) * // => true * ``` */ function contains(elem, options = {}) { const { amount = 1, eq = Object.is, negate = false } = options; return Reducer.create((initHalt) => { if (amount <= 0) { initHalt(); } return amount; }, (state, next, _, halt) => { const satisfies = eq(next, elem) !== negate; if (!satisfies) { return state; } const newRemain = state - 1; if (newRemain <= 0) { halt(); } return newRemain; }, (state) => state <= 0); } Reducer.contains = contains; /** * Returns a `Reducer` that returns true if the first input values match the given `slice` values repeated `amount` times. Otherwise, * returns false. * @param slice - a sequence of elements to match against * @param options - (optional) an object containing the following properties:<br/> * - amount: (detaulf: 1) the amount of elements to find * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements */ function startsWithSlice(slice, options = {}) { const sliceStream = Stream.from(slice); const done = Symbol(); const { eq = Eq.objectIs, amount = 1 } = options; return Reducer.create((initHalt) => { const sliceIter = sliceStream[Symbol.iterator](); const sliceValue = sliceIter.fastNext(done); if (done === sliceValue || amount <= 0) { initHalt(); return { sliceIter, sliceValue, remain: 0 }; } return { sliceIter, sliceValue: sliceValue, remain: amount, }; }, (state, next, _, halt) => { if (done === state.sliceValue) { RimbuError.throwInvalidStateError(); } if (eq(next, state.sliceValue)) { state.sliceValue = state.sliceIter.fastNext(done); if (done === state.sliceValue) { state.remain--; if (state.remain <= 0) { halt(); } else { state.sliceIter = sliceStream[Symbol.iterator](); state.sliceValue = state.sliceIter.fastNext(done); } } } else { halt(); } return state; }, (state) => state.remain <= 0); } Reducer.startsWithSlice = startsWithSlice; /** * Returns a `Reducer` that returns true if the last input values match the given `slice` values repeated `amount` times. Otherwise, * returns false. * @param slice - a sequence of elements to match against * @param options - (optional) an object containing the following properties:<br/> * - amount: (detaulf: 1) the amount of elements to find * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements */ function endsWithSlice(slice, options = {}) { const sliceStream = Stream.from(slice); const done = Symbol(); const newReducerSpec = Reducer.startsWithSlice(slice, options); return Reducer.create((initHalt) => { const sliceIter = sliceStream[Symbol.iterator](); const sliceValue = sliceIter.fastNext(done); if (done === sliceValue) { initHalt(); } return new Set([newReducerSpec.compile()]); }, (state, nextValue) => { for (const instance of state) { if (instance.halted) { state.delete(instance); } else { instance.next(nextValue); } } const newReducerInstance = newReducerSpec.compile(); newReducerInstance.next(nextValue); state.add(newReducerInstance); return state; }, (state) => state.size === 0 || Stream.from(state).some((instance) => instance.getOutput())); } Reducer.endsWithSlice = endsWithSlice; /** * Returns a `Reducer` that returns true if the input values contain the given `slice` sequence `amount` times. Otherwise, * returns false. * @param slice - a sequence of elements to match against * @param options - (optional) an object containing the following properties:<br/> * - amount: (detaulf: 1) the amount of elements to find * - eq: (default: Eq.objectIs) the `Eq` instance to use to compare elements */ function containsSlice(slice, options = {}) { const { eq, amount = 1 } = options; return Reducer.pipe(endsWithSlice(slice, { eq }), Reducer.contains(true, { amount })); } Reducer.containsSlice = containsSlice; /** * A `Reducer` that takes boolean values and outputs true if all input values are true, and false otherwise. * @example * ```ts * console.log(Stream.of(true, false, true)).reduce(Reducer.and)) * // => false * ``` */ Reducer.and = createMono(() => true, (state, next, _, halt) => { if (!next) { halt(); } return next; }); /** * A `Reducer` that takes boolean values and outputs true if one or more input values are true, and false otherwise. * @example * ```ts * console.log(Stream.of(true, false, true)).reduce(Reducer.or)) * // => true * ``` */ Reducer.or = createMono(() => false, (state, next, _, halt) => { if (next) { halt(); } return next; }); /** * A `Reducer` that outputs true if no input values are received, false otherwise. * @example * ```ts * console.log(Stream.of(1, 2, 3).reduce(Reducer.isEmpty)) * // => false * ``` */ Reducer.isEmpty = createOutput(() => true, (_, __, ___, halt) => { halt(); return false; }); /** * A `Reducer` that outputs true if one or more input values are received, false otherwise. * @example * ```ts * console.log(Stream.of(1, 2, 3).reduce(Reducer.nonEmpty)) * // => true * ``` */ Reducer.nonEmpty = createOutput(() => false, (_, __, ___, halt) => { halt(); return true; }); /** * Returns a `Reducer` that always outputs the given `value`, and does not accept input values. */ function constant(value) { return Reducer.create((initHalt) => { initHalt(); }, identity, () => OptLazy(value)); } Reducer.constant = constant; /** * Returns a `Reducer` that splits the incoming values into two separate outputs based on the given `pred` predicate. Values for which the predicate is true * are fed into the `collectorTrue` reducer, and other values are fed into the `collectorFalse` instance. If no collectors are provided the values are collected * into arrays. * @param pred - a predicate receiving the value and its index * @param options - (optional) an object containing the following properties:<br/> * - collectorTrue: (default: Reducer.toArray()) a reducer that collects the values for which the predicate is true<br/> * - collectorFalse: (default: Reducer.toArray()) a reducer that collects the values for which the predicate is false * @typeparam T - the input element type * @typeparam RT - the reducer result type for the `collectorTrue` value * @typeparam RF - the reducer result type for the `collectorFalse` value * @note if the predicate is a type guard, the return type is automatically inferred * @example * ```ts * Stream.of(1, 2, 3).partition((v) => v % 2 === 0) * // => [[2], [1, 3]] * * Stream.of<number | string>(1, 'a', 'b', 2) * .partition((v): v is string => typeof v === 'string') * // => [['a', 'b'], [1, 2]] * // return type is: [string[], number[]] * * Stream.of(1, 2, 3, 4).partition( * (v) => v % 2 === 0, * { collectorTrue: Reducer.toJSSet(), collectorFalse: Reducer.sum } * ) * // => [Set(2, 4), 4] * ``` */ Reducer.partition = (pred, options = {}) => { const collectorTrue = options.collectorTrue ?? Reducer.toArray(); const collectorFalse = options.collectorFalse ?? Reducer.toArray(); return Reducer.create(() => [collectorTrue.compile(), collectorFalse.compile()], (state, value, index) => { const instanceIndex = pred(value, index) ? 0 : 1; state[instanceIndex].next(value); return state; }, (state) => state.map((v) => v.getOutput())); }; /** * Returns a `Reducer` that uses the `valueToKey` function to calculate a key for each value, and feeds the tuple of the key and the value to the * `collector` reducer. Finally, it returns the output of the `collector`. If no collector is given, the default collector will return a JS multimap * of the type `Map<K, V[]>`. * @param valueToKey - function taking a value and its index, and returning the corresponding key * @param options - (optional) an object containing the following properties:<br/> * - collector: (default: Reducer.toArray()) a reducer that collects the incoming tuple of key and value, and provides the output * @typeparam T - the input value type * @typeparam K - the key type * @typeparam R - the collector output type * @example * ```ts * Stream.of(1, 2, 3).groupBy((v) => v % 2) * // => Map {0 => [2], 1 => [1, 3]} * ``` */ Reducer.groupBy = (valueToKey, options = {}) => { const { collector = Reducer.toJSMultiMap(), } = options; return Reducer.create(() => collector.compile(), (state, value, index) => { const key = valueToKey(value, index); state.next([key, value]); return state; }, (state) => state.getOutput()); }; /** * Returns a `Reducer` that feeds incoming values to all reducers in the provided `reducers` source, and halts when the first * reducer in the array is halted and returns the output of that reducer. Returns the `otherwise` value if no reducer is yet halted. * @param reducers - a stream source of reducers that will receive the incoming values * @param otherwise - a fallback value to return if none of the reducers has been halted * @typeparam T - the input value type * @typeparam R - the output value type * @typeparam O - the fallback value type */ Reducer.race = (reducers, otherwise) => { return Reducer.create((initHalt) => { const instances = Stream.from(reducers) .map((reducer) => reducer.compile()) .toArray(); const doneInstance = instances.find((instance) => instance.halted); if (undefined !== doneInstance) { initHalt(); } return { instances, doneInstance }; }, (state, next, _, halt) => { for (const instance of state.instances) { instance.next(next); if (instance.halted) { state.doneInstance = instance; halt(); return state; } } return state; }, (state) => state.doneInstance === undefined ? OptLazy(otherwise) : state.doneInstance.getOutput()); }; /** * Returns a `Reducer` that collects received input values in an array, and returns a copy of that array as an output value when requested. * @typeparam T - the element type * @param options - (optional) object specifying the following properties<br/> * - reversed: (optional) when true will create a reversed array * @example * ```ts * console.log(Stream.of(1, 2, 3).reduce(Reducer.toArray())) * // => [1, 2, 3] * console.log(Stream.of(1, 2, 3).reduce(Reducer.toArray({ reversed: true }))) * // => [3, 2, 1] * ``` */ function toArray(options = {}) { const { reversed = false } = options; return create(() => [], (state, next) => { if (reversed) state.unshift(next); else state.push(next); return state; }, (state) => state.slice()); } Reducer.toArray = toArray; /** * Returns a `Reducer` that collects received input tuples into a mutable JS Map, and returns * a copy of that map when output is requested. * @typeparam K - the map key type * @typeparam V - the map value type * @example * ```ts * console.log(Stream.of([1, 'a'], [2, 'b']).reduce(Reducer.toJSMap())) * // Map { 1 => 'a', 2 => 'b' } * ``` */ function toJSMap() { return create(() => new Map(), (state, next) => { state.set(next[0], next[1]); return state; }, (state) => new Map(state)); } Reducer.toJSMap = toJSMap; /** * Returns a `Reducer` that collects received input tuples into a mutable JS multimap, and returns * a copy of that map when output is requested. * @typeparam K - the map key type * @typeparam V - the map value type * @example * ```ts * console.log(Stream.of([1, 'a'], [2, 'b']).reduce(Reducer.toJSMap())) * // Map { 1 => 'a', 2 => 'b' } * ``` */ function toJSMultiMap() { return create(() => new Map(), (state, [key, value]) => { const entry = state.get(key); if (undefined === entry) { state.set(key, [value]); } else { entry.push(value); } return state; }, (state) => new Map(state)); } Reducer.toJSMultiMap = toJSMultiMap; /** * Returns a `Reducer` that collects received input values into a mutable JS Set, and returns * a copy of that map when output is requested. * @typeparam T - the element type * @example * ```ts * console.log(Stream.of(1, 2, 3).reduce(Reducer.toJSSet())) * // Set {1, 2, 3} * ``` */ function toJSSet() { return create(() => new Set(), (state, next) => { state.add(next); return state; }, (s) => new Set(s)); } Reducer.toJSSet = toJSSet; /** * Returns a `Reducer` that collects 2-tuples containing keys and values into a plain JS object, and * returns a copy of that object when output is requested. * @typeparam K - the result object key type * @typeparam V - the result object value type * @example * ```ts * console.log(Stream.of(['a', 1], ['b', true]).reduce(Reducer.toJSObject())) * // { a: 1, b: true } * ``` */ function toJSObject() { return create(() => ({}), (state, entry) => { state[entry[0]] = entry[1]; return state; }, (s) => ({ ...s })); } Reducer.toJSObject = toJSObject; class InvalidCombineShapeError extends ErrBase.CustomError { constructor() { super('Invalid reducer combine shape supplied'); } } Reducer.InvalidCombineShapeError = InvalidCombineShapeError; class ReducerHaltedError extends ErrBase.CustomError { constructor() { super('A halted reducer cannot receive more values'); } } Reducer.ReducerHaltedError = ReducerHaltedError; /** * Returns a `Reducer` that combines multiple input `reducers` according to the given "shape" by providing input values to all of them and collecting the outputs in the shape. * @typeparam T - the input value type for all the reducers * @typeparam S - the desired result shape type * @param shape - a shape defining where reducer outputs will be located in the result. It can consist of a single reducer, an array of shapes, or an object with string keys and shapes as values. * @example * ```ts * const red = Reducer.combine([Reducer.sum, { av: [Reducer.average] }]) * console.log(Stream.range({amount: 9 }).reduce(red)) * // => [36, { av: [4] }] * ``` */ function combine(shape) { if (shape instanceof Reducer.Base) { return shape; } if (Array.isArray(shape)) { return combineArr(...shape.map((item) => Reducer.combine(item))); } if (typeof shape === 'object' && shape !== null) { const result = {}; for (const key in shape) { result[key] = Reducer.combine(shape[key]); } return combineObj(result); } throw new Reducer.InvalidCombineShapeError(); } Reducer.combine = combine; /** * Returns a `Reducer` instance that first applies this reducer, and then applies the given `next` reducer to each output produced * by the previous reducer. * @typeparam I - the input type of the `reducer1` reducer * @typeparam O1 - the output type of the `reducer1` reducer * @typeparam O2 - the output type of the `reducer2` reducer * @typeparam O3 - the output type of the `reducer3` reducer * @typeparam O4 - the output type of the `reducer4` reducer * @typeparam O5 - the output type of the `reducer5` reducer * @param reducer1 - the next reducer to apply to each output of this reducer. * @param reducer2 - (optional) the next reducer to apply to each output of this reducer. * @param reducer3 - (optional) the next reducer to apply to each output of this reducer. * @param reducer4 - (optional) the next reducer to apply to each output of this reducer. * @param reducer5 - (optional) the next reducer to apply to each output of this reducer. * @example * ```ts * Stream.of(1, 2, 3) * .reduce( * Reducer.pipe(Reducer.product, Reducer.sum) * ) * // => 9 * ``` */ Reducer.pipe = (...nextReducers) => { if (nextReducers.length < 2) { RimbuError.throwInvalidUsageError('Reducer.pipe should have at least two arguments'); } const [current, next, ...others] = nextReducers; if (others.length > 0) { return Reducer.pipe(current, Reducer.pipe(next, ...others)); } return Reducer.create((inithalt) => { const currentInstance = current.compile(); const nextInstance = next.compile(); if (currentInstance.halted || nextInstance.halted) { inithalt(); } return { currentInstance, nextInstance, }; }, (state, next, index, halt) => { const { currentInstance, nextInstance } = state; currentInstance.next(next); nextInstance.next(currentInstance.getOutput()); if (currentInstance.halted || nextInstance.halted) { halt(); } return state; }, (state, index, halted) => { if (halted && index === 0) { state.nextInstance.next(state.currentInstance.getOutput()); } return state.nextInstance.getOutput(); }); }; })(Reducer || (Reducer = {})); //# sourceMappingURL=reducer.mjs.map