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

Move Sequence Implementation into a class #12

Merged
merged 1 commit into from
Dec 14, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 21 additions & 34 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,31 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceRoot}/lib/index.js",
"cwd": "${workspaceRoot}",
"outDir": "${workspaceRoot}/lib",
"sourceMaps": true
"name": "Jest current-file",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand", "${file}"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/.bin/jest",
}
},
{
"type": "node",
"request": "attach",
"name": "Attach to Process",
"port": 5858,
"outDir": "${workspaceRoot}/lib",
"sourceMaps": true
},
{
// Name of configuration; appears in the launch configuration drop down menu.
"name": "Run mocha",
// Type of configuration. Possible values: "node", "mono".
"type": "node2",
// Request type "launch" or "attach"
"request": "launch",
// Workspace relative or absolute path to the program.
"program": "${workspaceRoot}/node_modules/mocha/bin/_mocha",
// Automatically stop program after launch.
"stopOnEntry": false,
// Command line arguments passed to the program.
"args": ["--recursive", "lib/**/*.test.js"],
// Workspace relative or absolute path to the working directory of the program being debugged. Default is the current workspace.
"cwd": "${workspaceRoot}",
// Workspace relative or absolute path to the runtime executable to be used. Default is the runtime executable on the PATH.
"runtimeExecutable": null,
"outDir": "${workspaceRoot}/lib",
"sourceMaps": true,
// Environment variables passed to the program.
"env": { "NODE_ENV": "test"}
}
"name": "Jest All",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"disableOptimisticBPs": true,
"windows": {
"program": "${workspaceFolder}/node_modules/.bin/jest",
}
}
]
}
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"clean-build": "npm run clean && npm run build",
"clean": "rimraf dist",
"test": "jest src/**/*.test.ts src/*.test.ts",
"perf": "jest src/**/*.perf.ts src/*.perf.ts",
"perf": "jest src/*.perf.ts",
"build": "tsc -p .",
"watch": "tsc -w -p .",
"coverage": "npm test -- --coverage",
Expand Down
37 changes: 16 additions & 21 deletions src/GenSequence.perf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ describe('Performance Test', () => {
}
const rBase = measure(fnBase, 100);
const rExp = measure(fnExp, 100);
const ratio = rExp.avg / rBase.avg;

expect(rExp.result).toEqual(rBase.result);
console.log('Simple Generator to an array' + compareMeasurementsToString(rBase, rExp));
expect(ratio).toBeLessThan(1.2);
assertExpectedRatio('Simple Generator to an array', rBase, rExp, 1.2);
});

test('filter filter reduce', () => {
Expand All @@ -36,11 +34,9 @@ describe('Performance Test', () => {
}
const rBase = measure(fnBase, 10);
const rExp = measure(fnExp, 10);
const ratio = rExp.avg / rBase.avg;

expect(rExp.result).toBe(rBase.result);
console.log('filter filter reduce' + compareMeasurementsToString(rBase, rExp));
expect(ratio).toBeLessThan(1.4);
assertExpectedRatio('filter filter reduce', rBase, rExp, 1.4);
});

test('filter slice filter reduce', () => {
Expand All @@ -64,11 +60,9 @@ describe('Performance Test', () => {
}
const rBase = measure(fnBase, 10);
const rExp = measure(fnExp, 10);
const ratio = rExp.avg / rBase.avg;

expect(rExp.result).toBe(rBase.result);
console.log('filter slice filter reduce' + compareMeasurementsToString(rBase, rExp));
expect(ratio).toBeLessThan(1);
assertExpectedRatio('filter slice filter reduce', rBase, rExp, 1);
});

test('filter slice filter reduce (1000)', () => {
Expand All @@ -92,11 +86,9 @@ describe('Performance Test', () => {
}
const rBase = measure(fnBase, 1000);
const rExp = measure(fnExp, 1000);
const ratio = rExp.avg / rBase.avg;

expect(rExp.result).toBe(rBase.result);
console.log('filter slice filter reduce (1000)' + compareMeasurementsToString(rBase, rExp));
expect(ratio).toBeLessThan(2);
assertExpectedRatio('filter slice filter reduce (1000)', rBase, rExp, 2, 3);
});

test('filter slice filter first (1000)', () => {
Expand All @@ -122,11 +114,9 @@ describe('Performance Test', () => {
}
const rBase = measure(fnBase, 1000);
const rExp = measure(fnExp, 1000);
const ratio = rExp.avg / rBase.avg;

expect(rExp.result).toBe(rBase.result);
console.log('filter slice filter first (1000)' + compareMeasurementsToString(rBase, rExp));
expect(ratio).toBeLessThan(3);
assertExpectedRatio('filter slice filter first (1000)', rBase, rExp, 0.5);
});

test('concatMap', () => {
Expand Down Expand Up @@ -156,11 +146,9 @@ describe('Performance Test', () => {
}
const rBase = measure(fnBase, 100);
const rExp = measure(fnExp, 100);
const ratio = rExp.avg / rBase.avg;

expect(rExp.result).toBe(rBase.result);
console.log('concatMap' + compareMeasurementsToString(rBase, rExp));
expect(ratio).toBeLessThan(2);
assertExpectedRatio('concatMap', rBase, rExp, 1);
});
});

Expand Down Expand Up @@ -209,19 +197,26 @@ function toMs(diff: [number, number]) {
return diff[0] * 1e3 + diff[1] / 1e6;
}

function compareMeasurementsToString<T>(base: Measurement<T>, comp: Measurement<T>): string {
function assertExpectedRatio<T>(testName: string, base: Measurement<T>, comp: Measurement<T>, expectedRatio: number, failRatio?: number) {
console.log(testName + compareMeasurementsToString(base, comp, expectedRatio));
const ratio = comp.avg / base.avg;
expect(ratio).toBeLessThan(failRatio ?? expectedRatio);
}

function compareMeasurementsToString<T>(base: Measurement<T>, comp: Measurement<T>, expectedRatio: number): string {
function fix(n: number | undefined) {
if (n === undefined) {
return '-';
}
return n.toFixed(3);
}
const ratio = (comp.avg || 0) / (base.avg || 1);
return `
\tbase\tcomp
\t\tbase\tcomp
avg:\t${fix(base.avg)}\t${fix(comp.avg)}
min:\t${fix(base.min)}\t${fix(comp.min)}
max:\t${fix(base.max)}\t${fix(comp.max)}
cnt:\t${base.count}\t${comp.count}
ratio:\t${fix((comp.avg || 0) / (base.avg || 1))}
ratio:\t${fix(expectedRatio)}\t${fix(ratio)}\t${ratio <= expectedRatio ? 'PASS' : 'FAIL'}
`;
}
2 changes: 1 addition & 1 deletion src/GenSequence.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { genSequence, sequenceFromObject, sequenceFromRegExpMatch, objectToSequence, Sequence } from './GenSequence';
import * as op from './operators/operators';
import * as op from './operators/operatorsBase';

describe('GenSequence Tests', () => {

Expand Down
138 changes: 6 additions & 132 deletions src/GenSequence.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,9 @@
import { IterableLike, Maybe } from './operators/types';
import { filter, skip, take, concat, concatMap, combine, map, scan, all, any, count, first, forEach, max, min, reduce } from './operators/operators';
import { Sequence, GenIterable } from './util/types';
import { toIterableIterator } from './util/util';
import { ImplSequence } from './ImplSequence';

export interface Sequence<T> extends IterableLike<T> {
next(): IteratorResult<T>;

//// Filters
/** keep values where the fnFilter(t) returns true */
filter(fnFilter: (t: T) => boolean): Sequence<T>;
skip(n: number): Sequence<T>;
take(n: number): Sequence<T>;

//// Extenders
concat(j: Iterable<T>): Sequence<T>;
concatMap<U>(fn: (t: T) => Iterable<U>): Sequence<U>;

//// Mappers
combine<U, V>(fn: (t: T, u?: U) => V, j: Iterable<U>): Sequence<V>;
/** map values from type T to type U */
map<U>(fnMap: (t: T) => U): Sequence<U>;
scan(fnReduce: (previousValue: T, currentValue: T, currentIndex: number) => T, initialValue?: T): Sequence<T>;
scan<U>(fnReduce: (previousValue: U, currentValue: T, currentIndex: number) => U, initialValue: U): Sequence<U>;

//// Reducers
all(fnFilter: (t: T) => boolean): boolean;
any(fnFilter: (t: T) => boolean): boolean;
count(): number;
first(fnFilter?: (t: T) => boolean, defaultValue?: T): Maybe<T>;
first(fnFilter: (t: T) => boolean, defaultValue: T): T;
forEach(fn: (t: T, index: number) => void): void;
max(fnSelector?: (t: T) => T): Maybe<T>;
max<U>(fnSelector: (t: T) => U): Maybe<T>;
min(fnSelector?: (t: T) => T): Maybe<T>;
min<U>(fnSelector: (t: T) => U): Maybe<T>;
/** reduce function see Array.reduce */
reduce(fnReduce: (previousValue: T, currentValue: T, currentIndex: number) => T): Maybe<T>;
reduce<U>(fnReduce: (previousValue: U, currentValue: T, currentIndex: number) => U, initialValue: U): U;
reduceToSequence<U, V extends GenIterable<U>>(fnReduce: (previousValue: V, currentValue: T, currentIndex: number) => V, initialValue: V): Sequence<U>;
reduceToSequence<U>(fnReduce: (previousValue: GenIterable<U>, currentValue: T, currentIndex: number) => GenIterable<U>, initialValue: GenIterable<U>): Sequence<U>;

//// Cast
toArray(): T[];
toIterable(): IterableIterator<T>;
}

export interface GenIterable<T> extends IterableLike<T> {}
export { Sequence, GenIterable } from './util/types';
export { toIterableIterator } from './util/util';

export interface SequenceCreator<T> {
(i: GenIterable<T>): Sequence<T>;
Expand All @@ -53,88 +13,7 @@ export interface SequenceCreator<T> {
export function genSequence<T>(i: () => GenIterable<T>): Sequence<T>;
export function genSequence<T>(i: GenIterable<T>): Sequence<T>;
export function genSequence<T>(i: (() => GenIterable<T>) | GenIterable<T>): Sequence<T> {
const createIterable: () => GenIterable<T> = (typeof i === "function") ? i : () => i;

function fnNext() {
let iter: Maybe<Iterator<T>>;
return () => {
if(!iter) {
iter = createIterable()[Symbol.iterator]();
}
return iter.next();
};
}

const seq = {
[Symbol.iterator]: () => createIterable()[Symbol.iterator](),
next: fnNext(), // late binding is intentional here.

//// Filters
filter: (fnFilter: (t: T) => boolean) => genSequence(() => filter(fnFilter, createIterable())),
skip: (n: number) => {
return genSequence(() => skip(n, createIterable()));
},
take: (n: number) => {
return genSequence(() => take(n, createIterable()));
},

//// Extenders
concat: (j: Iterable<T>) => {
return genSequence(() => concat(createIterable(), j));
},
concatMap: <U>(fn: (t: T) => Iterable<U>) => {
return genSequence(() => concatMap(fn, createIterable()));
},

//// Mappers
combine: <U, V>(fn: (t: T, u?: U) => V, j: Iterable<U>) => {
return genSequence(() => combine(fn, createIterable(), j));
},
map: <U>(fn: (t: T) => U) => genSequence(() => map(fn, createIterable())),
scan: <U>(fnReduce: (prevValue: U, curValue: T, curIndex: number) => U, initValue: U) => {
return genSequence(() => scan(createIterable(), fnReduce, initValue));
},

// Reducers
all: (fnFilter: (t: T) => boolean): boolean => {
return all(fnFilter, createIterable());
},
any: (fnFilter: (t: T) => boolean): boolean => {
return any(fnFilter, createIterable());
},
count: (): number => {
return count(createIterable());
},
first: (fnFilter: (t: T) => boolean, defaultValue: T): T => {
return first(fnFilter, defaultValue, createIterable()) as T;
},
forEach: (fn: (t: T, index: number) => void): void => {
return forEach(fn, createIterable());
},
max: <U>(fnSelector: (t: T) => U): Maybe<T> => {
return max<T, U>(fnSelector, createIterable());
},
min: <U>(fnSelector: (t: T) => U): Maybe<T> => {
return min<T, U>(fnSelector, createIterable());
},
reduce: <U>(fnReduce: (prevValue: U, curValue: T, curIndex: number) => U, initValue?: U) => {
return reduce<T, U>(fnReduce, initValue!, createIterable());
},
reduceToSequence: <U>(
fnReduce: (previousValue: GenIterable<U>, currentValue: T, currentIndex: number) => GenIterable<U>,
initialValue: GenIterable<U>
): Sequence<U> => {
return genSequence<U>(reduce<T, GenIterable<U>>(fnReduce, initialValue!, createIterable()));
},

//// Cast
toArray: () => [...createIterable()],
toIterable: () => {
return toIterableIterator(createIterable());
},
};

return seq;
return new ImplSequence(i);
}

// Collection of entry points into GenSequence
Expand All @@ -144,11 +23,6 @@ export const GenSequence = {
sequenceFromObject,
};

//// Cast
export function* toIterableIterator<T>(i: Iterable<T>) {
yield* i;
}

/**
* alias of toIterableIterator
*/
Expand Down
Loading