From 35c54b393114aeb64c33afc4de5055963b8b78a5 Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Thu, 24 Mar 2022 20:41:31 +0100 Subject: [PATCH 1/7] adds optimized iterators for mapping (.map()) and filtering (.filter()) --- asynciterator.ts | 44 ++++++++++++++++++++++++++-- test/SimpleTransformIterator-test.js | 12 ++++---- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/asynciterator.ts b/asynciterator.ts index 000fd10..bbc6931 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -456,7 +456,8 @@ export class AsyncIterator extends EventEmitter { @returns {module:asynciterator.AsyncIterator} A new iterator that maps the items from this iterator */ map(map: (item: T) => D, self?: any): AsyncIterator { - return this.transform({ map: self ? map.bind(self) : map }); + // return this.transform({ map: self ? map.bind(self) : map }); + return new MappingIterator(this, self ? map.bind(self) : map); } /** @@ -469,7 +470,8 @@ export class AsyncIterator extends EventEmitter { filter(filter: (item: T) => item is K, self?: any): AsyncIterator; filter(filter: (item: T) => boolean, self?: any): AsyncIterator; filter(filter: (item: T) => boolean, self?: any): AsyncIterator { - return this.transform({ filter: self ? filter.bind(self) : filter }); + return new FilteringIterator(this, self ? filter.bind(self) : filter); + // return this.transform({ filter: self ? filter.bind(self) : filter }); } /** @@ -1251,6 +1253,44 @@ function destinationFillBuffer(this: InternalSource) { (this._destination as any)._fillBuffer(); } +export class MappingIterator extends AsyncIterator { + constructor(source: AsyncIterator, map: (item: S) => D) { + super(); + let item: S | null; + this.read = (): D | null => { + if ((item = source.read()) !== null) + return map.call(this, item); + return null; + }; + source.on('end', () => { + this.close(); + }); + source.on('readable', () => { + this.readable = true; + }); + } +} + +export class FilteringIterator extends AsyncIterator { + constructor(source: AsyncIterator, filter: (item: T) => boolean) { + super(); + let item: T | null; + this.read = (): T | null => { + while ((item = source.read()) !== null) { + if (filter.call(this, item)) + return item; + } + return null; + }; + source.on('end', () => { + this.close(); + }); + source.on('readable', () => { + this.readable = true; + }); + } +} + /** An iterator that generates items based on a source iterator diff --git a/test/SimpleTransformIterator-test.js b/test/SimpleTransformIterator-test.js index e033b8e..78815aa 100644 --- a/test/SimpleTransformIterator-test.js +++ b/test/SimpleTransformIterator-test.js @@ -7,6 +7,8 @@ import { ArrayIterator, IntegerIterator, scheduleTask, + MappingIterator, + FilteringIterator, } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; @@ -1110,8 +1112,8 @@ describe('SimpleTransformIterator', () => { result.on('end', done); }); - it('should be a SimpleTransformIterator', () => { - result.should.be.an.instanceof(SimpleTransformIterator); + it('should be a MappingIterator', () => { + result.should.be.an.instanceof(MappingIterator); }); it('should execute the map function on all items in order', () => { @@ -1146,7 +1148,7 @@ describe('SimpleTransformIterator', () => { }); it('should be a SimpleTransformIterator', () => { - result.should.be.an.instanceof(SimpleTransformIterator); + result.should.be.an.instanceof(MappingIterator); }); it('should execute the map function on all items in order', () => { @@ -1185,7 +1187,7 @@ describe('SimpleTransformIterator', () => { }); it('should be a SimpleTransformIterator', () => { - result.should.be.an.instanceof(SimpleTransformIterator); + result.should.be.an.instanceof(FilteringIterator); }); it('should execute the filter function on all items in order', () => { @@ -1219,7 +1221,7 @@ describe('SimpleTransformIterator', () => { }); it('should be a SimpleTransformIterator', () => { - result.should.be.an.instanceof(SimpleTransformIterator); + result.should.be.an.instanceof(FilteringIterator); }); it('should execute the filter function on all items in order', () => { From 98195c274ffa9ea8f96635070904e2cf5014e2e5 Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Thu, 24 Mar 2022 21:09:13 +0100 Subject: [PATCH 2/7] adds simpler and faster iterators for skipping and limiting --- asynciterator.ts | 54 ++++++++++++++++++++++++++-- test/SimpleTransformIterator-test.js | 6 ++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/asynciterator.ts b/asynciterator.ts index bbc6931..5a8db07 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -512,7 +512,8 @@ export class AsyncIterator extends EventEmitter { @returns {module:asynciterator.AsyncIterator} A new iterator that skips the given number of items */ skip(offset: number): AsyncIterator { - return this.transform({ offset }); + // return this.transform({ offset }); + return new SkippingIterator(this, offset); } /** @@ -522,7 +523,8 @@ export class AsyncIterator extends EventEmitter { @returns {module:asynciterator.AsyncIterator} A new iterator with at most the given number of items */ take(limit: number): AsyncIterator { - return this.transform({ limit }); + // return this.transform({ limit }); + return new LimitingIterator(this, limit); } /** @@ -1291,6 +1293,54 @@ export class FilteringIterator extends AsyncIterator { } } +export class SkippingIterator extends AsyncIterator { + constructor(source: AsyncIterator, skip: number) { + super(); + let item: T | null; + let skipped = 0; + this.read = (): T | null => { + while ((item = source.read()) !== null) { + if (skipped < skip) + skipped += 1; + else + return item; + } + return null; + }; + source.on('end', () => { + this.close(); + }); + source.on('readable', () => { + this.readable = true; + }); + } +} + +export class LimitingIterator extends AsyncIterator { + constructor(source: AsyncIterator, limit: number) { + super(); + let item: T | null; + let count = 0; + this.read = (): T | null => { + while ((item = source.read()) !== null) { + if (count < limit) { + count += 1; + return item; + } + this.close(); + return null; + } + return null; + }; + source.on('end', () => { + this.close(); + }); + source.on('readable', () => { + this.readable = true; + }); + } +} + /** An iterator that generates items based on a source iterator diff --git a/test/SimpleTransformIterator-test.js b/test/SimpleTransformIterator-test.js index 78815aa..71ef501 100644 --- a/test/SimpleTransformIterator-test.js +++ b/test/SimpleTransformIterator-test.js @@ -9,6 +9,8 @@ import { scheduleTask, MappingIterator, FilteringIterator, + SkippingIterator, + LimitingIterator, } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; @@ -1349,7 +1351,7 @@ describe('SimpleTransformIterator', () => { }); it('should be a SimpleTransformIterator', () => { - result.should.be.an.instanceof(SimpleTransformIterator); + result.should.be.an.instanceof(SkippingIterator); }); it('should skip the given number of items', () => { @@ -1379,7 +1381,7 @@ describe('SimpleTransformIterator', () => { }); it('should be a SimpleTransformIterator', () => { - result.should.be.an.instanceof(SimpleTransformIterator); + result.should.be.an.instanceof(LimitingIterator); }); it('should take the given number of items', () => { From f1e4b14a1c1741a6044d85fe6316a37d098a6887 Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Thu, 24 Mar 2022 21:31:30 +0100 Subject: [PATCH 3/7] fixes if vs. while in LimitingIterator's .read() --- asynciterator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asynciterator.ts b/asynciterator.ts index 5a8db07..96913f8 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -1322,7 +1322,7 @@ export class LimitingIterator extends AsyncIterator { let item: T | null; let count = 0; this.read = (): T | null => { - while ((item = source.read()) !== null) { + if ((item = source.read()) !== null) { if (count < limit) { count += 1; return item; From 974f076fcf15802050fc7cb90dd68aeb3db0772c Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Sat, 26 Mar 2022 18:08:10 +0100 Subject: [PATCH 4/7] refactors read() functions into class methods --- asynciterator.ts | 102 +++++++++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 38 deletions(-) diff --git a/asynciterator.ts b/asynciterator.ts index 96913f8..0349194 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -1256,14 +1256,13 @@ function destinationFillBuffer(this: InternalSource) { } export class MappingIterator extends AsyncIterator { + protected readonly _map: (item: S) => D; + protected readonly _source: AsyncIterator; + constructor(source: AsyncIterator, map: (item: S) => D) { super(); - let item: S | null; - this.read = (): D | null => { - if ((item = source.read()) !== null) - return map.call(this, item); - return null; - }; + this._source = source; + this._map = map; source.on('end', () => { this.close(); }); @@ -1271,19 +1270,23 @@ export class MappingIterator extends AsyncIterator { this.readable = true; }); } + + read(): D | null { + const item = this._source.read(); + if (item !== null) + return this._map(item); + return null; + } } export class FilteringIterator extends AsyncIterator { + protected readonly _filter: (item: T) => boolean; + protected readonly _source: AsyncIterator; + constructor(source: AsyncIterator, filter: (item: T) => boolean) { super(); - let item: T | null; - this.read = (): T | null => { - while ((item = source.read()) !== null) { - if (filter.call(this, item)) - return item; - } - return null; - }; + this._source = source; + this._filter = filter; source.on('end', () => { this.close(); }); @@ -1291,22 +1294,27 @@ export class FilteringIterator extends AsyncIterator { this.readable = true; }); } + + read(): T | null { + let item; + while ((item = this._source.read()) !== null) { + if (this._filter(item)) + return item; + } + return null; + } } export class SkippingIterator extends AsyncIterator { + protected readonly _source: AsyncIterator; + protected readonly _skip: number; + protected _skipped: number; + constructor(source: AsyncIterator, skip: number) { super(); - let item: T | null; - let skipped = 0; - this.read = (): T | null => { - while ((item = source.read()) !== null) { - if (skipped < skip) - skipped += 1; - else - return item; - } - return null; - }; + this._skip = skip; + this._skipped = 0; + this._source = source; source.on('end', () => { this.close(); }); @@ -1314,24 +1322,29 @@ export class SkippingIterator extends AsyncIterator { this.readable = true; }); } + + read(): T | null { + let item; + while ((item = this._source.read()) !== null) { + if (this._skipped < this._skip) + this._skipped += 1; + else + return item; + } + return null; + } } export class LimitingIterator extends AsyncIterator { + protected readonly _source: AsyncIterator; + protected readonly _limit: number; + protected _count: number; + constructor(source: AsyncIterator, limit: number) { super(); - let item: T | null; - let count = 0; - this.read = (): T | null => { - if ((item = source.read()) !== null) { - if (count < limit) { - count += 1; - return item; - } - this.close(); - return null; - } - return null; - }; + this._source = source; + this._limit = limit; + this._count = 0; source.on('end', () => { this.close(); }); @@ -1339,6 +1352,19 @@ export class LimitingIterator extends AsyncIterator { this.readable = true; }); } + + read(): T | null { + const item = this._source.read(); + if (item !== null) { + if (this._count < this._limit) { + this._count += 1; + return item; + } + this.close(); + return null; + } + return null; + } } From 728f9ba790dc5fd731e70f50450f7f944def416c Mon Sep 17 00:00:00 2001 From: Jacopo Scazzosi Date: Sat, 26 Mar 2022 18:31:10 +0100 Subject: [PATCH 5/7] centralizes common code into shared base class --- asynciterator.ts | 72 +++++++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/asynciterator.ts b/asynciterator.ts index 0349194..e11a87e 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -1255,20 +1255,40 @@ function destinationFillBuffer(this: InternalSource) { (this._destination as any)._fillBuffer(); } -export class MappingIterator extends AsyncIterator { - protected readonly _map: (item: S) => D; +export class SynchronousTransformIterator extends AsyncIterator { protected readonly _source: AsyncIterator; - constructor(source: AsyncIterator, map: (item: S) => D) { + constructor(source: AsyncIterator) { + /* eslint-disable no-use-before-define */ super(); this._source = source; - this._map = map; - source.on('end', () => { + const cleanup = () => { + source.removeListener('end', onEnd); + source.removeListener('readable', onReadable); + }; + const onEnd = () => { + cleanup(); this.close(); - }); - source.on('readable', () => { + }; + const onReadable = () => { this.readable = true; - }); + }; + source.on('end', onEnd); + source.on('readable', onReadable); + } + + protected _destroy(cause: Error | undefined, callback: (error?: Error) => void) { + super._destroy(cause, callback); + this._source.destroy(cause); + } +} + +export class MappingIterator extends SynchronousTransformIterator { + protected readonly _map: (item: S) => D; + + constructor(source: AsyncIterator, map: (item: S) => D) { + super(source); + this._map = map; } read(): D | null { @@ -1279,20 +1299,12 @@ export class MappingIterator extends AsyncIterator { } } -export class FilteringIterator extends AsyncIterator { +export class FilteringIterator extends SynchronousTransformIterator { protected readonly _filter: (item: T) => boolean; - protected readonly _source: AsyncIterator; constructor(source: AsyncIterator, filter: (item: T) => boolean) { - super(); - this._source = source; + super(source); this._filter = filter; - source.on('end', () => { - this.close(); - }); - source.on('readable', () => { - this.readable = true; - }); } read(): T | null { @@ -1305,22 +1317,14 @@ export class FilteringIterator extends AsyncIterator { } } -export class SkippingIterator extends AsyncIterator { - protected readonly _source: AsyncIterator; +export class SkippingIterator extends SynchronousTransformIterator { protected readonly _skip: number; protected _skipped: number; constructor(source: AsyncIterator, skip: number) { - super(); + super(source); this._skip = skip; this._skipped = 0; - this._source = source; - source.on('end', () => { - this.close(); - }); - source.on('readable', () => { - this.readable = true; - }); } read(): T | null { @@ -1335,22 +1339,14 @@ export class SkippingIterator extends AsyncIterator { } } -export class LimitingIterator extends AsyncIterator { - protected readonly _source: AsyncIterator; +export class LimitingIterator extends SynchronousTransformIterator { protected readonly _limit: number; protected _count: number; constructor(source: AsyncIterator, limit: number) { - super(); - this._source = source; + super(source); this._limit = limit; this._count = 0; - source.on('end', () => { - this.close(); - }); - source.on('readable', () => { - this.readable = true; - }); } read(): T | null { From c1d47354b922ac693bf55b9b7a71620146795e60 Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sun, 27 Mar 2022 10:56:50 +1100 Subject: [PATCH 6/7] chore: remove unecessary commented out lines --- asynciterator.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/asynciterator.ts b/asynciterator.ts index e11a87e..f7c4613 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -456,7 +456,6 @@ export class AsyncIterator extends EventEmitter { @returns {module:asynciterator.AsyncIterator} A new iterator that maps the items from this iterator */ map(map: (item: T) => D, self?: any): AsyncIterator { - // return this.transform({ map: self ? map.bind(self) : map }); return new MappingIterator(this, self ? map.bind(self) : map); } @@ -471,7 +470,6 @@ export class AsyncIterator extends EventEmitter { filter(filter: (item: T) => boolean, self?: any): AsyncIterator; filter(filter: (item: T) => boolean, self?: any): AsyncIterator { return new FilteringIterator(this, self ? filter.bind(self) : filter); - // return this.transform({ filter: self ? filter.bind(self) : filter }); } /** @@ -512,7 +510,6 @@ export class AsyncIterator extends EventEmitter { @returns {module:asynciterator.AsyncIterator} A new iterator that skips the given number of items */ skip(offset: number): AsyncIterator { - // return this.transform({ offset }); return new SkippingIterator(this, offset); } @@ -523,7 +520,6 @@ export class AsyncIterator extends EventEmitter { @returns {module:asynciterator.AsyncIterator} A new iterator with at most the given number of items */ take(limit: number): AsyncIterator { - // return this.transform({ limit }); return new LimitingIterator(this, limit); } From 1ec39fbb12497ad46ea5409ba683deb4cbb2079c Mon Sep 17 00:00:00 2001 From: Jesse Wright Date: Sun, 27 Mar 2022 12:32:20 +1100 Subject: [PATCH 7/7] feat: optimize chained mapping and filtering --- asynciterator.ts | 138 +++++++++++++++++++++++++++++++++++++ test/AsyncIterator-test.js | 84 ++++++++++++++++++++++ 2 files changed, 222 insertions(+) diff --git a/asynciterator.ts b/asynciterator.ts index f7c4613..9ee0446 100644 --- a/asynciterator.ts +++ b/asynciterator.ts @@ -459,6 +459,14 @@ export class AsyncIterator extends EventEmitter { return new MappingIterator(this, self ? map.bind(self) : map); } + /** + MultiMaps items according to a synchronous generator (hence no need for buffering) + @param {Function} multiMap The function to multiMap items with + */ + multiMap(multiMap: (item: T) => Generator): AsyncIterator { + return new MultiMappingIterator(this, multiMap); + } + /** Return items from this iterator that match the filter. After this operation, only read the returned iterator instead of the current one. @@ -1279,6 +1287,32 @@ export class SynchronousTransformIterator extends AsyncIterator { } } +export class MultiMappingIterator extends SynchronousTransformIterator { + protected readonly _map: (item: S) => Generator; + private generator?: Generator; + + constructor(source: AsyncIterator, map: (item: S) => Generator) { + super(source); + this._map = map; + } + + read(): D | null { + let _item; + + // eslint-disable-next-line no-constant-condition + while (true) { + if (!this.generator) { + if ((_item = this._source.read()) === null) + return null; + this.generator = this._map(_item); + } + if (!(_item = this.generator.next()).done) + return _item.value; + this.generator = undefined; + } + } +} + export class MappingIterator extends SynchronousTransformIterator { protected readonly _map: (item: S) => D; @@ -1293,6 +1327,28 @@ export class MappingIterator extends SynchronousTransformIterator(map: (item: D) => T, self?: any): AsyncIterator { + return new MultiMapFilterTransformIterator(this._source, { + filter: false, + function: self ? map.bind(self) : map, + next: { + filter: false, + function: this._map, + }, + }); + } + + filter(filter: (item: D) => boolean, self?: any): AsyncIterator { + return new MultiMapFilterTransformIterator(this._source, { + filter: true, + function: self ? filter.bind(self) : filter, + next: { + filter: false, + function: this._map, + }, + }); + } } export class FilteringIterator extends SynchronousTransformIterator { @@ -1311,6 +1367,28 @@ export class FilteringIterator extends SynchronousTransformIterator { } return null; } + + map(map: (item: T) => D, self?: any): AsyncIterator { + return new MultiMapFilterTransformIterator(this._source, { + filter: false, + function: self ? map.bind(self) : map, + next: { + filter: true, + function: this._filter, + }, + }); + } + + filter(filter: (item: T) => boolean, self?: any): AsyncIterator { + return new MultiMapFilterTransformIterator(this._source, { + filter: true, + function: self ? filter.bind(self) : filter, + next: { + filter: true, + function: this._filter, + }, + }); + } } export class SkippingIterator extends SynchronousTransformIterator { @@ -1359,6 +1437,66 @@ export class LimitingIterator extends SynchronousTransformIterator { } } +interface Transform { + filter: boolean, + function: Function, + next?: Transform +} + +export class MultiMapFilterTransformIterator extends SynchronousTransformIterator { + private _transformation?: (item: S) => D | null; + + constructor(source: AsyncIterator, private transforms: Transform) { + super(source); + } + + protected transformation(_item: S): D | null { + if (!this._transformation) { + let _transforms: Transform | undefined = this.transforms; + + const { filter, function: func } = _transforms!; + + this._transformation = filter ? + ((item: any) => func(item) ? item : null) : + func as any; + + while ((_transforms = _transforms!.next) !== undefined) { + const { filter: _filter, function: _func } = _transforms; + const t = this._transformation!; + + this._transformation = _filter ? + (item: any) => _func(item) ? t(item) : null : + (item: any) => t(_func(item)); + } + } + return this._transformation!(_item); + } + + read(): D | null { + let item; + while ((item = this._source.read()) !== null) { + if ((item = this.transformation(item)) !== null) + return item; + } + return null; + } + + map(map: (item: D) => T, self?: any): AsyncIterator { + return new MultiMapFilterTransformIterator(this._source, { + filter: false, + function: self ? map.bind(self) : map, + next: this.transforms, + }); + } + + filter(filter: (item: D) => boolean, self?: any): AsyncIterator { + return new MultiMapFilterTransformIterator(this._source, { + filter: true, + function: self ? filter.bind(self) : filter, + next: this.transforms, + }); + } +} /** An iterator that generates items based on a source iterator diff --git a/test/AsyncIterator-test.js b/test/AsyncIterator-test.js index 37a74d6..d7527b1 100644 --- a/test/AsyncIterator-test.js +++ b/test/AsyncIterator-test.js @@ -5,6 +5,7 @@ import { ENDED, DESTROYED, scheduleTask, + range, } from '../dist/asynciterator.js'; import { EventEmitter } from 'events'; @@ -1307,4 +1308,87 @@ describe('AsyncIterator', () => { }); }); }); + describe('Testing chains fo maps and filters', () => { + let iterator; + beforeEach(() => { + iterator = range(0, 2); + }); + it('Should handle no transforms', async () => { + iterator.read().should.equal(0); + iterator.read().should.equal(1); + iterator.read().should.equal(2); + }); + it('Should handle no transforms arrayified', async () => { + (await iterator.toArray()).should.deep.equal([0, 1, 2]); + }); + it('Should apply maps that doubles correctly', async () => { + (await iterator.map(x => x * 2).toArray()).should.deep.equal([0, 2, 4]); + }); + it('Should apply maps that doubles correctly', async () => { + (await iterator.map(x => `x${x}`).toArray()).should.deep.equal(['x0', 'x1', 'x2']); + }); + it('Should apply filter correctly', async () => { + (await iterator.filter(x => x % 2 === 0).toArray()).should.deep.equal([0, 2]); + }); + it('Should apply filter then map correctly', async () => { + (await iterator.filter(x => x % 2 === 0).map(x => `x${x}`).toArray()).should.deep.equal(['x0', 'x2']); + }); + it('Should apply map then filter correctly (1)', async () => { + (await iterator.map(x => x).filter(x => x % 2 === 0).toArray()).should.deep.equal([0, 2]); + }); + it('Should apply map then filter to false correctly', async () => { + (await iterator.map(x => `x${x}`).filter(x => true).toArray()).should.deep.equal(['x0', 'x1', 'x2']); + }); + it('Should apply map then filter to true correctly', async () => { + (await iterator.map(x => `x${x}`).filter(x => false).toArray()).should.deep.equal([]); + }); + it('Should apply filter to false then map correctly', async () => { + (await iterator.filter(x => true).map(x => `x${x}`).toArray()).should.deep.equal(['x0', 'x1', 'x2']); + }); + it('Should apply filter to true then map correctly', async () => { + (await iterator.filter(x => false).map(x => `x${x}`).filter(x => false).toArray()).should.deep.equal([]); + }); + it('Should apply filter one then double', async () => { + (await iterator.filter(x => x !== 1).map(x => x * 2).toArray()).should.deep.equal([0, 4]); + }); + it('Should apply double then filter one', async () => { + (await iterator.map(x => x * 2).filter(x => x !== 1).toArray()).should.deep.equal([0, 2, 4]); + }); + it('Should apply map then filter correctly', async () => { + (await iterator.map(x => `x${x}`).filter(x => (x[1] === '0')).toArray()).should.deep.equal(['x0']); + }); + it('Should correctly apply 3 filters', async () => { + (await range(0, 5).filter(x => x !== 1).filter(x => x !== 2).filter(x => x !== 2).toArray()).should.deep.equal([0, 3, 4, 5]); + }); + it('Should correctly apply 3 maps', async () => { + (await range(0, 1).map(x => x * 2).map(x => `z${x}`).map(x => `y${x}`).toArray()).should.deep.equal(['yz0', 'yz2']); + }); + it('Should correctly apply a map, followed by a filter, followed by another map', async () => { + (await range(0, 1).map(x => x * 2).filter(x => x !== 2).map(x => `y${x}`).toArray()).should.deep.equal(['y0']); + }); + it('Should correctly apply a filter-map-filter', async () => { + (await range(0, 2).filter(x => x !== 1).map(x => x * 3).filter(x => x !== 6).toArray()).should.deep.equal([0]); + }); + it('Should handle transforms', async () => { + iterator = iterator.multiMap(function* (data) { + yield `x${data}`; + yield `y${data}`; + }); + (await iterator.toArray()).should.deep.equal(['x0', 'y0', 'x1', 'y1', 'x2', 'y2']); + }); + it('Should handle transforms and maps', async () => { + iterator = iterator.multiMap(function* (data) { + yield `x${data}`; + yield `y${data}`; + }).map(x => `z${x}`); + (await iterator.toArray()).should.deep.equal(['zx0', 'zy0', 'zx1', 'zy1', 'zx2', 'zy2']); + }); + it('Should handle maps and transforms', async () => { + iterator = iterator.map(x => `z${x}`).multiMap(function* (data) { + yield `x${data}`; + yield `y${data}`; + }); + (await iterator.toArray()).should.deep.equal(['xz0', 'yz0', 'xz1', 'yz1', 'xz2', 'yz2']); + }); + }); });