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 Apr 19, 2023
1 parent 6af4228 commit 64c9320
Show file tree
Hide file tree
Showing 15 changed files with 452 additions and 5 deletions.
21 changes: 17 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@
"dependencies": {
"augmentative-iterable": "^1.5.8",
"extension-methods": "^1.0.1",
"typed-emitter": "^1.4.0"
"typed-emitter": "^1.4.0",
"uuid": "^9.0.0"
},
"devDependencies": {
"@codibre/confs": "^1.1.0",
Expand All @@ -104,6 +105,7 @@
"@types/node": "^16.11.6",
"@types/sinon": "^10.0.6",
"@types/sinon-chai": "^3.2.5",
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.2.0",
"@typescript-eslint/eslint-plugin-tslint": "^5.2.0",
"@typescript-eslint/parser": "^5.2.0",
Expand Down
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,4 +1,5 @@
import {
aggregateAsync,
anyAsync,
appendAsync,
avgAsync,
Expand Down Expand Up @@ -115,6 +116,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,
contains,
count,
Expand Down Expand Up @@ -58,6 +59,7 @@ import {
toObjectChainReduce,
} from '../sync';
import {
aggregateAsync,
allAsync,
avgAsync,
anyAsync,
Expand Down Expand Up @@ -155,6 +157,8 @@ export const special = {
};

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

const baseDefaultId = uuidV4();
const contextSymbol = Symbol('context');

class Aggregations {
[contextSymbol]: any = {
id: 0,
defaultId(): string | number {
return `${baseDefaultId}${this.id++}`;
},
};

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: string | number = this[contextSymbol].defaultId(),
) {
const context = (this[contextSymbol].modSum ??= {});
return (context[id] = this.sum(value, `modSum${baseDefaultId}${id}`) % mod);
}
modMultiply(
value: number,
mod: number,
id: string | number = this[contextSymbol].defaultId(),
) {
const context = (this[contextSymbol].modMultiply ??= {});
return (context[id] =
this.multiply(value, `modMultiply${baseDefaultId}${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].id = 0;
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 64c9320

Please sign in to comment.