Skip to content

Commit

Permalink
Move Sequence Implimention into a class (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jason3S committed Dec 14, 2019
1 parent 74315b7 commit 5d66f45
Show file tree
Hide file tree
Showing 14 changed files with 518 additions and 383 deletions.
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

0 comments on commit 5d66f45

Please sign in to comment.