Skip to content

Commit

Permalink
Implement Iterator#zipLongest and Iterator#zipLongestWith
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 or provided defaultValue. Iteration continues until the
longest iterable is exhausted.
  • Loading branch information
lundibundi committed Jul 29, 2020
1 parent d16ec20 commit e978d0b
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 3 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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>`][array] iterators to be aggregated

_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.zipLongestWith(defaultValue, ...iterators)

- `defaultValue`: `<any>` value to fill-in missing values with
- `...iterators`: [`<Array>`][array] iterators to be aggregated

_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
`<defaultValue>`. Iteration continues until the longest iterable is exhausted.

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

#### Iterator.prototype.apply(fn)
Expand Down Expand Up @@ -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>`][array] iterators to be aggregated

_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.zipLongestWith(defaultValue, ...iterators)

- `defaultValue`: `<any>` value to fill-in missing values with
- `...iterators`: [`<Array>`][array] iterators to be aggregated

_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
`<defaultValue>`. Iteration continues until the longest iterable is exhausted.

### iter(base)

### iterEntries(obj)
Expand Down
78 changes: 78 additions & 0 deletions lib/iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <undefined>. Iteration continues until the longest iterable
// is exhausted.
// ...iterators <Array> iterators to be aggregated
// Returns: <Iterator>
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 <defaultValue>. Iteration continues until the longest iterable
// is exhausted.
// defaultValue <any> value to fill-in missing values with
// ...iterators <Array> iterators to be aggregated
// Returns: <Iterator>
zipLongestWith(defaultValue, ...iterators) {
return new ZipIteratorLongest(this, iterators, defaultValue);
}

chain(...iterators) {
return new ChainIterator(this, iterators);
}
Expand Down Expand Up @@ -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 <undefined>. Iteration continues until the longest iterable
// is exhausted.
// ...iterators <Array> iterators to be aggregated
// Returns: <Iterator>
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 <defaultValue>. Iteration continues until the longest iterable
// is exhausted.
// defaultValue <any> value to fill-in missing values with
// ...iterators <Array> iterators to be aggregated
// Returns: <Iterator>
static zipLongestWith(defaultValue, base, ...iterators) {
return new ZipIteratorLongest(toIterator(base), iterators, defaultValue);
}

// Create iterator iterating over the range
// Signature: start, stop[, step]
// start <number>
Expand Down Expand Up @@ -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 };
}
}
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.

148 changes: 148 additions & 0 deletions test/iterator.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit e978d0b

Please sign in to comment.