If I look up the zip function, it just combines each entry by index, you should be able to simulate that easily enough with a normal function, but to keep the iterator aspect of it, you can use a generator function
const b = [2,3,5];
const d = [['e1' , 'e2' , 'e3'], ['i1', 'i2'],
['o1', 'o2', 'o3', 'o4']];
function *zip( ...iterables ) {
const inputs = iterables.map( iterator => Array.isArray( iterator ) ? iterator : Array.from( iterator ) );
const size = Math.min( ...inputs.map( i => i.length ) );
for (let i = 0; i < size; i++) {
yield inputs.map( input => input[i] );
}
}
console.log( 'iterating with arrays' );
for (const entry of zip(b, d)) {
console.log( entry );
}
console.log( 'iterating with other iterators' );
for (const entry of zip(b, zip(b, d))) {
console.log( entry );
}
What did bug me about the above answer is that it will potentially read through any iteratable at the start already, making it potentially inefficient (depending on the size of the input, and how complex each iteration is)
So thinking about this, instead of making all the iterators an array, and evaluate them like this, it actually makes more sense to make use of [Symbol.iterator]
on every argument and then go through the iterations one by one.
I added a simple nextNumberGenerator
that creates an array of up to max numbers to prove the point. In the original answer, it would have iterated all max numbers before any result comes available, but with the updated functionality, it will only evaluate as far as other combinations are possible
I only added a test still with a Set
argument, and did some input validation on it. So if you use this code somewhere, be sure to write your own tests for your use cases :)
const b = [2, 3, 5];
const d = [['e1', 'e2', 'e3'], ['i1', 'i2'],
['o1', 'o2', 'o3', 'o4']];
const set = new Set(['Angus', 'Bob', 'Daisy']);
function* nextNumberGenerator(max) {
let current = 0;
while (current < max) {
console.log('returning ' + current + ' of ' + max);
yield current++;
}
}
function* zip(...iterables) {
const inputs = iterables.map(iterator => iterator[Symbol.iterator] ? iterator[Symbol.iterator]() : null);
if (inputs.some(v => v === null)) {
throw new Error('ArgumentException: At least one argument is not iterable');
}
let done = false;
while (!done) {
const result = [];
for (const iterator of inputs) {
const value = iterator.next();
if (value.done) {
done = true;
break;
}
result.push(value.value);
}
if (!done) {
yield result;
}
}
}
console.log('iterating with arrays');
for (const entry of zip(b, d, set)) {
console.log(entry);
}
console.log('iterating with other iterators');
for (const entry of zip(b, zip(b, d), nextNumberGenerator(10), set)) {
console.log(entry);
}
Also observe, this will only handle synchronous iterables, but no asynchronous ones.
For more information on the iterables, you can have a look here: