Skip to content

Commit 5cd35e9

Browse files
jeswrRubenVerborgh
authored andcommitted
Add uniq method.
1 parent 9af21cb commit 5cd35e9

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

asynciterator.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,24 @@ export class AsyncIterator<T> extends EventEmitter {
478478
});
479479
}
480480

481+
/**
482+
* Returns a new iterator containing all of the unique items in the original iterator.
483+
* @param by - The derived value by which to determine uniqueness (e.g., stringification).
484+
Defaults to the identity function.
485+
* @returns An iterator with duplicates filtered out.
486+
*/
487+
uniq(by: (item: T) => any = identity): AsyncIterator<T> {
488+
const uniques = new Set();
489+
return this.filter(function (this: AsyncIterator<T>, item) {
490+
const hashed = by.call(this, item);
491+
if (!uniques.has(hashed)) {
492+
uniques.add(hashed);
493+
return true;
494+
}
495+
return false;
496+
});
497+
}
498+
481499
/**
482500
Prepends the items after those of the current iterator.
483501
After this operation, only read the returned iterator instead of the current one.

test/MappingIterator-test.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -874,6 +874,69 @@ describe('MappingIterator', () => {
874874
});
875875
});
876876

877+
describe('The AsyncIterator#uniq function', () => {
878+
it('should be a function', () => {
879+
expect(AsyncIterator.prototype.uniq).to.be.a('function');
880+
});
881+
882+
describe('when called on an iterator', () => {
883+
let iterator, result;
884+
before(() => {
885+
iterator = new ArrayIterator([1, 1, 2, 1, 1, 2, 2, 3, 3, 3, 3]);
886+
result = iterator.uniq();
887+
});
888+
889+
describe('the return value', () => {
890+
const items = [];
891+
before(done => {
892+
result.on('data', item => { items.push(item); });
893+
result.on('end', done);
894+
});
895+
896+
it('should be a MappingIterator', () => {
897+
result.should.be.an.instanceof(MappingIterator);
898+
});
899+
900+
it('only contains unique items', () => {
901+
items.should.deep.equal([1, 2, 3]);
902+
});
903+
});
904+
});
905+
906+
describe('when called with a hashing function', () => {
907+
let iterator, hash, result;
908+
before(() => {
909+
iterator = new ArrayIterator([{ x: 1 }, { x: 1 }, { x: 1 }]);
910+
hash = sinon.spy(x => JSON.stringify(x));
911+
result = iterator.uniq(hash);
912+
});
913+
914+
describe('the return value', () => {
915+
const items = [];
916+
before(done => {
917+
result.on('data', item => { items.push(item); });
918+
result.on('end', done);
919+
});
920+
921+
it('should be a MappingIterator', () => {
922+
result.should.be.an.instanceof(MappingIterator);
923+
});
924+
925+
it('only contains unique items', () => {
926+
items.should.deep.equal([{ x: 1 }]);
927+
});
928+
929+
it('should call the hash function once for each item', () => {
930+
hash.should.have.been.calledThrice;
931+
});
932+
933+
it('should call the hash function with the returned iterator as `this`', () => {
934+
hash.alwaysCalledOn(result).should.be.true;
935+
});
936+
});
937+
});
938+
});
939+
877940
describe('The AsyncIterator#skip function', () => {
878941
it('should be a function', () => {
879942
expect(AsyncIterator.prototype.skip).to.be.a('function');

0 commit comments

Comments
 (0)