Skip to content

Commit

Permalink
feat: adding aggregate operation
Browse files Browse the repository at this point in the history
  • Loading branch information
Farenheith committed Nov 7, 2024
1 parent 1e37075 commit b7da7fd
Show file tree
Hide file tree
Showing 13 changed files with 449 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/async/aggregate-async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { basicAsync } from './basic-ingredients-async';
import { aggregateRecipe } from '../recipes';

export const aggregateAsync = aggregateRecipe(basicAsync);
1 change: 1 addition & 0 deletions src/async/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './aggregate-async';
export * from './all-async';
export * from './any-async';
export * from './append-async';
Expand Down
2 changes: 2 additions & 0 deletions src/mounters/fluent-async-functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { toMapChainAsync } from './../async/to-map-chain-async';
import {
aggregateAsync,
anyAsync,
appendAsync,
avgAsync,
Expand Down Expand Up @@ -119,6 +120,7 @@ export const asyncSpecial = {
};

export const asyncResolvingFuncs = {
aggregate: aggregateAsync,
count: countAsync,
first: firstAsync,
last: lastAsync,
Expand Down
4 changes: 4 additions & 0 deletions src/mounters/fluent-functions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
aggregate,
any,
branch,
contains,
Expand Down Expand Up @@ -62,6 +63,7 @@ import {
toMapChainReduce,
} from '../sync';
import {
aggregateAsync,
allAsync,
avgAsync,
anyAsync,
Expand Down Expand Up @@ -166,6 +168,8 @@ export const specialAsync = {
};

export const resolvingFuncs = {
aggregate,
aggregateAsync,
count,
countAsync,
emit,
Expand Down
109 changes: 109 additions & 0 deletions src/recipes/aggregate-recipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { AnyIterable } from 'augmentative-iterable';
import { AverageStepper } from '../types';
import { getAverageStepper } from '../utils';
import { BasicIngredients } from './ingredients';

const contextSymbol = Symbol('context');

class Context {
id = 0;
customIds: any = {};
modMultiplyId = 0;
modMultiplySymbols: any = {};
modSumId = 0;
modSumSymbols: any = {};
[key: string]: any;

resetIds() {
this.modSumId = this.modMultiplyId = this.id = 0;
}
defaultId() {
return (this.customIds[this.id++] ??= Symbol(this.id));
}
getModMultiplyId(id: any) {
return typeof id === 'symbol'
? id
: (this.modMultiplySymbols[id] ??= Symbol(id));
}
getModSumId(id: any) {
return typeof id === 'symbol'
? id
: (this.modSumSymbols[id] ??= Symbol(id));
}
}

class Aggregations {
[contextSymbol] = new Context();

sum(value: number, id: string | number = this[contextSymbol].defaultId()) {
const context = (this[contextSymbol].sum ??= {});
return (context[id] = (context[id] ?? 0) + value);
}
multiply(
value: number,
id: string | number = this[contextSymbol].defaultId(),
) {
const context = (this[contextSymbol].multiply ??= {});
return (context[id] = (context[id] ?? 1) * value);
}
max(
value: string | number,
id: string | number = this[contextSymbol].defaultId(),
) {
const context = (this[contextSymbol].max ??= {});
if (context[id] === undefined || context[id] < value) context[id] = value;
return context[id];
}
avg(value: number, id: string | number = this[contextSymbol].defaultId()) {
const context = (this[contextSymbol].avg ??= {});
if (context[id] === undefined) context[id] = getAverageStepper();
const stepper: AverageStepper = context[id];
stepper.step(value);
return stepper.avg;
}
min<T>(value: T, id: string | number = this[contextSymbol].defaultId()) {
const context = (this[contextSymbol].min ??= {});
if (context[id] === undefined || context[id] > value) context[id] = value;
return context[id];
}
first<T>(value: T, id: string | number = this[contextSymbol].defaultId()) {
const context = (this[contextSymbol].first ??= {});
if (context[id] === undefined) context[id] = { value };
return context[id].value;
}
last<T>(value: T) {
return value;
}
modSum(value: number, mod: number, id = this[contextSymbol].defaultId()) {
const context = (this[contextSymbol].modSumInternal ??= {});
return (context[id] =
this.sum(value, this[contextSymbol].getModSumId(id)) % mod);
}
modMultiply(
value: number,
mod: number,
id = this[contextSymbol].defaultId(),
) {
const context = (this[contextSymbol].modMultiplyInternal ??= {});
return (context[id] =
this.multiply(value, this[contextSymbol].getModMultiplyId(id)) % mod);
}
}

export function aggregateRecipe(ingredients: BasicIngredients): any {
const { forEach, resolver } = ingredients;
return function aggregate<T>(
this: AnyIterable<T>,
callback: (a: T, agg: Aggregations, prev: any) => any,
result?: any,
) {
const agg = new Aggregations();
return resolver(
forEach.call(this, (item: T) => {
agg[contextSymbol].resetIds();
result = callback(item, agg, result);
}),
() => result,
);
};
}
1 change: 1 addition & 0 deletions src/recipes/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './aggregate-recipe';
export * from './append-recipe';
export * from './augment-iterable-recipe';
export * from './avg-recipe';
Expand Down
4 changes: 4 additions & 0 deletions src/sync/aggregate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { basic } from './basic-ingredients';
import { aggregateRecipe } from '../recipes';

export const aggregate = aggregateRecipe(basic);
1 change: 1 addition & 0 deletions src/sync/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './aggregate';
export * from './all';
export * from './any';
export * from './append';
Expand Down
1 change: 1 addition & 0 deletions src/types/fluent-async-iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as f from './function-types';

declare module './base' {
interface FluentAsyncIterable<T> {
aggregate: f.AsyncAggregateFunction<T>;
withIndex: f.AsyncWithIndexFunction<T>;
takeWhile: f.AsyncTakeWhileFunction<T>;
take: f.AsyncTakeFunction<T>;
Expand Down
2 changes: 2 additions & 0 deletions src/types/fluent-iterable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as f from './function-types';

declare module './base' {
interface FluentIterable<T> {
aggregate: f.AggregateFunction<T>;
aggregateAsync: f.AsyncAggregateFunction<T>;
withIndex: f.WithIndexFunction<T>;
takeWhile: f.TakeWhileFunction<T>;
takeWhileAsync: f.AsyncTakeWhileFunction<T>;
Expand Down
36 changes: 36 additions & 0 deletions src/types/function-types/aggregate-function.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
export interface Aggregations {
sum(value: number, id?: string | number): number;
multiply(value: number, id?: string | number): number;
max<T extends string | number>(value: T, id?: string | number): T;
avg(value: number, id?: string | number): number;
min<T>(value: T, id?: string | number): T;
first<T>(value: T, id?: string | number): T;
last<T>(value: T, id?: string | number): T;
modSum(value: number, mod: number, id?: string | number): number;
modMultiply(value: number, mod: number, id?: string | number): number;
}

export interface AggregateFunction<T> {
/**
* Execute aggregations, returning the last result. This is a resolving operation
* The aggregations available are: sum, multiply, max, avg, min, first, last, modSum, modMultiply
* @param callback The callback to execute the aggregations. It receives a second param: an object containing all the available aggregations
* @returns the last method result
*/
<R, I extends R | undefined = R | undefined>(
callback: (item: T, agg: Aggregations, acc: I) => R,
initialize?: I,
): R;
}
export interface AsyncAggregateFunction<T> {
/**
* Execute aggregations, returning the last result. This is a resolving operation
* The aggregations available are: sum, multiply, max, avg, min, first, last, modSum, modMultiply
* @param callback The callback to execute the aggregations. It receives a second param: an object containing all the available aggregations
* @returns the last method result
*/
<R, I extends R | undefined = R | undefined>(
callback: (item: T, agg: Aggregations, acc: R | undefined) => R,
initialize?: I,
): Promise<R>;
}
1 change: 1 addition & 0 deletions src/types/function-types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './aggregate-function';
export * from './all-function';
export * from './any-function';
export * from './append-function';
Expand Down
Loading

0 comments on commit b7da7fd

Please sign in to comment.