@rimbu/stream
Version:
Efficient structure representing a sequence of elements, with powerful operations for TypeScript
1,166 lines (1,165 loc) • 51 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Reducer = void 0;
var tslib_1 = require("tslib");
var base_1 = require("@rimbu/base");
var common_1 = require("@rimbu/common");
var stream_1 = require("@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(function (initHalt) {
var result = {};
var allHalted = true;
for (var key in reducerObj) {
var instance = reducerObj[key].compile();
allHalted = allHalted && instance.halted;
result[key] = instance;
}
if (allHalted) {
initHalt();
}
return result;
}, function (state, elem, index, halt) {
var allHalted = true;
for (var key in state) {
var reducerInstance = state[key];
if (!reducerInstance.halted) {
reducerInstance.next(elem);
allHalted = allHalted && reducerInstance.halted;
}
}
if (allHalted) {
halt();
}
return state;
}, function (state) {
var result = {};
for (var 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() {
var reducers = [];
for (var _i = 0; _i < arguments.length; _i++) {
reducers[_i] = arguments[_i];
}
return Reducer.create(function (initHalt) {
var allHalted = true;
var result = reducers.map(function (reducer) {
var instance = reducer.compile();
allHalted = allHalted && instance.halted;
return instance;
});
if (allHalted) {
initHalt();
}
return result;
}, function (state, elem, index, halt) {
var allHalted = true;
var i = -1;
var len = state.length;
while (++i < len) {
var reducerInstance = state[i];
if (!reducerInstance.halted) {
reducerInstance.next(elem);
allHalted = allHalted && reducerInstance.halted;
}
}
if (allHalted) {
halt();
}
return state;
}, function (state) { return state.map(function (reducerInstance) { return reducerInstance.getOutput(); }); });
}
var Reducer;
(function (Reducer) {
/**
* 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
*/
var Base = /** @class */ (function () {
function Base(init, next, stateToResult) {
this.init = init;
this.next = next;
this.stateToResult = stateToResult;
}
Base.prototype.filterInput = function (pred, options) {
var _this = this;
if (options === void 0) { options = {}; }
var _a = options.negate, negate = _a === void 0 ? false : _a;
return create(function () { return _this.compile(); }, function (state, elem, index, halt) {
if (pred(elem, index, halt) !== negate) {
state.next(elem);
if (state.halted) {
halt();
}
}
return state;
}, function (state) { return state.getOutput(); });
};
Base.prototype.mapInput = function (mapFun) {
var _this = this;
return create(this.init, function (state, elem, index, halt) {
return _this.next(state, mapFun(elem, index), index, halt);
}, this.stateToResult);
};
Base.prototype.flatMapInput = function (flatMapFun) {
var _this = this;
return create(function () { return _this.compile(); }, function (state, elem, index, halt) {
if (state.halted) {
halt();
return state;
}
var elems = flatMapFun(elem, index);
var iter = stream_1.Stream.from(elems)[Symbol.iterator]();
var done = Symbol();
var value;
while (done !== (value = iter.fastNext(done))) {
state.next(value);
if (state.halted) {
halt();
break;
}
}
return state;
}, function (state) { return state.getOutput(); });
};
Base.prototype.collectInput = function (collectFun) {
var _this = this;
return create(function () { return _this.compile(); }, function (state, elem, index, halt) {
var nextElem = collectFun(elem, index, common_1.CollectFun.Skip, halt);
if (common_1.CollectFun.Skip !== nextElem) {
state.next(nextElem);
if (state.halted) {
halt();
}
}
return state;
}, function (state) { return state.getOutput(); });
};
Base.prototype.mapOutput = function (mapFun) {
var _this = this;
return create(this.init, this.next, function (state, index, halted) {
return mapFun(_this.stateToResult(state, index, halted), index, halted);
});
};
Base.prototype.takeOutput = function (amount) {
var _this = this;
if (amount <= 0) {
return create(function (initHalt) {
initHalt();
return _this.init(initHalt);
}, this.next, this.stateToResult);
}
return create(this.init, function (state, next, index, halt) {
if (index >= amount - 1) {
halt();
}
return _this.next(state, next, index, halt);
}, this.stateToResult);
};
Base.prototype.takeOutputUntil = function (pred, options) {
var _this = this;
if (options === void 0) { options = {}; }
var _a = options.negate, negate = _a === void 0 ? false : _a;
return create(this.init, function (state, next, index, halt) {
var nextState = _this.next(state, next, index, halt);
var nextOutput = _this.stateToResult(nextState, index, false);
if (pred(nextOutput, index) !== negate) {
halt();
}
return nextState;
}, this.stateToResult);
};
Base.prototype.takeInput = function (amount) {
if (amount <= 0) {
return create(this.init, identity, this.stateToResult);
}
return this.filterInput(function (_, i, halt) {
if (i >= amount - 1) {
halt();
}
return i < amount;
});
};
Base.prototype.dropInput = function (amount) {
if (amount <= 0) {
return this;
}
return this.filterInput(function (_, i) { return i >= amount; });
};
Base.prototype.sliceInput = function (from, amount) {
if (from === void 0) { from = 0; }
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);
};
Base.prototype.chain = function (nextReducers) {
var _this = this;
return Reducer.create(function (initHalt) {
var iterator = stream_1.Stream.from(nextReducers)[Symbol.iterator]();
var activeInstance = _this.compile();
if (undefined !== activeInstance && activeInstance.halted) {
var output = activeInstance.getOutput();
do {
var creator = iterator.fastNext();
if (undefined === creator) {
initHalt();
return {
activeInstance: activeInstance,
iterator: iterator,
};
}
activeInstance = (0, common_1.OptLazy)(creator, output).compile();
output = activeInstance.getOutput();
} while (activeInstance.halted);
}
return {
activeInstance: activeInstance,
iterator: iterator,
};
}, function (state, next, index, halt) {
state.activeInstance.next(next);
while (state.activeInstance.halted) {
var output = state.activeInstance.getOutput();
var creator = state.iterator.fastNext();
if (undefined === creator) {
halt();
return state;
}
state.activeInstance = (0, common_1.OptLazy)(creator, output).compile();
}
return state;
}, function (state) { return state.activeInstance.getOutput(); });
};
Base.prototype.compile = function () {
return new Reducer.InstanceImpl(this);
};
return Base;
}());
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
*/
var InstanceImpl = /** @class */ (function () {
function InstanceImpl(reducer) {
var _this = this;
this.reducer = reducer;
this.___index = 0;
this.___halted = false;
this.halt = function () {
_this.___halted = true;
};
this.next = function (value) {
if (_this.___halted) {
throw new Reducer.ReducerHaltedError();
}
_this.___state = _this.reducer.next(_this.___state, value, _this.___index++, _this.halt);
};
this.___state = reducer.init(this.halt);
}
Object.defineProperty(InstanceImpl.prototype, "halted", {
get: function () {
return this.___halted;
},
enumerable: false,
configurable: true
});
Object.defineProperty(InstanceImpl.prototype, "index", {
get: function () {
return this.___index;
},
enumerable: false,
configurable: true
});
InstanceImpl.prototype.getOutput = function () {
return this.reducer.stateToResult(this.___state, this.index, this.halted);
};
return InstanceImpl;
}());
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 !== null && stateToResult !== void 0 ? 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 !== null && stateToResult !== void 0 ? 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(function () { return (0, common_1.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(function () { return 0; }, function (state, next) { return 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(function () { return 1; }, function (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(function () { return 0; }, function (avg, value, index) { return 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 = function (compFun, otherwise) {
var token = Symbol();
return create(function () { return token; }, function (state, next) {
if (token === state) {
return next;
}
return compFun(state, next) < 0 ? state : next;
}, function (state) { return (token === state ? (0, common_1.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 = function (otherwise) {
return create(function () { return undefined; }, function (state, next) {
return undefined !== state && state < next ? state : next;
}, function (state) { return state !== null && state !== void 0 ? state : (0, common_1.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 = function (compFun, otherwise) {
var token = Symbol();
return create(function () { return token; }, function (state, next) {
if (token === state) {
return next;
}
return compFun(state, next) > 0 ? state : next;
}, function (state) { return (token === state ? (0, common_1.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 = function (otherwise) {
return create(function () { return undefined; }, function (state, next) {
return undefined !== state && state > next ? state : next;
}, function (state) { return state !== null && state !== void 0 ? state : (0, common_1.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(_a) {
var _b = _a === void 0 ? {} : _a, _c = _b.sep, sep = _c === void 0 ? '' : _c, _d = _b.start, start = _d === void 0 ? '' : _d, _e = _b.end, end = _e === void 0 ? '' : _e, _f = _b.valueToString, valueToString = _f === void 0 ? String : _f;
return create(function () { return ''; }, function (state, next, index) {
var valueString = valueToString(next);
if (index <= 0) {
return start.concat(valueString);
}
return state.concat(sep, valueToString(next));
}, function (state) { return 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(function () {
//
}, identity, function (_, index) { return 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 = function (otherwise) {
return create(function () { return undefined; }, function (state, next, _, halt) {
halt();
return next;
}, function (state, index) { return (index <= 0 ? (0, common_1.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 = function (otherwise) {
return create(function () { return undefined; }, function (_, next) { return next; }, function (state, index) { return (index <= 0 ? (0, common_1.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 = function (otherwise) {
return create(function () { return undefined; }, function (state, next, index, halt) {
if (index > 1) {
halt();
}
return next;
}, function (state, index) { return (index !== 1 ? (0, common_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) {
if (options === void 0) { 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) {
if (options === void 0) { options = {}; }
var _a = options.negate, negate = _a === void 0 ? false : _a;
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) {
if (options === void 0) { options = {}; }
var _a = options.eq, eq = _a === void 0 ? common_1.Eq.objectIs : _a, _b = options.negate, negate = _b === void 0 ? false : _b;
var sliceStream = stream_1.Stream.from(other);
var done = Symbol();
return Reducer.create(function () {
var iter = sliceStream[Symbol.iterator]();
var nextSeq = iter.fastNext(done);
return { iter: iter, nextSeq: nextSeq, result: false };
}, function (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;
}, function (state, index, halted) { return !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) {
if (options === void 0) { options = {}; }
var _a = options.amount, amount = _a === void 0 ? 1 : _a, _b = options.eq, eq = _b === void 0 ? Object.is : _b, _c = options.negate, negate = _c === void 0 ? false : _c;
return Reducer.create(function (initHalt) {
if (amount <= 0) {
initHalt();
}
return amount;
}, function (state, next, _, halt) {
var satisfies = eq(next, elem) !== negate;
if (!satisfies) {
return state;
}
var newRemain = state - 1;
if (newRemain <= 0) {
halt();
}
return newRemain;
}, function (state) { return 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) {
if (options === void 0) { options = {}; }
var sliceStream = stream_1.Stream.from(slice);
var done = Symbol();
var _a = options.eq, eq = _a === void 0 ? common_1.Eq.objectIs : _a, _b = options.amount, amount = _b === void 0 ? 1 : _b;
return Reducer.create(function (initHalt) {
var sliceIter = sliceStream[Symbol.iterator]();
var sliceValue = sliceIter.fastNext(done);
if (done === sliceValue || amount <= 0) {
initHalt();
return { sliceIter: sliceIter, sliceValue: sliceValue, remain: 0 };
}
return {
sliceIter: sliceIter,
sliceValue: sliceValue,
remain: amount,
};
}, function (state, next, _, halt) {
if (done === state.sliceValue) {
base_1.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;
}, function (state) { return 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) {
if (options === void 0) { options = {}; }
var sliceStream = stream_1.Stream.from(slice);
var done = Symbol();
var newReducerSpec = Reducer.startsWithSlice(slice, options);
return Reducer.create(function (initHalt) {
var sliceIter = sliceStream[Symbol.iterator]();
var sliceValue = sliceIter.fastNext(done);
if (done === sliceValue) {
initHalt();
}
return new Set([newReducerSpec.compile()]);
}, function (state, nextValue) {
var e_1, _a;
try {
for (var state_1 = tslib_1.__values(state), state_1_1 = state_1.next(); !state_1_1.done; state_1_1 = state_1.next()) {
var instance = state_1_1.value;
if (instance.halted) {
state.delete(instance);
}
else {
instance.next(nextValue);
}
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (state_1_1 && !state_1_1.done && (_a = state_1.return)) _a.call(state_1);
}
finally { if (e_1) throw e_1.error; }
}
var newReducerInstance = newReducerSpec.compile();
newReducerInstance.next(nextValue);
state.add(newReducerInstance);
return state;
}, function (state) {
return state.size === 0 ||
stream_1.Stream.from(state).some(function (instance) { return 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) {
if (options === void 0) { options = {}; }
var eq = options.eq, _a = options.amount, amount = _a === void 0 ? 1 : _a;
return Reducer.pipe(endsWithSlice(slice, { eq: eq }), Reducer.contains(true, { amount: 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(function () { return true; }, function (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(function () { return false; }, function (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(function () { return true; }, function (_, __, ___, 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(function () { return false; }, function (_, __, ___, 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(function (initHalt) {
initHalt();
}, identity, function () { return (0, common_1.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 = function (pred, options) {
var _a, _b;
if (options === void 0) { options = {}; }
var collectorTrue = (_a = options.collectorTrue) !== null && _a !== void 0 ? _a : Reducer.toArray();
var collectorFalse = (_b = options.collectorFalse) !== null && _b !== void 0 ? _b : Reducer.toArray();
return Reducer.create(function () { return [collectorTrue.compile(), collectorFalse.compile()]; }, function (state, value, index) {
var instanceIndex = pred(value, index) ? 0 : 1;
state[instanceIndex].next(value);
return state;
}, function (state) { return state.map(function (v) { return 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 = function (valueToKey, options) {
if (options === void 0) { options = {}; }
var _a = options.collector, collector = _a === void 0 ? Reducer.toJSMultiMap() : _a;
return Reducer.create(function () { return collector.compile(); }, function (state, value, index) {
var key = valueToKey(value, index);
state.next([key, value]);
return state;
}, function (state) { return 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 = function (reducers, otherwise) {
return Reducer.create(function (initHalt) {
var instances = stream_1.Stream.from(reducers)
.map(function (reducer) { return reducer.compile(); })
.toArray();
var doneInstance = instances.find(function (instance) { return instance.halted; });
if (undefined !== doneInstance) {
initHalt();
}
return { instances: instances, doneInstance: doneInstance };
}, function (state, next, _, halt) {
var e_2, _a;
try {
for (var _b = tslib_1.__values(state.instances), _c = _b.next(); !_c.done; _c = _b.next()) {
var instance = _c.value;
instance.next(next);
if (instance.halted) {
state.doneInstance = instance;
halt();
return state;
}
}
}
catch (e_2_1) { e_2 = { error: e_2_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_2) throw e_2.error; }
}
return state;
}, function (state) {
return state.doneInstance === undefined
? (0, common_1.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) {
if (options === void 0) { options = {}; }
var _a = options.reversed, reversed = _a === void 0 ? false : _a;
return create(function () { return []; }, function (state, next) {
if (reversed)
state.unshift(next);
else
state.push(next);
return state;
}, function (state) { return 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(function () { return new Map(); }, function (state, next) {
state.set(next[0], next[1]);
return state;
}, function (state) { return 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(function () { return new Map(); }, function (state, _a) {
var _b = tslib_1.__read(_a, 2), key = _b[0], value = _b[1];
var entry = state.get(key);
if (undefined === entry) {
state.set(key, [value]);
}
else {
entry.push(value);
}
return state;
}, function (state) { return 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(function () { return new Set(); }, function (state, next) {
state.add(next);
return state;
}, function (s) { return 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(function () { return ({}); }, function (state, entry) {
state[entry[0]] = entry[1];
return state;
}, function (s) { return (tslib_1.__assign({}, s)); });
}
Reducer.toJSObject = toJSObject;
var InvalidCombineShapeError = /** @class */ (function (_super) {
tslib_1.__extends(InvalidCombineShapeError, _super);
function InvalidCombineShapeError() {
return _super.call(this, 'Invalid reducer combine shape supplied') || this;
}
return InvalidCombineShapeError;
}(common_1.ErrBase.CustomError));
Reducer.InvalidCombineShapeError = InvalidCombineShapeError;
var ReducerHaltedError = /** @class */ (function (_super) {
tslib_1.__extends(ReducerHaltedError, _super);
function ReducerHaltedError() {
return _super.call(this, 'A halted reducer cannot receive more values') || this;
}
return ReducerHaltedError;
}(common_1.ErrBase.CustomError));
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.apply(void 0, tslib_1.__spreadArray([], tslib_1.__read(shape.map(function (item) { return Reducer.combine(item); })), false));
}
if (typeof shape === 'object' && shape !== null) {
var result = {};
for (var 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 = function () {
var nextReducers = [];
for (var _i = 0; _i < arguments.length; _i++) {
nextReducers[_i] = arguments[_i];
}
if (nextReducers.length < 2) {
base_1.RimbuError.throwInvalidUsageError('Reducer.pipe should have at least two arguments');
}
var _a = tslib_1.__read(nextReducers), current = _a[0], next = _a[1], others = _a.slice(2);
if (others.length > 0) {
return Reducer.pipe(current, Reducer.pipe.apply(Reducer, tslib_1.__spreadArray([next], tslib_1.__read(others), false)));
}
return Reducer.create(function (inithalt) {
var currentInstance = current.compile();