Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(common): add reducer combineObj method and rename old combine to… #115

Merged
merged 1 commit into from
Oct 2, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 118 additions & 9 deletions deno_dist/common/async-reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -574,15 +574,15 @@ export namespace AsyncReducer {
* ```
*/
export const count: {
(): AsyncReducer<any, number>;
(): AsyncReducer<never, number>;
<T>(pred: (value: T, index: number) => MaybePromise<boolean>): AsyncReducer<
T,
number
>;
} = (
pred?: (value: any, index: number) => MaybePromise<boolean>
): AsyncReducer<any, number> => {
if (undefined === pred) return createMono(0, (_, __, i): number => i + 1);
): AsyncReducer<never, number> => {
if (undefined === pred) return createOutput(0, (_, __, i): number => i + 1);

return createOutput(0, async (state, next, i): Promise<number> => {
if (await pred?.(next, i)) return state + 1;
Expand Down Expand Up @@ -843,7 +843,7 @@ export namespace AsyncReducer {
* // => false
* ```
*/
export const isEmpty = createOutput<any, boolean>(
export const isEmpty = createOutput<never, boolean>(
true,
(_, __, ___, halt): false => {
halt();
Expand All @@ -859,7 +859,7 @@ export namespace AsyncReducer {
* // => true
* ```
*/
export const nonEmpty = createOutput<any, boolean>(
export const nonEmpty = createOutput<never, boolean>(
false,
(_, __, ___, halt): true => {
halt();
Expand Down Expand Up @@ -956,16 +956,18 @@ export namespace AsyncReducer {
}

/**
* Returns a `Reducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in an array.
* Returns an `AsyncReducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in an array.
* @param reducers - 2 or more reducers to combine
* @example
* ```ts
* const red = Reducer.combine(Reducer.sum, Reducer.average)
* console.log(Stream.range({amount: 9 }).reduce(red))
* const red = AsyncReducer.combineArr(AsyncReducer.sum, AsyncReducer.average)
*
* await AsyncStream.from(Stream.range({ amount: 9 }))
* .reduce(red)
* // => [36, 4]
* ```
*/
export function combine<
export function combineArr<
T,
R extends readonly [unknown, unknown, ...unknown[]]
>(
Expand Down Expand Up @@ -1036,4 +1038,111 @@ export namespace AsyncReducer {
) as any
);
}

/**
* Returns an `AsyncReducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in the shape of the given object.
* @typeparam T - the input type for all the reducers
* @typeparam R - the result object shape
* @param reducerObj - an object of keys, and reducers corresponding to those keys
* @example
* ```ts
* const red = AsyncReducer.combineObj({
* theSum: Reducer.sum,
* theAverage: Reducer.average
* });
*
* await AsyncStream.from(Stream.range({ amount: 9 }))
* .reduce(red));
* // => { theSum: 36, theAverage: 4 }
* ```
*/
export function combineObj<T, R extends { readonly [key: string]: unknown }>(
reducerObj: { readonly [K in keyof R]: AsyncReducer<T, R[K]> } & Record<
string,
AsyncReducer<T, unknown>
>
): AsyncReducer<T, R> {
const createState = async (): Promise<
Record<
keyof R,
{
reducer: AsyncReducer<T, unknown>;
halted: boolean;
halt(): void;
state: unknown;
}
>
> => {
const entries = await Promise.all(
Object.entries(reducerObj).map(async ([key, reducer]) => {
const result = {
reducer,
halted: false,
halt(): void {
result.halted = true;
},
state: await AsyncOptLazy.toMaybePromise(reducer.init),
};

return [key, result] as const;
})
);

return Object.fromEntries(entries) as any;
};

return create<
T,
R,
Record<
keyof R,
{
reducer: AsyncReducer<T, unknown>;
halted: boolean;
halt(): void;
state: unknown;
}
>
>(
createState,
async (allState, next, index, halt) => {
let anyNotHalted = false;

await Promise.all(
Object.values(allState).map(async (red) => {
if (red.halted) {
return;
}

red.state = await red.reducer.next(
red.state,
next,
index,
red.halt
);

if (!red.halted) {
anyNotHalted = true;
}
})
);

if (!anyNotHalted) {
halt();
}

return allState;
},
async (allState) => {
const entries = await Promise.all(
Object.entries(allState).map(
async ([key, st]) =>
[key, await st.reducer.stateToResult(st.state)] as const
)
);

return Object.fromEntries(entries) as any;
}
);
}
}
119 changes: 109 additions & 10 deletions deno_dist/common/reducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,10 +501,12 @@ export namespace Reducer {
* ```
*/
export const count: {
(): Reducer<any, number>;
(): Reducer<never, number>;
<T>(pred: (value: T, index: number) => boolean): Reducer<T, number>;
} = (pred?: (value: any, index: number) => boolean): Reducer<any, number> => {
if (undefined === pred) return createMono(0, (_, __, i): number => i + 1);
} = (
pred?: (value: unknown, index: number) => boolean
): Reducer<never, number> => {
if (undefined === pred) return createOutput(0, (_, __, i): number => i + 1);

return createOutput(0, (state, next, i): number => {
if (pred?.(next, i)) return state + 1;
Expand Down Expand Up @@ -749,7 +751,7 @@ export namespace Reducer {
* // => false
* ```
*/
export const isEmpty = createOutput<any, boolean>(
export const isEmpty = createOutput<never, boolean>(
true,
(_, __, ___, halt): false => {
halt();
Expand All @@ -765,7 +767,7 @@ export namespace Reducer {
* // => true
* ```
*/
export const nonEmpty = createOutput<any, boolean>(
export const nonEmpty = createOutput<never, boolean>(
false,
(_, __, ___, halt): true => {
halt();
Expand Down Expand Up @@ -866,12 +868,12 @@ export namespace Reducer {
* @param reducers - 2 or more reducers to combine
* @example
* ```ts
* const red = Reducer.combine(Reducer.sum, Reducer.average)
* const red = Reducer.combineArr(Reducer.sum, Reducer.average)
* console.log(Stream.range({amount: 9 }).reduce(red))
* // => [36, 4]
* ```
*/
export function combine<
export function combineArr<
T,
R extends readonly [unknown, unknown, ...unknown[]]
>(
Expand Down Expand Up @@ -904,21 +906,118 @@ export namespace Reducer {

let i = -1;
const len = allState.length;

while (++i < len) {
const red = allState[i];

if (red.halted) continue;
if (red.halted) {
continue;
}

red.state = red.reducer.next(red.state, next, index, red.halt);
if (!red.halted) anyNotHalted = true;

if (!red.halted) {
anyNotHalted = true;
}
}

if (!anyNotHalted) halt();
if (!anyNotHalted) {
halt();
}

return allState;
},
(allState) =>
allState.map((st) => st.reducer.stateToResult(st.state)) as any
);
}

/**
* Returns a `Reducer` that combines multiple input `reducers` by providing input values to all of them and collecting the outputs in the shape of the given object.
* @typeparam T - the input type for all the reducers
* @typeparam R - the result object shape
* @param reducerObj - an object of keys, and reducers corresponding to those keys
* @example
* ```ts
* const red = Reducer.combineObj({
* theSum: Reducer.sum,
* theAverage: Reducer.average
* });
*
* Stream.range({ amount: 9 }).reduce(red);
* // => { theSum: 36, theAverage: 4 }
* ```
*/
export function combineObj<T, R extends { readonly [key: string]: unknown }>(
reducerObj: { readonly [K in keyof R]: Reducer<T, R[K]> } & Record<
string,
Reducer<T, unknown>
>
): Reducer<T, R> {
const createState = (): Record<
keyof R,
{
reducer: Reducer<T, unknown>;
halted: boolean;
halt(): void;
state: unknown;
}
> => {
const allState: any = {};

for (const key in reducerObj) {
const reducer = reducerObj[key];

const result = {
reducer,
halted: false,
halt(): void {
result.halted = true;
},
state: OptLazy(reducer.init),
};

allState[key] = result;
}

return allState;
};

return create<T, R, ReturnType<typeof createState>>(
createState,
(allState, next, index, halt) => {
let anyNotHalted = false;

for (const key in allState) {
const red = allState[key];

if (red.halted) {
continue;
}

red.state = red.reducer.next(red.state, next, index, red.halt);

if (!red.halted) {
anyNotHalted = true;
}
}

if (!anyNotHalted) {
halt();
}

return allState;
},
(allState) => {
const result: any = {};

for (const key in allState) {
const st = allState[key];
result[key] = st.reducer.stateToResult(st.state);
}

return result;
}
);
}
}
Loading