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

Add SequenceBuilder #13

Merged
merged 3 commits into from
Dec 15, 2019
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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/
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
language: node_js
node_js:
- "node"
- "12"
- "10"
- "8"
script:
- npm install
- npm run build
Expand Down
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('builder 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('builder 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';