@rimbu/stream
Version:
Efficient structure representing a sequence of elements, with powerful operations for TypeScript
2,188 lines (1,809 loc) • 56.5 kB
text/typescript
import { RimbuError, type Token } from '@rimbu/base';
import {
Comp,
Eq,
ErrBase,
IndexRange,
OptLazy,
Range,
TraverseState,
type ArrayNonEmpty,
type CollectFun,
type ToJSON,
} from '@rimbu/common';
import {
Reducer,
Transformer,
type FastIterator,
type Stream,
type StreamSource,
} from '@rimbu/stream';
import type { StreamConstructors } from '@rimbu/stream/custom';
import {
AlwaysIterator,
AppendIterator,
ArrayIterator,
ArrayReverseIterator,
CollectIterator,
ConcatIterator,
DropIterator,
DropWhileIterator,
FilterApplyIterator,
FilterIterator,
FilterPureIterator,
IndexedIterator,
MapApplyIterator,
MapIterator,
MapPureIterator,
PrependIterator,
RandomIntIterator,
RandomIterator,
RangeDownIterator,
RangeUpIterator,
ReducerFastIterator,
RepeatIterator,
TakeIterator,
TransformerFastIterator,
UnfoldIterator,
ZipAllWithItererator,
ZipWithIterator,
emptyFastIterator,
isFastIterator,
} from '@rimbu/stream/custom';
function* yieldObjKeys<K extends string | number | symbol>(
obj: Record<K, any>
): Generator<K> {
for (const key in obj) {
yield key;
}
}
function* yieldObjValues<V>(obj: Record<any, V>): Generator<V> {
for (const key in obj) {
yield (obj as any)[key];
}
}
function* yieldObjEntries<K extends string | number | symbol, V>(
obj: Record<K, V>
): Generator<[K, V]> {
for (const key in obj) {
yield [key, obj[key]];
}
}
export abstract class StreamBase<T> implements Stream<T> {
abstract [Symbol.iterator](): FastIterator<T>;
stream(): this {
return this;
}
equals(
other: StreamSource<T>,
{ eq = Eq.objectIs, negate = false }: { eq?: Eq<T>; negate?: boolean } = {}
): boolean {
const it1 = this[Symbol.iterator]();
const it2 = fromStreamSource(other)[Symbol.iterator]();
const done = Symbol('Done');
while (true) {
const v1 = it1.fastNext(done);
const v2 = it2.fastNext(done);
if (done === v1 || done === v2) {
return Object.is(v1, v2);
}
if (eq(v1, v2) === negate) {
return false;
}
}
}
assumeNonEmpty(): Stream.NonEmpty<T> {
return this as unknown as Stream.NonEmpty<T>;
}
asNormal(): Stream<T> {
return this;
}
prepend(value: OptLazy<T>): Stream.NonEmpty<T> {
return new PrependStream<T>(this, value).assumeNonEmpty();
}
append(value: OptLazy<T>): Stream.NonEmpty<T> {
return new AppendStream<T>(this, value).assumeNonEmpty();
}
forEach(
f: (value: T, index: number, halt: () => void) => void,
options: { state?: TraverseState } = {}
): void {
const { state = TraverseState() } = options;
if (state.halted) return;
const done = Symbol('Done');
let value: T | typeof done;
const iterator = this[Symbol.iterator]();
const { halt } = state;
while (!state.halted && done !== (value = iterator.fastNext(done))) {
f(value, state.nextIndex(), halt);
}
}
forEachPure<A extends readonly unknown[]>(
f: (value: T, ...args: A) => void,
...args: A
): void {
const done = Symbol('Done');
let value: T | typeof done;
const iterator = this[Symbol.iterator]();
while (done !== (value = iterator.fastNext(done))) {
f(value, ...args);
}
}
indexed(options: { startIndex?: number } = {}): Stream<[number, T]> {
const { startIndex = 0 } = options;
return new IndexedStream(this, startIndex);
}
filter(
pred: (value: T, index: number, halt: () => void) => boolean,
options: { negate?: boolean | undefined } = {}
): any {
const { negate = false } = options;
return new FilterStream(this, pred, negate) as any;
}
filterPure<A extends readonly unknown[]>(
options: {
pred: (value: T, ...args: A) => boolean;
negate?: boolean | undefined;
},
...args: A
): any {
const { pred, negate = false } = options;
return new FilterPureStream(this, pred, args, negate) as any;
}
withOnly<F extends T>(values: F[]): Stream<F> {
if (values.length <= 0) {
return this as any;
}
const set = new Set<T>(values);
return this.filterPure({ pred: (v) => set.has(v) });
}
without<F extends T>(values: F[]): any {
if (values.length <= 0) {
return this as any;
}
const set = new Set<T>(values);
return this.filterPure({ pred: (v) => set.has(v), negate: true });
}
map<T2>(mapFun: (value: T, index: number) => T2): Stream<T2> {
return new MapStream<T, T2>(this, mapFun);
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: T, ...args: A) => T2,
...args: A
): Stream<T2> {
return new MapPureStream<T, A, T2>(this, mapFun, args);
}
collect<R>(collectFun: CollectFun<T, R>): Stream<R> {
return new CollectStream<T, R>(this, collectFun);
}
flatMap<T2>(
flatMapFun: (value: T, index: number, halt: () => void) => StreamSource<T2>
): Stream<T2> {
return this.transform(Transformer.flatMap(flatMapFun));
}
flatZip<T2>(
flatMapFun: (value: T, index: number, halt: () => void) => StreamSource<T2>
): Stream<[T, T2]> {
return this.transform(Transformer.flatZip(flatMapFun));
}
transform<R>(transformer: Transformer<T, R>): Stream<R> {
return new TransformerStream(this, transformer);
}
first<O>(otherwise?: OptLazy<O>): T | O {
return this[Symbol.iterator]().fastNext(otherwise as OptLazy<O>);
}
last<O>(otherwise?: OptLazy<O>): T | O {
const done = Symbol('Done');
let value: T | typeof done;
let lastValue: T | typeof done = done;
const iterator = this[Symbol.iterator]();
while (done !== (value = iterator.fastNext(done))) {
lastValue = value;
}
if (done === lastValue) {
return OptLazy(otherwise) as O;
}
return lastValue;
}
single<O>(otherwise?: OptLazy<O>): T | O {
const iterator = this[Symbol.iterator]();
const done = Symbol('Done');
const value = iterator.fastNext(done);
if (done !== value) {
if (done === iterator.fastNext(done)) {
return value;
}
}
return OptLazy(otherwise!);
}
count(): number {
let result = 0;
const done = Symbol('Done');
const iterator = this[Symbol.iterator]();
while (done !== iterator.fastNext(done)) {
result++;
}
return result;
}
countElement(
value: T,
options: { eq?: Eq<T>; negate?: boolean } = {}
): number {
const { eq = Eq.objectIs, negate = false } = options;
let result = 0;
const done = Symbol('Done');
const iterator = this[Symbol.iterator]();
let current: T | typeof done;
while (done !== (current = iterator.fastNext(done))) {
if (eq(value, current) !== negate) {
result++;
}
}
return result;
}
find<O>(
pred: (value: T, index: number) => boolean,
options: {
occurrance?: number | undefined;
negate?: boolean | undefined;
otherwise?: OptLazy<O>;
} = {}
): T | O {
const { occurrance = 1, negate = false, otherwise } = options;
if (occurrance <= 0) {
return OptLazy(otherwise) as O;
}
const done = Symbol('Done');
const iterator = this[Symbol.iterator]();
let value: T | typeof done;
let remain = occurrance;
let index = 0;
while (done !== (value = iterator.fastNext(done))) {
if (pred(value, index++) !== negate && --remain <= 0) {
return value;
}
}
return OptLazy(otherwise) as O;
}
elementAt<O>(index: number, otherwise?: OptLazy<O>): T | O {
if (index < 0) {
return OptLazy(otherwise) as O;
}
const done = Symbol('Done');
const iterator = this[Symbol.iterator]();
let value: T | typeof done;
let i = 0;
while (i <= index && done !== (value = iterator.fastNext(done))) {
if (i === index) {
return value;
}
i++;
}
return OptLazy(otherwise) as O;
}
indicesWhere(
pred: (value: T) => boolean,
options: { negate?: boolean } = {}
): Stream<number> {
return this.transform(Transformer.indicesWhere(pred, options));
}
indicesOf(
searchValue: T,
options: { eq?: Eq<T>; negate?: boolean } = {}
): Stream<number> {
return this.transform(Transformer.indicesOf(searchValue, options));
}
indexWhere(
pred: (value: T, index: number) => boolean,
options: {
occurrance?: number;
negate?: boolean;
} = {}
): number | undefined {
const { occurrance = 1, negate = false } = options;
if (occurrance <= 0) {
return undefined;
}
const done = Symbol('Done');
let value: T | typeof done;
const iterator = this[Symbol.iterator]();
let index = 0;
let occ = 0;
while (done !== (value = iterator.fastNext(done))) {
const i = index++;
if (pred(value, i) !== negate) {
occ++;
if (occ >= occurrance) {
return i;
}
}
}
return undefined;
}
indexOf(
searchValue: T,
options: {
occurrance?: number | undefined;
eq?: Eq<T> | undefined;
negate?: boolean | undefined;
} = {}
): number | undefined {
const { occurrance = 1 } = options;
if (occurrance <= 0) {
return undefined;
}
const { eq = Eq.objectIs, negate = false } = options;
const done = Symbol('Done');
let value: T | typeof done;
const iterator = this[Symbol.iterator]();
let index = 0;
let occ = 0;
while (done !== (value = iterator.fastNext(done))) {
const i = index++;
if (eq(value, searchValue) !== negate) {
occ++;
if (occ >= occurrance) {
return i;
}
}
}
return undefined;
}
some(
pred: (value: T, index: number) => boolean,
options: { negate?: boolean } = {}
): boolean {
return undefined !== this.indexWhere(pred, options);
}
every(
pred: (value: T, index: number) => boolean,
options: { negate?: boolean } = {}
): boolean {
const { negate = false } = options;
return undefined === this.indexWhere(pred, { negate: !negate });
}
contains(
searchValue: T,
options: { amount?: number; eq?: Eq<T>; negate?: boolean } = {}
): boolean {
const { amount = 1 } = options;
if (amount <= 0) {
return true;
}
const { eq, negate } = options;
return (
undefined !==
this.indexOf(searchValue, { occurrance: amount, eq, negate })
);
}
containsSlice(
source: StreamSource.NonEmpty<T>,
options: { eq?: Eq<T>; amount?: number } = {}
): boolean {
return this.reduce(Reducer.containsSlice(source, options));
}
takeWhile(
pred: (value: T, index: number) => boolean,
options: { negate?: boolean } = {}
): Stream<T> {
const { negate = false } = options;
return this.filter((value, index, halt) => {
const result = pred(value, index) !== negate;
if (!result) {
halt();
}
return result;
});
}
dropWhile(
pred: (value: T, index: number) => boolean,
options: { negate?: boolean } = {}
): Stream<T> {
const { negate = false } = options;
return new DropWhileStream<T>(this, pred, negate);
}
take(amount: number): Stream<T> {
if (amount <= 0) {
return emptyStream;
}
return new TakeStream<T>(this, amount);
}
drop(amount: number): Stream<T> {
if (amount <= 0) {
return this;
}
return new DropStream<T>(this, amount);
}
repeat(amount?: number): Stream<T> {
if (undefined !== amount && amount <= 1) {
return this;
}
return new FromStream<T>(() => new RepeatIterator<T>(this, amount));
}
concat(...others: ArrayNonEmpty<StreamSource<T>>): Stream.NonEmpty<T> {
if (others.every(isEmptyStreamSourceInstance)) {
return this.assumeNonEmpty();
}
return new ConcatStream<T>(this, others).assumeNonEmpty();
}
min<O>(otherwise?: OptLazy<O>): T | O {
return this.minBy(Comp.defaultComp().compare, otherwise);
}
minBy<O>(compare: (v1: T, v2: T) => number, otherwise?: OptLazy<O>): T | O {
const done = Symbol('Done');
const iterator = this[Symbol.iterator]();
let result: T | typeof done = iterator.fastNext(done);
if (done === result) return OptLazy(otherwise) as O;
let value: T | typeof done;
while (done !== (value = iterator.fastNext(done))) {
if (compare(value, result) < 0) result = value;
}
return result;
}
max<O>(otherwise?: OptLazy<O>): T | O {
return this.maxBy(Comp.defaultComp().compare, otherwise);
}
maxBy<O>(compare: (v1: T, v2: T) => number, otherwise?: OptLazy<O>): T | O {
const done = Symbol('Done');
const iterator = this[Symbol.iterator]();
let result: T | typeof done = iterator.fastNext(done);
if (done === result) return OptLazy(otherwise) as O;
let value: T | typeof done;
while (done !== (value = iterator.fastNext(done))) {
if (compare(value, result) > 0) result = value;
}
return result;
}
intersperse(sep: StreamSource<T>): Stream<T> {
if (isEmptyStreamSourceInstance(sep)) {
return this;
}
return this.transform(Transformer.intersperse(sep));
}
join({
sep = '',
start = '',
end = '',
valueToString = String,
ifEmpty = undefined,
} = {}): string {
const done = Symbol('Done');
const iterator = this[Symbol.iterator]();
let value: T | typeof done = iterator.fastNext(done);
if (done === value) {
if (undefined !== ifEmpty) {
return ifEmpty;
}
return start.concat(end);
}
let result = start.concat(valueToString(value));
while (done !== (value = iterator.fastNext(done))) {
result = result.concat(sep, valueToString(value));
}
return result.concat(end);
}
mkGroup({
sep = emptyStream as StreamSource<T>,
start = emptyStream as StreamSource<T>,
end = emptyStream as StreamSource<T>,
} = {}): any {
return fromStreamSource(start).concat(this.intersperse(sep), end);
}
splitWhere<R>(
pred: (value: T, index: number) => boolean,
options: {
negate?: boolean | undefined;
collector?: Reducer<T, R> | undefined;
} = {}
): Stream<R> {
return this.transform(Transformer.splitWhere(pred, options));
}
splitOn<R>(
sepElem: T,
options: {
eq?: Eq<T> | undefined;
negate?: boolean | undefined;
collector?: Reducer<T, R> | undefined;
} = {}
): Stream<R> {
return this.transform(Transformer.splitOn(sepElem, options));
}
splitOnSlice<R>(
sepSeq: StreamSource<T>,
options: {
eq?: Eq<T> | undefined;
collector?: Reducer<T, R> | undefined;
} = {}
): Stream<R> {
return this.transform(Transformer.splitOnSlice(sepSeq, options));
}
distinctPrevious(options: { eq?: Eq<T>; negate?: boolean } = {}): Stream<T> {
return this.transform(Transformer.distinctPrevious(options));
}
window<R>(
windowSize: number,
options: {
skipAmount?: number | undefined;
collector?: Reducer<T, R> | undefined;
} = {}
): Stream<R> {
return this.transform(Transformer.window(windowSize, options as any));
}
partition(
pred: (value: T, index: number) => any,
options: { collectorTrue?: any; collectorFalse?: any } = {}
): [any, any] {
return this.reduce(Reducer.partition(pred, options));
}
groupBy<K, R>(
valueToKey: (value: T, index: number) => K,
options: { collector?: Reducer<readonly [K, T], R> | undefined } = {}
): R {
return this.reduce(Reducer.groupBy<T, K, R>(valueToKey, options as any));
}
fold<R>(
init: OptLazy<R>,
next: (current: R, value: T, index: number, halt: () => void) => R
): R {
return this.reduce(Reducer.fold(init, next));
}
foldStream<R>(
init: OptLazy<R>,
next: (current: R, value: T, index: number, halt: () => void) => R
): Stream<R> {
return this.reduceStream(Reducer.fold(init, next));
}
reduce<const S extends Reducer.CombineShape<T2>, T2 extends T = T>(
shape: S & Reducer.CombineShape<T2>
): Reducer.CombineResult<S> {
const reducerInstance = Reducer.combine(
shape
).compile() as Reducer.Instance<T, Reducer.CombineResult<S>>;
const done = Symbol('Done');
let value: T | typeof done;
const iter = this[Symbol.iterator]();
while (!reducerInstance.halted && done !== (value = iter.fastNext(done))) {
reducerInstance.next(value);
}
return reducerInstance.getOutput();
}
reduceStream<const S extends Reducer.CombineShape<T2>, T2 extends T = T>(
shape: S & Reducer.CombineShape<T2>
): Stream<Reducer.CombineResult<S>> {
const reducer = Reducer.combine(shape) as Reducer<
T2,
Reducer.CombineResult<S>
>;
return new ReducerStream(this, reducer);
}
toArray(): T[] {
const iterator = this[Symbol.iterator]();
const result: T[] = [];
const done = Symbol('Done');
let value: T | typeof done;
while (done !== (value = iterator.fastNext(done))) {
result.push(value);
}
return result;
}
toString(): string {
return `Stream(...<potentially empty>)`;
}
toJSON(): ToJSON<T[], 'Stream'> {
return {
dataType: 'Stream',
value: this.toArray(),
};
}
}
class FromStream<T> extends StreamBase<T> {
[Symbol.iterator]: () => FastIterator<T> = undefined as any;
constructor(createIterator: () => FastIterator<T>) {
super();
this[Symbol.iterator] = createIterator;
}
}
class PrependStream<T> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly item: OptLazy<T>
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new PrependIterator<T>(this.source[Symbol.iterator](), this.item);
}
first(): T {
return OptLazy(this.item);
}
last(): T {
return this.source.last(this.item);
}
count(): number {
return this.source.count() + 1;
}
forEach(
f: (value: T, index: number, halt: () => void) => void,
options: { state?: TraverseState } = {}
): void {
const { state = TraverseState() } = options;
if (state.halted) return;
f(OptLazy(this.item), state.nextIndex(), state.halt);
if (state.halted) return;
this.source.forEach(f, { state });
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: T, ...args: A) => T2,
...args: A
): Stream<T2> {
return new PrependStream(this.source.mapPure(mapFun, ...args), () =>
mapFun(OptLazy(this.item), ...args)
);
}
take(amount: number): Stream<T> {
if (amount <= 0) {
return emptyStream;
}
if (amount === 1) {
return StreamConstructorsImpl.of(OptLazy(this.item));
}
return new PrependStream(this.source.take(amount - 1), this.item);
}
drop(amount: number): Stream<T> {
if (amount <= 0) {
return this;
}
if (amount === 1) {
return this.source;
}
return this.source.drop(amount - 1);
}
minBy<O>(compare: (v1: T, v2: T) => number): T | O {
const token = Symbol();
const result = this.source.minBy(compare, token);
const itemValue = OptLazy(this.item);
if (token === result) {
return itemValue;
}
return compare(result, itemValue) <= 0 ? result : itemValue;
}
maxBy<O>(compare: (v1: T, v2: T) => number): T | O {
const token = Symbol();
const result = this.source.maxBy(compare, token);
const itemValue = OptLazy(this.item);
if (token === result) {
return itemValue;
}
return compare(result, itemValue) > 0 ? result : itemValue;
}
toArray(): T[] {
const result = this.source.toArray();
result.unshift(OptLazy(this.item));
return result;
}
}
class AppendStream<T> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly item: OptLazy<T>
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new AppendIterator<T>(this.source[Symbol.iterator](), this.item);
}
first(): T {
return this.source.first(this.item);
}
last(): T {
return OptLazy(this.item);
}
count(): number {
return this.source.count() + 1;
}
forEach(
f: (value: T, index: number, halt: () => void) => void,
options: { state?: TraverseState } = {}
): void {
const { state = TraverseState() } = options;
if (state.halted) return;
this.source.forEach(f, { state });
if (state.halted) return;
f(OptLazy(this.item), state.nextIndex(), state.halt);
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: T, ...args: A) => T2,
...args: A
): Stream<T2> {
return new AppendStream(this.source.mapPure(mapFun, ...args), () =>
mapFun(OptLazy(this.item), ...args)
);
}
minBy<O>(compare: (v1: T, v2: T) => number): T | O {
const token = Symbol();
const result = this.source.minBy(compare, token);
const itemValue = OptLazy(this.item);
if (token === result) {
return itemValue;
}
return compare(result, itemValue) <= 0 ? result : itemValue;
}
maxBy<O>(compare: (v1: T, v2: T) => number): T | O {
const token = Symbol();
const result = this.source.maxBy(compare, token);
const itemValue = OptLazy(this.item);
if (token === result) {
return itemValue;
}
return compare(result, itemValue) > 0 ? result : itemValue;
}
toArray(): T[] {
const result = this.source.toArray();
result.push(OptLazy(this.item));
return result;
}
}
class MapStream<T, T2> extends StreamBase<T2> {
constructor(
readonly source: Stream<T>,
readonly mapFun: (value: T, index: number) => T2
) {
super();
}
[Symbol.iterator](): FastIterator<T2> {
return new MapIterator<T, T2>(this.source[Symbol.iterator](), this.mapFun);
}
first<O>(otherwise?: OptLazy<O>): T2 | O {
const done = Symbol('Done');
const value = this.source.first(done);
if (done === value) return OptLazy(otherwise) as O;
return this.mapFun(value, 0);
}
last<O>(otherwise?: OptLazy<O>): T2 | O {
const done = Symbol('Done');
const value = this.source.last(done);
if (done === value) return OptLazy(otherwise) as O;
return this.mapFun(value, 0);
}
count(): number {
return this.source.count();
}
elementAt<O>(index: number, otherwise?: OptLazy<O>): T2 | O {
const done = Symbol('Done');
const value = this.source.elementAt(index, done);
if (done === value) return OptLazy(otherwise) as O;
return this.mapFun(value, index);
}
map<T3>(mapFun: (value: T2, index: number) => T3): Stream<T3> {
return new MapStream<T, T3>(this.source, (value, index) =>
mapFun(this.mapFun(value, index), index)
);
}
take(amount: number): Stream<T2> {
if (amount <= 0) {
return emptyStream;
}
return new MapStream(this.source.take(amount), this.mapFun);
}
drop(amount: number): Stream<T2> {
if (amount <= 0) {
return this;
}
return new MapStream(this.source.drop(amount), this.mapFun);
}
}
class MapPureStream<
T,
A extends readonly unknown[],
T2,
> extends StreamBase<T2> {
constructor(
readonly source: Stream<T>,
readonly mapFun: (value: T, ...args: A) => T2,
readonly args: A
) {
super();
}
[Symbol.iterator](): FastIterator<T2> {
return new MapPureIterator<T, A, T2>(
this.source[Symbol.iterator](),
this.mapFun,
this.args
);
}
first<O>(otherwise?: OptLazy<O>): T2 | O {
const done = Symbol('Done');
const value = this.source.first(done);
if (done === value) return OptLazy(otherwise) as O;
return this.mapFun(value, ...this.args);
}
last<O>(otherwise?: OptLazy<O>): T2 | O {
const done = Symbol('Done');
const value = this.source.last(done);
if (done === value) return OptLazy(otherwise) as O;
return this.mapFun(value, ...this.args);
}
count(): number {
return this.source.count();
}
elementAt<O>(index: number, otherwise?: OptLazy<O>): T2 | O {
const done = Symbol('Done');
const value = this.source.elementAt(index, done);
if (done === value) return OptLazy(otherwise) as O;
return this.mapFun(value, ...this.args);
}
mapPure<T3, A2 extends readonly unknown[]>(
mapFun: (value: T2, ...args: A2) => T3,
...args: A2
): Stream<T3> {
return new MapPureStream<T, A2, T3>(
this.source,
(value, ...args) => mapFun(this.mapFun(value, ...this.args), ...args),
args
);
}
take(amount: number): Stream<T2> {
if (amount <= 0) {
return emptyStream;
}
return new MapPureStream(this.source.take(amount), this.mapFun, this.args);
}
drop(amount: number): Stream<T2> {
if (amount <= 0) {
return this;
}
return new MapPureStream(this.source.drop(amount), this.mapFun, this.args);
}
}
class ConcatStream<T> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly otherSources: StreamSource<T>[]
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new ConcatIterator<T>(
this.source,
this.otherSources,
streamSourceHelpers
);
}
forEach(
f: (value: T, index: number, halt: () => void) => void,
options: { state?: TraverseState } = {}
): void {
const { state = TraverseState() } = options;
if (state.halted) return;
this.source.forEach(f, { state });
let sourceIndex = -1;
const sources = this.otherSources;
const length = sources.length;
while (!state.halted && ++sourceIndex < length) {
const source = sources[sourceIndex];
if (!isEmptyStreamSourceInstance(source)) {
fromStreamSource(source).forEach(f, { state });
}
}
}
forEachPure<A extends readonly unknown[]>(
f: (value: T, ...args: A) => void,
...args: A
): void {
this.source.forEachPure(f, ...args);
let sourceIndex = -1;
const sources = this.otherSources;
const length = sources.length;
while (++sourceIndex < length) {
const source = sources[sourceIndex];
if (!isEmptyStreamSourceInstance(source)) {
fromStreamSource(source).forEachPure(f, ...args);
}
}
}
last<O>(otherwise?: OptLazy<O>): T | O {
const sources = this.otherSources;
let sourceIndex = sources.length;
while (--sourceIndex >= 0) {
const source = sources[sourceIndex];
if (!isEmptyStreamSourceInstance(source)) {
const done = Symbol('Done');
const value = fromStreamSource(source).last(done);
if (done !== value) return value;
}
}
return this.source.last(otherwise!);
}
count(): number {
let result = this.source.count();
const sources = this.otherSources;
const length = sources.length;
let sourceIndex = -1;
while (++sourceIndex < length) {
const source = sources[sourceIndex];
if (!isEmptyStreamSourceInstance(source)) {
result += fromStreamSource(source).count();
}
}
return result;
}
filterPure<A extends readonly unknown[]>(
options: {
pred: (value: T, ...args: A) => boolean;
negate?: boolean | undefined;
},
...args: A
): any {
return new ConcatStream(
this.source.filterPure(options, ...args),
this.otherSources.map((source) =>
fromStreamSource(source).filterPure(options, ...args)
)
) as any;
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: T, ...args: A) => T2,
...args: A
): Stream<T2> {
return new ConcatStream(
this.source.mapPure(mapFun, ...args),
this.otherSources.map((source) =>
fromStreamSource(source).mapPure(mapFun, ...args)
)
);
}
concat<T2>(...others2: StreamSource<T2>[]): any {
return new ConcatStream<T | T2>(
this.source,
(this.otherSources as StreamSource<T | T2>[]).concat(others2)
);
}
toArray(): T[] {
let result: T[] = this.source.toArray();
let sourceIndex = -1;
const sources = this.otherSources;
const length = sources.length;
while (++sourceIndex < length) {
const source = sources[sourceIndex];
if (!isEmptyStreamSourceInstance(source)) {
result = result.concat(fromStreamSource(source).toArray());
}
}
return result;
}
}
class IndexedStream<T> extends StreamBase<[number, T]> {
constructor(
readonly source: Stream<T>,
readonly startIndex: number
) {
super();
}
[Symbol.iterator](): FastIterator<[number, T]> {
return new IndexedIterator<T>(
this.source[Symbol.iterator](),
this.startIndex
);
}
first<O>(otherwise?: OptLazy<O> | undefined): [number, T] | O {
const token = Symbol();
const sourceFirst = this.source.first(token);
if (token === sourceFirst) {
return OptLazy(otherwise) as O;
}
return [0, sourceFirst];
}
count(): number {
return this.source.count();
}
elementAt<O>(
index: number,
otherwise?: OptLazy<O> | undefined
): [number, T] | O {
const token = Symbol();
const elementAtSource = this.source.elementAt(index, token);
if (token === elementAtSource) {
return OptLazy(otherwise) as O;
}
return [index, elementAtSource];
}
take(amount: number): Stream<[number, T]> {
if (amount <= 0) {
return emptyStream;
}
return new IndexedStream(this.source.take(amount), this.startIndex);
}
toArray(): Array<[number, T]> {
let index = this.startIndex;
const iterator = this.source[Symbol.iterator]();
const result: Array<[number, T]> = [];
const done = Symbol('Done');
let value: T | typeof done;
while (done !== (value = iterator.fastNext(done))) {
result.push([index++, value]);
}
return result;
}
}
class FilterStream<T> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly pred: (value: T, index: number, halt: () => void) => boolean,
readonly negate = false
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new FilterIterator<T>(
this.source[Symbol.iterator](),
this.pred,
this.negate
);
}
filterPure<A extends readonly unknown[]>(
options: {
pred: (value: T, ...args: A) => boolean;
negate?: boolean | undefined;
},
...args: A
): Stream<never> {
const { pred, negate = false } = options;
const { pred: thisPred, negate: thisNegate } = this;
return new FilterStream(
this.source,
(value, index, halt) =>
thisPred(value, index, halt) !== thisNegate &&
pred(value, ...args) !== negate
) as any;
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: T, ...args: A) => T2,
...args: A
): Stream<T2> {
const { pred, negate } = this;
return new CollectStream(this.source, (value, index, skip, halt) =>
pred(value, index, halt) !== negate ? mapFun(value, ...args) : skip
);
}
}
class FilterPureStream<T, A extends readonly unknown[]> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly pred: (value: T, ...args: A) => boolean,
readonly args: A,
readonly negate = false
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new FilterPureIterator<T, A>(
this.source[Symbol.iterator](),
this.pred,
this.args,
this.negate
);
}
filterPure<A extends readonly unknown[]>(
options: {
pred: (value: T, ...args: A) => boolean;
negate?: boolean | undefined;
},
...args: A
): Stream<never> {
const { pred, negate = false } = options;
const thisPred = this.pred;
const thisArgs = this.args;
const thisNegate = this.negate;
return new FilterPureStream(
this.source,
(value, ...args) => {
return (
thisPred(value, ...thisArgs) !== thisNegate &&
pred(value, ...args) !== negate
);
},
args
) as any;
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: T, ...args: A) => T2,
...args: A
): Stream<T2> {
const { pred, negate, args: thisArgs } = this;
return new CollectStream(this.source, (value, _, skip) =>
pred(value, ...thisArgs) !== negate ? mapFun(value, ...args) : skip
);
}
}
class CollectStream<T, R> extends StreamBase<R> {
constructor(
readonly source: Stream<T>,
readonly collectFun: CollectFun<T, R>
) {
super();
}
[Symbol.iterator](): FastIterator<R> {
return new CollectIterator<T, R>(
this.source[Symbol.iterator](),
this.collectFun
);
}
filterPure<A extends readonly unknown[]>(
options: {
pred: (value: R, ...args: A) => boolean;
negate?: boolean | undefined;
},
...args: A
): any {
const { pred, negate = false } = options;
const { collectFun } = this;
return new CollectStream(this.source, (value, index, skip, halt) => {
const result = collectFun(value, index, skip, halt);
if (skip === result || pred(result, ...args) === negate) {
return skip;
}
return result;
}) as any;
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: R, ...args: A) => T2,
...args: A
): Stream<T2> {
const { collectFun } = this;
return new CollectStream(this.source, (value, index, skip, halt) => {
const result = collectFun(value, index, skip, halt);
if (skip === result) {
return skip;
}
return mapFun(result, ...args);
});
}
}
class DropWhileStream<T> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly pred: (value: T, index: number) => boolean,
readonly negate: boolean
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new DropWhileIterator<T>(
this.source[Symbol.iterator](),
this.pred,
this.negate
);
}
}
class TakeStream<T> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly amount: number
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new TakeIterator<T>(this.source[Symbol.iterator](), this.amount);
}
take(amount: number): Stream<T> {
if (amount <= 0) {
return emptyStream;
}
if (amount >= this.amount) {
return this;
}
return this.source.take(amount);
}
}
class DropStream<T> extends StreamBase<T> {
constructor(
readonly source: Stream<T>,
readonly amount: number
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new DropIterator<T>(this.source[Symbol.iterator](), this.amount);
}
drop(amount: number): Stream<T> {
if (amount <= 0) {
return this;
}
return this.source.drop(this.amount + amount);
}
}
class SlowIteratorAdapter<T> implements FastIterator<T> {
constructor(readonly source: Iterator<T>) {}
next(): IteratorResult<T> {
return this.source.next();
}
fastNext<O>(otherwise?: OptLazy<O>): T | O {
const result = this.source.next();
if (result.done) {
return OptLazy(otherwise) as O;
}
return result.value;
}
}
class FromIterable<T> extends StreamBase<T> {
constructor(readonly iterable: Iterable<T>) {
super();
}
[Symbol.iterator](): FastIterator<T> {
const iterator = this.iterable[Symbol.iterator]();
if (isFastIterator(iterator)) return iterator;
return new SlowIteratorAdapter<T>(iterator);
}
}
class EmptyStream<T = any> extends StreamBase<T> implements Stream<T> {
[Symbol.iterator](): FastIterator<T> {
return emptyFastIterator;
}
stream(): this {
return this;
}
assumeNonEmpty(): never {
RimbuError.throwEmptyCollectionAssumedNonEmptyError();
}
equals(other: StreamSource<T>): boolean {
const done = Symbol('Done');
return done === fromStreamSource(other)[Symbol.iterator]().fastNext(done);
}
prepend(value: OptLazy<T>): Stream.NonEmpty<T> {
return StreamConstructorsImpl.of(OptLazy(value));
}
append(value: OptLazy<T>): Stream.NonEmpty<T> {
return StreamConstructorsImpl.of(OptLazy(value));
}
forEach(): void {
//
}
forEachPure(): void {
//
}
indexed(): Stream<[number, T]> {
return this as any;
}
map<T2>(): Stream<T2> {
return this as any;
}
mapPure<T2>(): Stream<T2> {
return this as any;
}
flatMap<T2>(): Stream<T2> {
return this as any;
}
flatZip<T2>(): Stream<[T, T2]> {
return this as any;
}
transform<R>(transformer: Transformer<T, R>): Stream<R> {
return fromStreamSource(transformer.compile().getOutput());
}
filter(): any {
return this;
}
filterPure(): any {
return this;
}
withOnly(): any {
return this;
}
without(): any {
return this;
}
collect<R>(): Stream<R> {
return this as any;
}
first<O>(otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
last<O>(otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
single<O>(otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
count(): 0 {
return 0;
}
countElement(): 0 {
return 0;
}
find<O>(
pred: (value: any, index: number) => boolean,
options: { otherwise?: OptLazy<O>; occurrance?: number | undefined } = {}
): O {
const { otherwise } = options;
return OptLazy(otherwise) as O;
}
elementAt<O>(index: number, otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
indicesWhere(): Stream<number> {
return this as any;
}
indicesOf(): Stream<number> {
return this as any;
}
indexWhere(): undefined {
return undefined;
}
indexOf(): undefined {
return undefined;
}
some(): false {
return false;
}
every(): true {
return true;
}
contains(): false {
return false;
}
containsSlice(): false {
return false;
}
takeWhile(): Stream<T> {
return this;
}
dropWhile(): Stream<T> {
return this;
}
take(): Stream<T> {
return this;
}
drop(): Stream<T> {
return this;
}
repeat(): Stream<T> {
return this;
}
concat<T2>(...others: ArrayNonEmpty<StreamSource<T2>>): any {
if (others.every(isEmptyStreamSourceInstance)) return this;
const [source1, source2, ...sources] = others;
if (undefined === source2) return source1;
return fromStreamSource(source1).concat(source2, ...sources);
}
min<O>(otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
minBy<O>(compare: any, otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
max<O>(otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
maxBy<O>(compare: any, otherwise?: OptLazy<O>): O {
return OptLazy(otherwise) as O;
}
intersperse(): Stream<T> {
return this;
}
join({ start = '', end = '', ifEmpty = undefined } = {}): string {
if (undefined !== ifEmpty) return ifEmpty;
return start.concat(end);
}
mkGroup({
start = emptyStream as StreamSource<T>,
end = emptyStream as StreamSource<T>,
} = {}): Stream.NonEmpty<T> {
return fromStreamSource(start).concat(end) as any;
}
splitOn<R>(): Stream<R> {
return this as any;
}
splitWhere<R>(): Stream<R> {
return this as any;
}
splitOnSlice<R>(): Stream<R> {
return this as any;
}
distinctPrevious(): Stream<T> {
return this;
}
window<R>(): Stream<R> {
return this as any;
}
partition(
pred: any,
options: {
collectorTrue?: any;
collectorFalse?: any;
} = {}
): [any, any] {
const {
collectorTrue = Reducer.toArray(),
collectorFalse = Reducer.toArray(),
} = options;
return [
collectorTrue.compile().getOutput(),
collectorFalse.compile().getOutput(),
];
}
fold<R>(init: OptLazy<R>): R {
return OptLazy(init);
}
foldStream<R>(): Stream<R> {
return this as any;
}
reduce(shape: any): any {
const reducer = Reducer.combine(shape);
const instance = reducer.compile();
return instance.getOutput();
}
reduceStream(): any {
return this;
}
toArray(): [] {
return [];
}
toString(): string {
return `Stream(<empty>)`;
}
toJSON(): ToJSON<T[], 'Stream'> {
return {
dataType: 'Stream',
value: [],
};
}
}
class ArrayStream<T> extends StreamBase<T> {
readonly length: number;
constructor(
readonly array: readonly T[],
readonly startIndex = 0,
readonly endIndex = array.length - 1,
readonly reversed = false
) {
super();
this.length = endIndex - startIndex + 1;
}
[Symbol.iterator](): FastIterator<T> {
if (!this.reversed) {
return new ArrayIterator(this.array, this.startIndex, this.endIndex);
}
return new ArrayReverseIterator(this.array, this.startIndex, this.endIndex);
}
forEach(
f: (value: T, index: number, halt: () => void) => void,
options: { state?: TraverseState } = {}
): void {
const { state = TraverseState() } = options;
const startIndex = this.startIndex;
const endIndex = this.endIndex;
const array = this.array;
const { halt } = state;
if (!this.reversed) {
let i = this.startIndex - 1;
while (!state.halted && ++i <= endIndex) {
f(array[i], state.nextIndex(), halt);
}
} else {
let i = endIndex + 1;
while (!state.halted && --i >= startIndex) {
f(array[i], state.nextIndex(), halt);
}
}
}
first<O>(otherwise?: OptLazy<O>): T | O {
if (this.length <= 0) {
return OptLazy(otherwise) as O;
}
if (!this.reversed) {
return this.array[this.startIndex];
}
return this.array[this.endIndex];
}
last<O>(otherwise?: OptLazy<O>): T | O {
if (this.length <= 0) {
return OptLazy(otherwise) as O;
}
if (!this.reversed) {
return this.array[this.endIndex];
}
return this.array[this.startIndex];
}
count(): number {
return this.endIndex - this.startIndex + 1;
}
find<O>(
pred: (value: T, index: number) => boolean,
options: {
occurrance?: number | undefined;
negate?: boolean | undefined;
otherwise?: OptLazy<O>;
} = {}
): T | O {
const { occurrance = 1, negate = false, otherwise } = options;
const startIndex = this.startIndex;
const endIndex = this.endIndex;
const array = this.array;
let remain = occurrance;
let index = 0;
if (!this.reversed) {
let i = startIndex - 1;
while (++i <= endIndex) {
const value = array[i];
if (pred(value, index++) !== negate && --remain <= 0) return value;
}
} else {
let i = endIndex + 1;
while (--i >= startIndex) {
const value = array[i];
if (pred(value, index++) !== negate && --remain <= 0) return value;
}
}
return OptLazy(otherwise) as O;
}
elementAt<O>(index: number, otherwise?: OptLazy<O>): T | O {
if (index < 0 || index >= this.length) {
return OptLazy(otherwise) as O;
}
if (!this.reversed) {
return this.array[index + this.startIndex];
}
return this.array[this.endIndex - index];
}
indexOf(
searchValue: T,
options: {
occurrance?: number | undefined;
eq?: Eq<T> | undefined;
negate?: boolean | undefined;
} = {}
): number | undefined {
const { occurrance = 1 } = options;
if (occurrance <= 0) return undefined;
const { eq = Object.is, negate = false } = options;
let remain = occurrance;
const startIndex = this.startIndex;
const endIndex = this.endIndex;
const array = this.array;
if (!this.reversed) {
let i = startIndex - 1;
while (++i <= endIndex) {
if (eq(array[i], searchValue) !== negate && --remain <= 0)
return i - startIndex;
}
} else {
let i = endIndex + 1;
while (--i >= startIndex) {
if (eq(array[i], searchValue) !== negate && --remain <= 0)
return endIndex - i;
}
}
return undefined;
}
take(amount: number): Stream<T> {
if (amount <= 0) return emptyStream;
if (amount >= this.length) return this;
if (!this.reversed) {
return new ArrayStream(
this.array,
this.startIndex,
this.startIndex + amount - 1,
this.reversed
);
}
return new ArrayStream(
this.array,
this.endIndex - (amount - 1),
this.endIndex,
this.reversed
);
}
drop(amount: number): Stream<T> {
if (amount <= 0) return this;
if (amount >= this.length) return emptyStream;
if (!this.reversed) {
return new ArrayStream(
this.array,
this.startIndex + amount,
this.endIndex,
this.reversed
);
}
return new ArrayStream(
this.array,
this.startIndex,
this.endIndex - amount,
this.reversed
);
}
toArray(): T[] {
const array = this.array;
if (typeof array === 'string') {
return super.toArray();
}
if (this.reversed) {
// use normal iterator
return super.toArray();
}
return array.slice(this.startIndex, this.endIndex + 1);
}
}
class AlwaysStream<T> extends StreamBase<T> {
constructor(readonly value: T) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new AlwaysIterator(this.value);
}
first(): T {
return this.value;
}
append(): Stream.NonEmpty<T> {
return this as any;
}
forEach(
f: (value: T, index: number, halt: () => void) => void,
options: { state?: TraverseState } = {}
): void {
const { state = TraverseState() } = options;
const value = this.value;
while (!state.halted) {
f(value, state.nextIndex(), state.halt);
}
}
elementAt(): T {
return this.value;
}
repeat(): Stream<T> {
return this as any;
}
concat<T2>(): Stream.NonEmpty<T | T2> {
return this.assumeNonEmpty();
}
min(): T {
return this.value;
}
minBy(): T {
return this.value;
}
max(): T {
return this.value;
}
maxBy(): T {
return this.value;
}
}
class MapApplyStream<
T extends readonly unknown[],
A extends readonly unknown[],
R,
> extends StreamBase<R> {
constructor(
readonly source: StreamSource<T>,
readonly f: (...args: [...T, ...A]) => R,
readonly args: A
) {
super();
}
[Symbol.iterator](): FastIterator<R> {
return new MapApplyIterator(
this.source,
this.f,
this.args,
streamSourceHelpers
);
}
mapPure<T2, A extends readonly unknown[]>(
mapFun: (value: R, ...args: A) => T2,
...args: A
): Stream<T2> {
const { f, args: thisArgs } = this;
return new MapApplyStream(
this.source,
(...args2) => mapFun(f(...args2), ...args),
thisArgs
);
}
}
class FilterApplyStream<
T extends readonly unknown[],
A extends readonly unknown[],
> extends StreamBase<T> {
constructor(
readonly source: StreamSource<T>,
readonly pred: (...args: [...T, ...A]) => boolean,
readonly args: A,
readonly negate = false
) {
super();
}
[Symbol.iterator](): FastIterator<T> {
return new FilterApplyIterator(
this.source,
this.pred,
this.args,
this.negate,
streamSourceHelpers
);
}
}
class RangeStream extends StreamBase<number> {
constructor(
readonly start: number,
readonly end?: number,
readonly delta: number = 1
) {
super();
}
[Symbol.iterator](): FastIterator<number> {
if (this.delta >= 0) {
return new RangeUpIterator(this.start, this.end, this.delta);
}
return new RangeDownIterator(this.start, this.end, this.delta);
}
}
class ReducerStream<T, R = T> extends StreamBase<R> {
constructor(
readonly source: Stream<T>,
readonly reducer: Reducer<T, R>
) {
super();
}
[Symbol.iterator](): FastIterator<R> {
return new ReducerFastIterator<T, R>(
this.source[Symbol.iterator](),
this.reducer.compile()
);
}
}
class TransformerStream<T, R = T> extends StreamBase<R> {
constructor(
readonly source: Stream<T>,
readonly transformer: Transformer<T, R>
) {
super();
}
[Symbol.iterator](): FastIterator<R> {
return new TransformerFastIterator<T, R>(
this.source[Symbol.iterator](),
this.transformer.compile()
);
}
}
const emptyStream: Stream<any> = Object.freeze(new EmptyStream());
function isStream(obj: any): obj is Stream<any> {
return obj instanceof StreamBase;
}
export const fromStreamSource: {
<T>(source: StreamSource.NonEmpty<T>): Stream.NonEmpty<T>;
<T>(source: StreamSource<T>): Stream<T>;
} = (source: StreamSource<any>): any => {
if (undefined === source || isEmptyStreamSourceInstance(source))
return emptyStream;
if (isStream(source)) return source;
if (typeof source === 'object' && `stream` in source) return source.stream();
if (Array.isArray(source)) {
if (source.length <= 0) return emptyStream;
return new ArrayStream(source);
}
return new FromIterable(source);
};
/**
* Returns true if the given `source` StreamSource is known to be empty.
* @param source - a StreamSource
* @note
* If this function returns false, it does not guarantee that the Stream is not empty. It only
* means that it is not known if it is empty.
*/
export function isEmptyStreamSourceInstance(
source: StreamSource<any>
): boolean {
if (source === '') return true;
if (typeof source === 'object') {
if (source === emptyStream || source === null) return true;
if (`length` in source && (source as any).length === 0) return true;
if (`size` in source && (source as any).size === 0) return true;
if (`isEmpty` in source && (source as any).isEmpty === true) return true;
}
return false;
}
const streamSourceHelpers = {
fromStreamSource,
isEmptyStreamSourceInstance,
};
export type StreamSourceHelpers = typeof streamSourceHelpers;
ex