@rimbu/stream
Version:
Efficient structure representing a sequence of elements, with powerful operations for TypeScript
1,112 lines • 45.3 kB
JavaScript
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