Skip to content

Commit

Permalink
Implement Iterator#zipLongest
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
lundibundi committed Jul 27, 2020
1 parent ca1c520 commit e9e5b4e
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 6 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ and this project adheres to

## [Unreleased][unreleased]

### Added

- `Iterator#zipLongest()` and `Iterator.zipLongest()` 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`.

## [2.2.0][] - 2020-07-10

### Added
Expand Down
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ $ npm install @metarhia/common
- [Iterator](#class-iterator)
- [Iterator.range](#iteratorrangestart-stop-step)
- [Iterator.zip](#iteratorzipiterators)
- [Iterator.zipLongest](#iteratorziplongestiterators)
- [Iterator.prototype.constructor](#iteratorprototypeconstructorbase)
- [Iterator.prototype.apply](#iteratorprototypeapplyfn)
- [Iterator.prototype.chain](#iteratorprototypechainiterators)
Expand Down Expand Up @@ -174,6 +175,7 @@ $ npm install @metarhia/common
- [Iterator.prototype.toArray](#iteratorprototypetoarray)
- [Iterator.prototype.toObject](#iteratorprototypetoobject)
- [Iterator.prototype.zip](#iteratorprototypezipiterators)
- [Iterator.prototype.zipLongest](#iteratorprototypeziplongestiterators)
- [iter](#iterbase)
- [iterEntries](#iterentriesobj)
- [iterKeys](#iterkeysobj)
Expand Down Expand Up @@ -1072,6 +1074,18 @@ 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>`][array]

_Returns:_ `<Iterator>`

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>`][undefined]. Iteration continues until the longest iterable is
exhausted.

#### Iterator.prototype.constructor(base)

#### Iterator.prototype.apply(fn)
Expand Down Expand Up @@ -1259,6 +1273,18 @@ 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>`][array]

_Returns:_ `<Iterator>`

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>`][undefined]. Iteration continues until the longest iterable is
exhausted.

### iter(base)

### iterEntries(obj)
Expand Down
37 changes: 34 additions & 3 deletions lib/iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,17 @@ 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 <undefined>. Iteration continues until the longest iterable
// is exhausted.
// iterators <Array>
// Returns: <Iterator>
zipLongest(...iterators) {
return new ZipIterator(this, iterators, true);
}

chain(...iterators) {
return new ChainIterator(this, iterators);
}
Expand Down Expand Up @@ -346,6 +357,17 @@ 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 <undefined>. Iteration continues until the longest iterable
// is exhausted.
// iterators <Array>
// Returns: <Iterator>
static zipLongest(base, ...iterators) {
return new ZipIterator(toIterator(base), iterators, true);
}

// Create iterator iterating over the range
// Signature: start, stop[, step]
// start <number>
Expand Down Expand Up @@ -534,26 +556,35 @@ class TakeWhileIterator extends Iterator {
}

class ZipIterator extends Iterator {
constructor(base, iterators) {
constructor(base, iterators, longest = false) {
super(base);
this.iterators = iterators.map(toIterator);
this.longest = longest;
}

next() {
const result = [];

const next = this.base.next();
if (next.done) {
if (next.done && !this.longest) {
return next;
}
result.push(next.value);
let allDone = next.done;

for (const iterator of this.iterators) {
const next = iterator.next();
if (next.done) {
if (next.done && !this.longest) {
return next;
}
result.push(next.value);
allDone = allDone && next.done;
}

if (allDone) {
// Shortcut all consecutive calls to the initial check.
this.longest = false;
return { done: true, value: undefined };
}
return { done: false, value: result };
}
Expand Down
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 70 additions & 0 deletions test/iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,76 @@ 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.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.test('Iterator.chain', test => {
const it = iter(array).take(1);
const itr = iter(array)
Expand Down

0 comments on commit e9e5b4e

Please sign in to comment.