Skip to content

Commit

Permalink
feat: add pipe function
Browse files Browse the repository at this point in the history
  • Loading branch information
rob893 committed Sep 7, 2021
1 parent 7ffca67 commit 6df37e6
Show file tree
Hide file tree
Showing 18 changed files with 333 additions and 63 deletions.
136 changes: 79 additions & 57 deletions src/Enumerable.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,71 @@
import { from } from './functions/from';
import { aggregate } from './functions/aggregate';
import { all } from './functions/all';
import { any } from './functions/any';
import { append } from './functions/append';
import { atLeast } from './functions/atLeast';
import { atMost } from './functions/atMost';
import { concat } from './functions/concat';
import { contains } from './functions/contains';
import { count } from './functions/count';
import { defaultIfEmpty } from './functions/defaultIfEmpty';
import { distinct, distinctBy } from './functions/distinct';
import { elementAt, elementAtOrDefault } from './functions/elementAt';
import { empty } from './functions/empty';
import { endsWith } from './functions/endsWith';
import { except, exceptBy } from './functions/except';
import { first, firstOrDefault } from './functions/first';
import { forEach } from './functions/forEach';
import { groupBy } from './functions/groupBy';
import { intersect, intersectBy } from './functions/intersect';
import { last, lastOrDefault } from './functions/last';
import { max, maxBy } from './functions/max';
import { ofType } from './functions/ofType';
import { orderBy } from './functions/orderBy';
import { prepend } from './functions/prepend';
import { quantile } from './functions/quantile';
import { range } from './functions/range';
import { repeat } from './functions/repeat';
import { reverse } from './functions/reverse';
import { select, selectMany } from './functions/select';
import { sequenceEqual } from './functions/sequenceEqual';
import { shuffle } from './functions/shuffle';
import { skip, skipLast, skipWhile } from './functions/skip';
import { startsWith } from './functions/startsWith';
import { sum } from './functions/sum';
import { take, takeEvery, takeLast, takeWhile } from './functions/take';
import { thenBy } from './functions/thenBy';
import { toMap } from './functions/toMap';
import { toObject } from './functions/toObject';
import { toSet } from './functions/toSet';
import { union, unionBy } from './functions/union';
import { where } from './functions/where';
import { zip } from './functions/zip';
import { Comparer, EqualityComparer } from './types';
import {
from,
empty,
range,
repeat,
aggregate,
all,
any,
append,
asEnumerable,
atLeast,
atMost,
average,
chunk,
concat,
EqualityComparer,
contains,
count,
defaultIfEmpty,
distinct,
distinctBy,
elementAt,
elementAtOrDefault,
endsWith,
except,
exceptBy,
first,
firstOrDefault,
forEach,
groupBy,
intersect,
intersectBy,
last,
lastOrDefault,
max,
maxBy,
min,
minBy,
ofType,
Comparer,
orderBy,
pipe,
prepend,
quantile,
reverse,
select,
selectMany,
sequenceEqual,
shuffle,
skip,
skipLast,
skipWhile,
startsWith,
sum,
take,
takeEvery,
takeLast,
takeWhile,
toArray,
toMap,
toObject,
toSet,
union,
unionBy,
where,
zip,
thenBy
} from '.';

export class Enumerable<TSource> implements Iterable<TSource> {
private readonly srcGenerator: () => Generator<TSource>;
Expand Down Expand Up @@ -87,7 +111,7 @@ export class Enumerable<TSource> implements Iterable<TSource> {
return this.srcGenerator();
}

public aggregate<T>(aggregator: (prev: T, curr: T, index: number) => T): T;
public aggregate(aggregator: (prev: TSource, curr: TSource, index: number) => TSource): TSource;
public aggregate<TAccumulate>(
aggregator: (prev: TAccumulate, curr: TSource, index: number) => TAccumulate,
seed: TAccumulate
Expand All @@ -112,7 +136,7 @@ export class Enumerable<TSource> implements Iterable<TSource> {
}

public asEnumerable(): Enumerable<TSource> {
return new Enumerable(this.srcGenerator);
return asEnumerable(this);
}

public atLeast(count: number, predicate?: (item: TSource, index: number) => boolean): boolean {
Expand All @@ -124,13 +148,11 @@ export class Enumerable<TSource> implements Iterable<TSource> {
}

public average(selector?: (item: TSource) => number): number {
return this.sum(selector) / this.count();
return average(this, selector);
}

public chunk(chunkSize: number): Enumerable<Enumerable<TSource>> {
return this.select((x, i) => ({ index: i, value: x }))
.groupBy(x => Math.floor(x.index / chunkSize))
.select(x => x.select(v => v.value));
return chunk(this, chunkSize);
}

public concat(second: Iterable<TSource>): Enumerable<TSource> {
Expand Down Expand Up @@ -256,15 +278,11 @@ export class Enumerable<TSource> implements Iterable<TSource> {
public min(): TSource;
public min<TResult>(selector: (item: TSource) => TResult): TResult;
public min<TResult>(selector?: (item: TSource) => TResult): TSource | TResult {
if (!selector) {
return this.aggregate((prev, curr) => (prev < curr ? prev : curr));
}

return this.select(selector).aggregate((prev, curr) => (prev < curr ? prev : curr));
return min(this, selector);
}

public minBy<TKey>(keySelector: (item: TSource) => TKey): TSource {
return this.aggregate((prev, curr) => (keySelector(prev) <= keySelector(curr) ? prev : curr));
return minBy(this, keySelector);
}

public ofType<TResult>(type: new (...params: unknown[]) => TResult): Enumerable<TResult> {
Expand All @@ -282,6 +300,10 @@ export class Enumerable<TSource> implements Iterable<TSource> {
return orderBy(this, false, selector, comparer);
}

public pipe(action: (item: TSource, index: number) => void): Enumerable<TSource> {
return pipe(this, action);
}

public prepend(item: TSource): Enumerable<TSource> {
return prepend(this, item);
}
Expand Down Expand Up @@ -362,7 +384,7 @@ export class Enumerable<TSource> implements Iterable<TSource> {
}

public toArray(): TSource[] {
return [...this];
return toArray(this);
}

public toLookup(): unknown {
Expand Down
32 changes: 32 additions & 0 deletions src/__tests__/ofType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Enumerable, ofType, TypeOfMember } from '..';

describe('ofType', () => {
it.each([[1, 2, 3], new Set([1, 2, 3]), '123', new Map()])('should return an Enumerable', src => {
const result = ofType<unknown, string>(src, 'string');

expect(result).toBeInstanceOf(Enumerable);
});

it.each([
[[1, 'b', 'c', false], 'string', ['b', 'c']],
[[1, 'b', 'c', false, 2, true], 'number', [1, 2]],
[[1, 'b', 'c', false, 2, true], 'boolean', [false, true]]
])('should only return the specified type', (src, type, expected) => {
const result = ofType(src, type as TypeOfMember).toArray();

expect(result).toEqual(expected);
});

it('should only return instances of passed class', () => {
class Test {}

const a = new Test();
const b = new Test();

const items = [1, 3, 'sadf', a, false, null, undefined, b, NaN];

const result = ofType(items, Test).toArray();

expect(result).toEqual([a, b]);
});
});
35 changes: 35 additions & 0 deletions src/__tests__/pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Enumerable, pipe } from '..';

describe('pipe', () => {
it.each([[1, 2, 3], new Set([1, 2, 3]), '123', new Map()])('should return an Enumerable', src => {
const result = pipe<unknown>(src, () => {});

expect(result).toBeInstanceOf(Enumerable);
});

it('should call action for each item', () => {
const action = jest.fn();

const items = [1, 2, 3];

const _ = pipe(items, action).toArray();

expect(action).toHaveBeenCalledTimes(items.length);
});

it('should have deferred execution', () => {
const action = jest.fn();

const items = [1, 2, 3];

const result = pipe(items, action);

expect(action).toHaveBeenCalledTimes(0);

items.push(4);

const _ = result.toArray();

expect(action).toHaveBeenCalledTimes(items.length);
});
});
14 changes: 14 additions & 0 deletions src/functions/aggregate.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
export function aggregate<TSource>(
src: Iterable<TSource>,
aggregator: (prev: TSource, curr: TSource, index: number) => TSource
): TSource;
export function aggregate<TSource, TAccumulate>(
src: Iterable<TSource>,
aggregator: (prev: TAccumulate, curr: TSource, index: number) => TAccumulate,
seed: TAccumulate
): TAccumulate;
export function aggregate<TSource, TAccumulate>(
src: Iterable<TSource>,
aggregator: (prev: TAccumulate | TSource, curr: TSource, index: number) => TAccumulate | TSource,
seed?: TAccumulate | TSource
): TAccumulate | TSource;
export function aggregate<TSource, TAccumulate>(
src: Iterable<TSource>,
aggregator: (prev: TAccumulate | TSource, curr: TSource, index: number) => TAccumulate | TSource,
Expand Down
5 changes: 5 additions & 0 deletions src/functions/asEnumerable.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Enumerable } from '../Enumerable';

export function asEnumerable<TSource>(src: Iterable<TSource>): Enumerable<TSource> {
return new Enumerable(src);
}
6 changes: 6 additions & 0 deletions src/functions/average.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { count } from './count';
import { sum } from './sum';

export function average<TSource>(src: Iterable<TSource>, selector?: (item: TSource) => number): number {
return sum(src, selector) / count(src);
}
8 changes: 8 additions & 0 deletions src/functions/chunk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Enumerable } from '../Enumerable';
import { select } from './select';

export function chunk<TSource>(src: Iterable<TSource>, chunkSize: number): Enumerable<Enumerable<TSource>> {
return select(src, (x, i) => ({ index: i, value: x }))
.groupBy(x => Math.floor(x.index / chunkSize))
.select(x => x.select(v => v.value));
}
49 changes: 49 additions & 0 deletions src/functions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
export * from './aggregate';
export * from './all';
export * from './any';
export * from './append';
export * from './asEnumerable';
export * from './atLeast';
export * from './atMost';
export * from './average';
export * from './chunk';
export * from './concat';
export * from './contains';
export * from './count';
export * from './defaultIfEmpty';
export * from './distinct';
export * from './elementAt';
export * from './empty';
export * from './endsWith';
export * from './except';
export * from './first';
export * from './forEach';
export * from './from';
export * from './groupBy';
export * from './intersect';
export * from './last';
export * from './max';
export * from './min';
export * from './ofType';
export * from './orderBy';
export * from './pipe';
export * from './prepend';
export * from './quantile';
export * from './range';
export * from './repeat';
export * from './reverse';
export * from './select';
export * from './sequenceEqual';
export * from './shuffle';
export * from './skip';
export * from './startsWith';
export * from './sum';
export * from './take';
export * from './thenBy';
export * from './toArray';
export * from './toMap';
export * from './toObject';
export * from './toSet';
export * from './union';
export * from './where';
export * from './zip';
20 changes: 20 additions & 0 deletions src/functions/min.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { aggregate } from './aggregate';
import { select } from './select';

export function min<TSource>(src: Iterable<TSource>): TSource;
export function min<TSource, TResult>(src: Iterable<TSource>, selector: (item: TSource) => TResult): TResult;
export function min<TSource, TResult>(src: Iterable<TSource>, selector?: (item: TSource) => TResult): TSource | TResult;
export function min<TSource, TResult>(
src: Iterable<TSource>,
selector?: (item: TSource) => TResult
): TSource | TResult {
if (!selector) {
return aggregate(src, (prev, curr) => (prev < curr ? prev : curr));
}

return select(src, selector).aggregate((prev, curr) => (prev < curr ? prev : curr));
}

export function minBy<TSource, TKey>(src: Iterable<TSource>, keySelector: (item: TSource) => TKey): TSource {
return aggregate(src, (prev, curr) => (keySelector(prev) <= keySelector(curr) ? prev : curr));
}
Loading

0 comments on commit 6df37e6

Please sign in to comment.