Reputation: 26374
I've got two iterators that may or may not be iterable. If they were both guaranteed to be iterable, I could probably use an array and call .values()
to get another iterable iterator:
[...iter1, ...iter2].values(); // simple :)
However, I'm struggling to find a way to chain them together if they aren't iterable. I considered using a generator function, but that would return a generator and not an iterator. Ideally, the returned iterator should also not be iterable.
This returns an iterable iterator and only works for iterable iterators:
const iter1 = [1, 2, 3].values();
const iter2 = [4, 5, 6].values();
console.log([...[...iter1, ...iter2].values()]);
This works for iterators that aren't iterable but instead returns a generator:
const iter1 = [1, 2, 3].values();
const iter2 = [4, 5, 6].values();
console.log([...(function* () {
let item;
while (!(item = iter1.next()).done) yield item.value;
while (!(item = iter2.next()).done) yield item.value;
})()]);
So how would I take two iterators and chain them together to create a new iterator?
Maybe an XY problem; I'm concerned about iterability since TypeScript has separate interfaces: Iterator and IterableIterator. Is it implying that you can create an iterator that isn't iterable, or are all iterators in JavaScript iterable, and TypeScript is just being weird?
Upvotes: 3
Views: 369
Reputation: 351021
If you have iterators that are not iterable, then the reasonable thing to do since ECMAScript 2025 is to turn them into instances of Iterator
with Iterator.from
. Such instances are called iterator helpers, and are iterable iterators. Then the iterator helper methods become available, such as flatMap
. Here is how you can chain iterators even when originally they are not iterable:
// Function to help this demo:
function createNonIterableIterator(...values) {
let i = 0;
return {
next: () => ({ value: values[i++], done: i > values.length })
};
}
const iter1 = createNonIterableIterator(1, 2, 3);
const iter2 = createNonIterableIterator("a", "b");
const iter3 = createNonIterableIterator(false, true);
const chained = [iter1, iter2, iter3].values().flatMap(Iterator.from);
// The result iterator will also be iterable:
console.log(...chained);
Upvotes: 0
Reputation: 25930
An elegant solution, using iter-ops, which can concatenate both iterables and iterators:
import {pipe, concat} from 'iter-ops';
const iter1 = [1, 2, 3].values(); // test iterator 1
const iter2 = [4, 5, 6].values(); // test iterator 2
const i = pipe([], concat(iter1, iter2)); //=> IterableIterator<number>
console.log(...i); //=> 1 2 3 4 5 6
To test it with proper Iterator
-only (non-iterable) inputs, let's create a helper below, and replace our iter1
and iter2
inputs:
function createIterator(values: number[]): Iterator<number> {
let index = 0;
return {
next() {
if (index < values.length) {
return {value: values[index++], done: false};
}
return {value: undefined, done: true}
}
};
}
const iter1 = createIterator([1, 2, 3]); //=> Iterator<number>
const iter2 = createIterator([4, 5, 6]); //=> Iterator<number>
Concatenating the updated iter1
and iter2
will produce the same result.
Upvotes: 1
Reputation: 8890
I'll post the obvious answer that despite being old-fashioned and baroque, seems to fit the requirements:
class ConsIterable{
constructor(...iterators){
this.iterators = iterators;
this.current = 0;
}
next(){
let result;
do{
result = this.iterators[this.current].next();
if(result.done){
this.current++;
}
}while(result.done && this.current < this.iterators.length);
return result;
}
}
const iter1 = [1, 2, 3].values();
const iter2 = [4, 5, 6].values();
const chained_iterable = new ConsIterable(iter1, iter2);
let nxt;
do{
nxt = chained_iterable.next();
console.log(nxt);
}while(!nxt.done)
Upvotes: 3
Reputation: 26374
Ideally the returned iterator shouldn't be iterable as well, but if we mix the two possibilities mentioned in the post, we'll achieve:
const iter1 = [1, 2, 3].values();
const iter2 = [4, 5, 6].values();
const result = [];
let item;
while (!(item = iter1.next()).done) result.push(item.value);
while (!(item = iter2.next()).done) result.push(item.value);
const chained = result.values(); // but still iterable
However the returned iterator is still iterable. Honestly, I don't even know if you can even have an iterator that isn't iterable in JavaScript. I was under the impression that you could since TypeScript provides separate interfaces for them.
Deleting (with delete
keyword) @@iterator
also doesn't seem to work (?).
Upvotes: 0