diff --git a/CHANGELOG.md b/CHANGELOG.md index 34b600ed..eae50d84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to ## [Unreleased][unreleased] +### Added + +- `Iterator#zipLongest()` and `Iterator.zipLongest()`, + `Iterator#zipLongestWith()` and `Iterator.zipLongestWith()` similar to + `Iterator#zip` but instead of stopping when shortest iterator is exhausted + continues until the longest iterable is exhausted filling-in missing values + with `undefined` or provided `defaultValue`. + ## [2.2.0][] - 2020-07-10 ### Added diff --git a/README.md b/README.md index 27434ccc..f7bad052 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,8 @@ $ npm install @metarhia/common - [Iterator](#class-iterator) - [Iterator.range](#iteratorrangestart-stop-step) - [Iterator.zip](#iteratorzipiterators) + - [Iterator.zipLongest](#iteratorziplongestiterators) + - [Iterator.zipLongestWith](#iteratorziplongestwithdefaultvalue-iterators) - [Iterator.prototype.constructor](#iteratorprototypeconstructorbase) - [Iterator.prototype.apply](#iteratorprototypeapplyfn) - [Iterator.prototype.chain](#iteratorprototypechainiterators) @@ -174,6 +176,8 @@ $ npm install @metarhia/common - [Iterator.prototype.toArray](#iteratorprototypetoarray) - [Iterator.prototype.toObject](#iteratorprototypetoobject) - [Iterator.prototype.zip](#iteratorprototypezipiterators) + - [Iterator.prototype.zipLongest](#iteratorprototypeziplongestiterators) + - [Iterator.prototype.zipLongestWith](#iteratorprototypeziplongestwithdefaultvalue-iterators) - [iter](#iterbase) - [iterEntries](#iterentriesobj) - [iterKeys](#iterkeysobj) @@ -1072,6 +1076,30 @@ Returns an iterator of Arrays where the i-th tuple contains the i-th element from each of the passed iterators. The iterator stops when the shortest input iterable is exhausted. +#### Iterator.zipLongest(...iterators) + +- `...iterators`: [``][array] iterators to be aggregated + +_Returns:_ `` + +Creates an iterator that aggregates elements from each of the iterators. + +If the iterables are of uneven length, missing values are filled-in with +[``][undefined]. Iteration continues until the longest iterable is +exhausted. + +#### Iterator.zipLongestWith(defaultValue, ...iterators) + +- `defaultValue`: `` value to fill-in missing values with +- `...iterators`: [``][array] iterators to be aggregated + +_Returns:_ `` + +Creates an iterator that aggregates elements from each of the iterators. + +If the iterables are of uneven length, missing values are filled-in with +``. Iteration continues until the longest iterable is exhausted. + #### Iterator.prototype.constructor(base) #### Iterator.prototype.apply(fn) @@ -1259,6 +1287,30 @@ Returns an iterator of Arrays where the i-th tuple contains the i-th element from each of the passed iterators. The iterator stops when the shortest input iterable is exhausted. +#### Iterator.prototype.zipLongest(...iterators) + +- `...iterators`: [``][array] iterators to be aggregated + +_Returns:_ `` + +Creates an iterator that aggregates elements from each of the iterators. + +If the iterables are of uneven length, missing values are filled-in with +[``][undefined]. Iteration continues until the longest iterable is +exhausted. + +#### Iterator.prototype.zipLongestWith(defaultValue, ...iterators) + +- `defaultValue`: `` value to fill-in missing values with +- `...iterators`: [``][array] iterators to be aggregated + +_Returns:_ `` + +Creates an iterator that aggregates elements from each of the iterators. + +If the iterables are of uneven length, missing values are filled-in with +``. Iteration continues until the longest iterable is exhausted. + ### iter(base) ### iterEntries(obj) diff --git a/lib/iterator.js b/lib/iterator.js index fcb97a7f..ebb0243c 100644 --- a/lib/iterator.js +++ b/lib/iterator.js @@ -167,6 +167,29 @@ class Iterator { return new ZipIterator(this, iterators); } + // Creates an iterator that aggregates elements from each of the iterators. + // + // If the iterables are of uneven length, missing values are filled-in + // with . Iteration continues until the longest iterable + // is exhausted. + // ...iterators iterators to be aggregated + // Returns: + zipLongest(...iterators) { + return new ZipIteratorLongest(this, iterators); + } + + // Creates an iterator that aggregates elements from each of the iterators. + // + // If the iterables are of uneven length, missing values are filled-in + // with . Iteration continues until the longest iterable + // is exhausted. + // defaultValue value to fill-in missing values with + // ...iterators iterators to be aggregated + // Returns: + zipLongestWith(defaultValue, ...iterators) { + return new ZipIteratorLongest(this, iterators, defaultValue); + } + chain(...iterators) { return new ChainIterator(this, iterators); } @@ -346,6 +369,29 @@ class Iterator { return new ZipIterator(toIterator(base), iterators); } + // Creates an iterator that aggregates elements from each of the iterators. + // + // If the iterables are of uneven length, missing values are filled-in + // with . Iteration continues until the longest iterable + // is exhausted. + // ...iterators iterators to be aggregated + // Returns: + static zipLongest(base, ...iterators) { + return new ZipIteratorLongest(toIterator(base), iterators); + } + + // Creates an iterator that aggregates elements from each of the iterators. + // + // If the iterables are of uneven length, missing values are filled-in + // with . Iteration continues until the longest iterable + // is exhausted. + // defaultValue value to fill-in missing values with + // ...iterators iterators to be aggregated + // Returns: + static zipLongestWith(defaultValue, base, ...iterators) { + return new ZipIteratorLongest(toIterator(base), iterators, defaultValue); + } + // Create iterator iterating over the range // Signature: start, stop[, step] // start @@ -555,6 +601,38 @@ class ZipIterator extends Iterator { } result.push(next.value); } + + return { done: false, value: result }; + } +} + +class ZipIteratorLongest extends Iterator { + constructor(base, iterators, defaultValue) { + super(base); + this.done = false; + this.iterators = iterators.map(toIterator); + this.defaultValue = defaultValue; + } + + next() { + if (this.done) return { done: true, value: undefined }; + + const result = []; + + const next = this.base.next(); + result.push(!next.done ? next.value : this.defaultValue); + + let allDone = next.done; + for (const iterator of this.iterators) { + const next = iterator.next(); + result.push(!next.done ? next.value : this.defaultValue); + allDone = allDone && next.done; + } + + if (allDone) { + this.done = true; + return { done: true, value: undefined }; + } return { done: false, value: result }; } } diff --git a/package-lock.json b/package-lock.json index d8030854..52634259 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2929,9 +2929,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", "dev": true }, "loose-envify": { diff --git a/test/iterator.js b/test/iterator.js index 2dd66909..f75b6beb 100644 --- a/test/iterator.js +++ b/test/iterator.js @@ -315,6 +315,154 @@ metatests.test('Iterator.zip with multiple iterators', test => { test.end(); }); +metatests.testSync('Iterator#zipLongest with single iterator', test => { + const it = iter(array).take(1); + const toZip = iter(array).skip(2); + test.strictSame(it.zipLongest(toZip).toArray(), [ + [1, 3], + [undefined, 4], + ]); +}); + +metatests.testSync('Iterator#zipLongest with multiple iterators', test => { + const it = iter(array); + const itr = iter(array).take(3); + const iterator = iter(array).take(2); + test.strictSame(it.zipLongest(itr, iterator).toArray(), [ + [1, 1, 1], + [2, 2, 2], + [3, 3, undefined], + [4, undefined, undefined], + ]); +}); + +metatests.testSync('Iterator#zipLongest different length', test => { + const it = iter([1, 2, 3]); + test.strictSame(it.zipLongest(iter([1]), iter([1, 2, 3, 4, 5])).toArray(), [ + [1, 1, 1], + [2, undefined, 2], + [3, undefined, 3], + [undefined, undefined, 4], + [undefined, undefined, 5], + ]); +}); + +metatests.testSync('Iterator#zipLongestWith with single iterator', test => { + const s = Symbol('none'); + const it = iter(array).take(1); + const toZip = iter(array).skip(2); + test.strictSame(it.zipLongestWith(s, toZip).toArray(), [ + [1, 3], + [s, 4], + ]); +}); + +metatests.testSync('Iterator#zipLongestWith with multiple iterators', test => { + const s = Symbol('none'); + const it = iter(array); + const itr = iter(array).take(3); + const iterator = iter(array).take(2); + test.strictSame(it.zipLongestWith(s, itr, iterator).toArray(), [ + [1, 1, 1], + [2, 2, 2], + [3, 3, s], + [4, s, s], + ]); +}); + +metatests.testSync('Iterator#zipLongestWith different length', test => { + const s = Symbol('none'); + const it = iter([1, 2, 3]); + test.strictSame( + it.zipLongestWith(s, iter([1]), iter([1, 2, 3, 4, 5])).toArray(), + [ + [1, 1, 1], + [2, s, 2], + [3, s, 3], + [s, s, 4], + [s, s, 5], + ] + ); +}); + +metatests.testSync('Iterator.zipLongest with single iterator', test => { + const it1 = iter(array).take(1); + const it2 = iter(array).skip(2); + test.strictSame(Iterator.zipLongest(it1, it2).toArray(), [ + [1, 3], + [undefined, 4], + ]); +}); + +metatests.testSync('Iterator.zipLongest with multiple iterators', test => { + const it1 = iter(array); + const it2 = iter(array).take(3); + const it3 = iter(array).take(2); + test.strictSame(Iterator.zipLongest(it1, it2, it3).toArray(), [ + [1, 1, 1], + [2, 2, 2], + [3, 3, undefined], + [4, undefined, undefined], + ]); +}); + +metatests.testSync('Iterator.zipLongest different length', test => { + test.strictSame( + Iterator.zipLongest( + iter([1, 2, 3]), + iter([1]), + iter([1, 2, 3, 4, 5]) + ).toArray(), + [ + [1, 1, 1], + [2, undefined, 2], + [3, undefined, 3], + [undefined, undefined, 4], + [undefined, undefined, 5], + ] + ); +}); + +metatests.testSync('Iterator.zipLongestWith with single iterator', test => { + const it1 = iter(array).take(1); + const it2 = iter(array).skip(2); + test.strictSame(Iterator.zipLongestWith(true, it1, it2).toArray(), [ + [1, 3], + [true, 4], + ]); +}); + +metatests.testSync('Iterator.zipLongest with multiple iterators', test => { + const it1 = iter(array); + const it2 = iter(array).take(3); + const it3 = iter(array).take(2); + test.strictSame(Iterator.zipLongestWith(true, it1, it2, it3).toArray(), [ + [1, 1, 1], + [2, 2, 2], + [3, 3, true], + [4, true, true], + ]); +}); + +metatests.testSync('Iterator.zipLongestWith different length', test => { + const s = Symbol('none'); + test.strictSame( + Iterator.zipLongestWith( + s, + iter([1, 2, 3]), + iter([1]), + iter([1, 2, 3, 4, 5]) + ).toArray(), + [ + [1, 1, 1], + [2, s, 2], + [3, s, 3], + [s, s, 4], + [s, s, 5], + ] + ); +}); + metatests.test('Iterator.chain', test => { const it = iter(array).take(1); const itr = iter(array)