Skip to content

Commit

Permalink
Add count method (#6)
Browse files Browse the repository at this point in the history
* Add count method

* Make sequences reiterable.

Sequence loses the "next" function because it had to reset when it saw done which felt a bit strange.

* added .next() back

* make IterableIterator reuse test test slightly more interesting

* Remove extra spaces
  • Loading branch information
sea1jxr authored and Jason3S committed May 30, 2017
1 parent 7c2a4e3 commit f6ea88d
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 41 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const setOf4LetterWords = new Set(genSequence(setOfWords).filter(a => a.length =
### Reducers
- `.all()` -- true if all values in the sequence return true for *fn(value)* or the sequence is empty.
- `.any()` -- true if any value in the sequence exists where *fn(value)* returns true.
- `.count()` -- return the number of values in the sequence.
- `.first()` -- return the next value in the sequence.
- `.first(fn)` -- return the next value in the sequence where *fn(value)* return true.
- `.max()` -- return the largest value in the sequence.
Expand Down
59 changes: 43 additions & 16 deletions src/GenSequence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,22 +162,6 @@ describe('GenSequence Tests', function() {
expect([...GS.objectToSequence(person)]).to.be.deep.equal([...sequenceFromObject(person)]);
});

it('tests that a sequence is empty once it has been used', () => {
const person = {
name: 'Bob',
age: 22,
height: 185,
weight: 87,
}
const i = sequenceFromObject(person);
const j = genSequence(i);
const values0 = j.toArray();
const values1 = i.toArray();
expect(values0).to.not.be.empty;
expect(values1).to.be.empty;
expect(values0).to.not.be.deep.equal(values1);
});

it('tests that a sequence can be reused if it is based upon an array', () => {
const values = [1,2,3,4,5];
const i = genSequence(values);
Expand Down Expand Up @@ -257,6 +241,18 @@ describe('GenSequence Tests', function() {
expect([...GS.makeIterable(genSequence(values))]).to.be.deep.equal(values);
});

it('test reusing getting the iterator from a sequence', () => {
const values = [1, 2, 3, 4];
const sequence: GS.Sequence<number> = genSequence(values).map(n => n);
// do it twice as an iterable
expect([...GS.makeIterable(sequence[Symbol.iterator]())]).to.be.deep.equal(values);
expect([...GS.makeIterable(sequence[Symbol.iterator]())]).to.be.deep.equal(values);

// do it twice as an iterator
expect([...GS.makeIterable(genSequence(values))]).to.be.deep.equal(values);
expect([...GS.makeIterable(genSequence(values))]).to.be.deep.equal(values);
});

it('test any with match', () => {
const values = [1, 2, 3, 4];
expect(genSequence(values).any(a => a > 3)).to.be.true;
Expand Down Expand Up @@ -496,4 +492,35 @@ describe('GenSequence Tests', function() {
expect(genSequence(values).min((v) => v.age)).to.equal(one);
expect(genSequence(values).min((v) => v.animal)).to.equal(two);
});

it('test count with no elements', () => {
const values: number[] = [];
expect(genSequence(values).count()).to.equal(0);
});

it('test count with 1 element', () => {
const values: number[] = [1];
expect(genSequence(values).count()).to.equal(1);
});

it('test count with 2 elements', () => {
const values: number[] = [18, 7];
expect(genSequence(values).count()).to.equal(2);
});

it('count twice on same array sequence', () => {
const values = [1, 2, 3, 4, 5, 6]
const seq = genSequence(values);
const firstCount = seq.count();
const secondCount = seq.count();
expect(firstCount).to.equal(secondCount);
});

it('count twice on same generated sequence', () => {
const values = [1, 2, 3, 4, 5, 6]
const filteredSequence = genSequence(values).filter(a => !!(a % 2));
const firstCount = filteredSequence.count();
const secondCount = filteredSequence.count();
expect(firstCount).to.equal(secondCount);
});
});
74 changes: 49 additions & 25 deletions src/GenSequence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export interface Sequence<T> extends IterableLike<T> {
//// 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;
max(fnSelector?: (t: T) => T): Maybe<T>;
Expand All @@ -52,79 +53,98 @@ export interface SequenceCreator<T> {
fromObject: <U>(u: U) => Sequence<KeyValuePair<U>>;
}

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>): Sequence<T>;
export function genSequence<T>(i: (() => GenIterable<T>) | GenIterable<T>): Sequence<T> {
let createIterable: () => GenIterable<T>;
if (typeof i === "function") {
createIterable = i;
} else {
// this is typeof "object"
createIterable = () => i;
}

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

return result;
};
}

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

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

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

//// Mappers
combine: <U, V>(fn: (t: T, u: U) => V, j: Iterable<U>) => {
return genSequence(combine(fn, i, j));
return genSequence(() => combine(fn, createIterable(), j));
},
map: <U>(fn: (t: T) => U) => genSequence(map(fn, i)),
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(i, fnReduce, initValue));
return genSequence(() => scan(createIterable(), fnReduce, initValue));
},

// Reducers
all: (fnFilter: (t: T) => boolean): boolean => {
return all(fnFilter, i);
return all(fnFilter, createIterable());
},
any: (fnFilter: (t: T) => boolean): boolean => {
return any(fnFilter, i);
return any(fnFilter, createIterable());
},
count: (): number => {
return count(createIterable());
},
first: (fnFilter: (t: T) => boolean, defaultValue: T): T => {
return first(fnFilter, defaultValue, i) as T;
return first(fnFilter, defaultValue, createIterable()) as T;
},
max: <U>(fnSelector: (t: T) => U): Maybe<T> => {
return max<T, U>(fnSelector, i);
return max<T, U>(fnSelector, createIterable());
},
min: <U>(fnSelector: (t: T) => U): Maybe<T> => {
return min<T, U>(fnSelector, i);
return min<T, U>(fnSelector, createIterable());
},
reduce: <U>(fnReduce: (prevValue: U, curValue: T, curIndex: number) => U, initValue?: U) => {
return reduce<T, U>(fnReduce, initValue!, i);
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!, i));
return genSequence<U>(reduce<T, GenIterable<U>>(fnReduce, initialValue!, createIterable()));
},

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

return seq;
}

Expand Down Expand Up @@ -258,6 +278,10 @@ export function any<T>(fn: (t: T) => boolean, i: Iterable<T>): boolean {
return false;
}

export function count<T>(i: Iterable<T>): number {
return reduce<T, number>(p => p + 1, 0, i);
}

export function first<T>(fn: Maybe<(t: T) => boolean>, defaultValue: Maybe<T>, i: Iterable<T>): Maybe<T>;
export function first<T>(fn: (t: T) => boolean, defaultValue: T, i: Iterable<T>): T {
fn = fn || (() => true);
Expand Down Expand Up @@ -352,7 +376,7 @@ export function objectToSequence<T>(t: T): Sequence<KeyValuePair<T>> {


export function sequenceFromObject<T>(t: T): Sequence<KeyValuePair<T>> {
return genSequence(objectIterator(t));
return genSequence(() => objectIterator(t));
}

export function sequenceFromRegExpMatch(pattern: RegExp, text: string): Sequence<RegExpExecArray> {
Expand All @@ -370,7 +394,7 @@ export function sequenceFromRegExpMatch(pattern: RegExp, text: string): Sequence
}
}

return genSequence(doMatch());
return genSequence(() => doMatch());
}

export default genSequence;

0 comments on commit f6ea88d

Please sign in to comment.