Skip to content

Commit

Permalink
Merge 94c1e8b into 5d66f45
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Dec 15, 2019
2 parents 5d66f45 + 94c1e8b commit 7d6933b
Show file tree
Hide file tree
Showing 13 changed files with 384 additions and 11 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ jspm_packages
# Optional REPL history
.node_repl_history

*.tgz

# ignore the output directory
dist/
coverage
coverage/
Binary file removed gensequence-2.1.3.tgz
Binary file not shown.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
{
"name": "gensequence",
"version": "2.3.0",
"version": "3.0.0",
"description": "Small library to simplify working with Generators and Iterators in Javascript / Typescript",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=10.0.0"
},
"dependencies": {},
"devDependencies": {
"@types/jest": "^24.0.23",
Expand Down
26 changes: 26 additions & 0 deletions src/GenSequence.perf.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as GS from './GenSequence';
import {builder} from '.';

describe('Performance Test', () => {
jest.setTimeout(100000);
Expand Down Expand Up @@ -91,6 +92,31 @@ describe('Performance Test', () => {
assertExpectedRatio('filter slice filter reduce (1000)', rBase, rExp, 2, 3);
});

test('pipe filter slice filter reduce (1000)', () => {
const getValues = () => range(0, 1000);
const fnBase = () => {
return [...getValues()]
.filter(a => !!(a & 1))
.slice(100)
.slice(0,500)
.filter(a => !!(a & 2))
.reduce((a, b) => a + b);
};

const fnExp = () => builder
.filter<number>(a => !!(a & 1))
.skip(100)
.take(500)
.filter(a => !!(a & 2))
.build(getValues())
.reduce((a, b) => a + b);
const rBase = measure(fnBase, 1000);
const rExp = measure(fnExp, 1000);

expect(rExp.result).toBe(rBase.result);
assertExpectedRatio('pipe filter slice filter reduce (1000)', rBase, rExp, 2, 3);
});

test('filter slice filter first (1000)', () => {
const getValues = () => range(0, 1000);
const fnBase = () => {
Expand Down
8 changes: 5 additions & 3 deletions src/ImplSequence.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { Sequence, GenIterable, Maybe } from './util/types';
import { Sequence, GenIterable, Maybe, LazyIterable, ChainFunction } from './util/types';
import { filter, skip, take, concat, concatMap, combine, map, scan, all, any, count, first, forEach, max, min, reduce } from './operators';
import { toIterableIterator } from './util/util';

type LazyIterable<T> = (() => GenIterable<T>) | GenIterable<T>;

export class ImplSequence<T> implements Sequence<T> {
private _iterator: Maybe<Iterator<T>>;

Expand Down Expand Up @@ -73,6 +71,10 @@ export class ImplSequence<T> implements Sequence<T> {
return this.chain(scan(fnReduce, initValue));
}

pipe<U>(fn: ChainFunction<T, U>): ImplSequence<U> {
return this.chain(fn);
}

// Reducers
all(fnFilter: (t: T) => boolean): boolean {
return all(fnFilter)(this.iter);
Expand Down
94 changes: 94 additions & 0 deletions src/ImplSequenceBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ImplSequenceBuilder } from './ImplSequenceBuilder';
import { map } from './operators';
import { scanMap } from './operators/operatorsBase';
import { builder } from './builder';

describe('Verify ImplSequenceBuilder', () => {
function getBuilder() {
return builder.map((a: number) => a);
}

test('Test empty builder', () => {
const a = [1, 2, 3];
const b = new ImplSequenceBuilder();
const i = b.build(a);
expect([...i]).toEqual(a);
});

test('Test empty builder', () => {
const a = [1, 2, 3];
const b = getBuilder();
const i = b.build(a);
expect([...i]).toEqual(a);
});

test('Test map', () => {
const fn = (a: number) => a * 2;
const a = [1, 2, 3];
const b = getBuilder().map(fn);
const i = b.build(a);
expect([...i]).toEqual(a.map(fn));
});

test('Test pipe', () => {
const fn = (a: number) => a * 2;
const a = [1, 2, 3];
const b = getBuilder().pipe(map(fn));
const i = b.build(a);
expect([...i]).toEqual(a.map(fn));
});

test('Test filter', () => {
const fn = (a: number) => !!(a % 2);
const a = [1, 2, 3, 4];
const b = getBuilder().filter(fn);
const i = b.build(a);
expect([...i]).toEqual(a.filter(fn));
});

test('Test skip', () => {
const a = [1, 2, 3];
const b = getBuilder().skip(2);
const i = b.build(a);
expect([...i]).toEqual(a.slice(2));
});

test('Test take', () => {
const a = [1, 2, 3];
const b = getBuilder().take(2);
const i = b.build(a);
expect([...i]).toEqual(a.slice(0, 2));
});

test('Test concat', () => {
const a = [1, 2, 3];
const c = [6, 7, 8];
const b = getBuilder().concat(c);
const i = b.build(a);
expect([...i]).toEqual(a.concat(c));
});

test('Test concatMap', () => {
const fn = (a: number) => [a];
const a = [1, 2, 3];
const b = getBuilder().concatMap(fn);
const i = b.build(a);
expect([...i]).toEqual(a);
});

test('Test scan', () => {
const fn = (acc: number, cur: number) => acc + cur;
const a = [1, 2, 3];
const b = getBuilder().scan(fn, 0);
const i = b.build(a);
expect([...i]).toEqual(a.map(scanMap(fn, 0)));
});

test('Test combine', () => {
const fn = (a: number, b: number | undefined) => a + (b ?? 0);
const a = [1, 2, 3];
const b = getBuilder().combine(fn, a);
const i = b.build(a);
expect([...i]).toEqual(a.map(a => a * 2));
});
});
57 changes: 57 additions & 0 deletions src/ImplSequenceBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { SequenceBuilder, ChainFunction, LazyIterable, Sequence } from './util/types';
import { pipe, filter, skip, take, concat, concatMap, combine, map, scan } from './operators';
import { ImplSequence } from './ImplSequence';

export class ImplSequenceBuilder<S, T = S> implements SequenceBuilder<S, T> {
private operators: ChainFunction<S, T>[] = [];

constructor(operators: ChainFunction<S, T>[] = []) {
this.operators = operators;
}

build(i: LazyIterable<S>): Sequence<T> {
return new ImplSequence(i).pipe(pipe.apply<unknown, any, ChainFunction<S, T>>(null, this.operators));
}

pipe<U>(fn: ChainFunction<T, U>): SequenceBuilder<S, U> {
return new ImplSequenceBuilder<S, U>([...this.operators, fn] as any[]);
}

//// Filters
/** keep values where the fnFilter(t) returns true */
filter(fnFilter: (t: T) => boolean): SequenceBuilder<S, T> {
return this.pipe(filter(fnFilter));
}

skip(n: number): SequenceBuilder<S, T> {
return this.pipe(skip(n));
}

take(n: number): SequenceBuilder<S, T> {
return this.pipe(take(n));
}

//// Extenders
concat(j: Iterable<T>): SequenceBuilder<S, T> {
return this.pipe(concat(j));
}

concatMap<U>(fn: (t: T) => Iterable<U>): SequenceBuilder<S, U> {
return this.pipe(concatMap(fn));
}

//// Mappers
combine<U, V>(fn: (t: T, u?: U) => V, j: Iterable<U>): SequenceBuilder<S, V> {
return this.pipe(combine(fn, j));
}

/** map values from type T to type U */
map<U>(fnMap: (t: T) => U): SequenceBuilder<S, U> {
return this.pipe(map(fnMap));
}

scan(fnReduce: (previousValue: T, currentValue: T, currentIndex: number) => T, initialValue?: T): SequenceBuilder<S, T>;
scan<U>(fnReduce: (previousValue: U, currentValue: T, currentIndex: number) => U, initialValue: U): SequenceBuilder<S, U> {
return this.pipe(scan(fnReduce, initialValue));
}
}
76 changes: 76 additions & 0 deletions src/builder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { builder } from './builder';
import { map } from './operators';
import { scanMap } from './operators/operatorsBase';
// import { genSequence } from '.';

describe('Verify builder', () => {
test('Test map', () => {
const fn = (a: number) => a * 2;
const a = [1, 2, 3];
const b = builder.map(fn);
const i = b.build(a);
expect([...i]).toEqual(a.map(fn));
});

test('Test pipe', () => {
const fn = (a: number) => a * 2;
const a = [1, 2, 3];
const b = builder.pipe(map(fn));
const i = b.build(a);
expect([...i]).toEqual(a.map(fn));
});

test('Test filter', () => {
const fn = (a: number) => !!(a % 2);
const a = [1, 2, 3, 4];
const b = builder.filter(fn);
const i = b.build(a);
expect([...i]).toEqual(a.filter(fn));
});

test('Test skip', () => {
const a = [1, 2, 3];
const b = builder.skip(2);
const i = b.build(a);
expect([...i]).toEqual(a.slice(2));
});

test('Test take', () => {
const a = [1, 2, 3];
const b = builder.take(2);
const i = b.build(a);
expect([...i]).toEqual(a.slice(0, 2));
});

test('Test concat', () => {
const a = [1, 2, 3];
const c = [6, 7, 8];
const b = builder.concat(c);
const i = b.build(a);
expect([...i]).toEqual(a.concat(c));
});

test('Test concatMap', () => {
const fn = (a: number) => [a];
const a = [1, 2, 3];
const b = builder.concatMap(fn);
const i = b.build(a);
expect([...i]).toEqual(a);
});

test('Test scan', () => {
const fn = (acc: number, cur: number) => acc + cur;
const a = [1, 2, 3];
const b = builder.scan(fn, 0);
const i = b.build(a);
expect([...i]).toEqual(a.map(scanMap(fn, 0)));
});

test('Test combine', () => {
const fn = (a: number, b: number | undefined) => a + (b ?? 0);
const a = [1, 2, 3];
const b = builder.combine(fn, a);
const i = b.build(a);
expect([...i]).toEqual(a.map(a => a * 2));
});
});
51 changes: 51 additions & 0 deletions src/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ChainFunction, SequenceBuilder } from './util/types';
import { ImplSequenceBuilder } from './ImplSequenceBuilder';
import { filter, skip, take, concat, concatMap, combine, map, scan } from './operators';


function makeBuilder<T, U = T>(fn: ChainFunction<T, U>): SequenceBuilder<T, U> {
return new ImplSequenceBuilder([fn]);
}

export const builder = Object.freeze({
pipe: <T, U>(fn: ChainFunction<T, U>) => {
return makeBuilder(fn);
},

//// Filters
/** keep values where the fnFilter(t) returns true */
filter: <T>(fnFilter: (t: T) => boolean) => {
return makeBuilder(filter(fnFilter));
},

skip: <T>(n: number) => {
return makeBuilder<T>(skip(n));
},

take: <T>(n: number) => {
return makeBuilder<T>(take(n));
},

//// Extenders
concat: <T>(j: Iterable<T>) => {
return makeBuilder(concat(j));
},

concatMap: <T, U>(fn: (t: T) => Iterable<U>) => {
return makeBuilder(concatMap(fn));
},

//// Mappers
combine: <T, U, V>(fn: (t: T, u?: U) => V, j: Iterable<U>) => {
return makeBuilder(combine(fn, j));
},

/** map values from type T to type U */
map: <T, U>(fnMap: (t: T) => U) => {
return makeBuilder(map(fnMap));
},

scan: <T, U>(fnReduce: (previousValue: U, currentValue: T, currentIndex: number) => U, initialValue: U) => {
return makeBuilder(scan(fnReduce, initialValue));
},
});
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './GenSequence';
export { Sequence } from './util/types';
export { Sequence, LazyIterable, IterableLike, SequenceBuilder } from './util/types';
export * from './builder';

0 comments on commit 7d6933b

Please sign in to comment.