Let's say I have an iterator:
function* someIterator () {
yield 1;
yield 2;
yield 3;
}
let iter = someIterator();
... that I look at the next element to be iterated:
let next = iter.next(); // {value: 1, done: false}
... and I then use the iterator in a loop:
for(let i of iterator)
console.log(i);
// 2
// 3
The loop will not include the element looked at. I wish to see the next element while not taking it out of the iteration series.
In other words, I wish to implement:
let next = peek(iter); // {value: 1, done: false}, or alternatively just 1
for(let i of iterator)
console.log(i);
// 1
// 2
// 3
... and I wan't to do it without modifying the code for the iterable function.
What I've tried is in my answer. It works (which is why I made it an answer), but I worry that it builds an object that is more plex than it has to be. And I worry that it will not work for cases where the 'done' object is something different than { value = undefined, done = true }
. So any improved answers are very much wele.
Let's say I have an iterator:
function* someIterator () {
yield 1;
yield 2;
yield 3;
}
let iter = someIterator();
... that I look at the next element to be iterated:
let next = iter.next(); // {value: 1, done: false}
... and I then use the iterator in a loop:
for(let i of iterator)
console.log(i);
// 2
// 3
The loop will not include the element looked at. I wish to see the next element while not taking it out of the iteration series.
In other words, I wish to implement:
let next = peek(iter); // {value: 1, done: false}, or alternatively just 1
for(let i of iterator)
console.log(i);
// 1
// 2
// 3
... and I wan't to do it without modifying the code for the iterable function.
What I've tried is in my answer. It works (which is why I made it an answer), but I worry that it builds an object that is more plex than it has to be. And I worry that it will not work for cases where the 'done' object is something different than { value = undefined, done = true }
. So any improved answers are very much wele.
- Do you consider an option of implementing a custom extended iterator or you need a solution that works with native iterators? – Shlang Commented Apr 11, 2020 at 23:10
- If a native iterator can be wrapped or fed into the extended iterator, and if you don't have to iterate it to make that happen, then yes, I would consider it. – pwilcox Commented Apr 12, 2020 at 0:32
5 Answers
Reset to default 6Just a bit different idea is to use wrapper that makes an iterator kind of eagier.
function peekable(iterator) {
let state = iterator.next();
const _i = (function* (initial) {
while (!state.done) {
const current = state.value;
state = iterator.next();
const arg = yield current;
}
return state.value;
})()
_i.peek = () => state;
return _i;
}
function* someIterator () { yield 1; yield 2; yield 3; }
let iter = peekable(someIterator());
let v = iter.peek();
let peeked = iter.peek();
console.log(peeked.value);
for (let i of iter) {
console.log(i);
}
Instead of a peek
function, I built a peeker
function that calls next
, removing the element from the iterator, but then adds it back in by creating an iterable function that first yields the captured element, then yields the remaining items in the iterable.
function peeker(iterator) {
let peeked = iterator.next();
let rebuiltIterator = function*() {
if(peeked.done)
return;
yield peeked.value;
yield* iterator;
}
return { peeked, rebuiltIterator };
}
function* someIterator () { yield 1; yield 2; yield 3; }
let iter = someIterator();
let peeked = peeker(iter);
console.log(peeked.peeked);
for(let i of peeked.rebuiltIterator())
console.log(i);
Translating @Shlang's answer to AsyncIterables
and TypeScript:
type PeekableAsyncIterable<T> = AsyncIterable<T> & {
peek: () => Promise<T | null>;
}
/**
* Adds a `peek` method to `AsyncIterable<T>`, which
* peeks at the next element in the iterable without consuming it.
*/
export const peekable = <T>(asyncIterable: AsyncIterable<T>): PeekableAsyncIterable<T> => {
let nextElPromise = asyncIterable[Symbol.asyncIterator]().next()
const it = (async function * () {
let nonEmpty = true
while (nonEmpty) {
const result = await nextElPromise
nonEmpty = !result.done
if (nonEmpty) {
nextElPromise = asyncIterable[Symbol.asyncIterator]().next()
yield result.value
}
}
})() as unknown as PeekableAsyncIterable<T>
it.peek = async () => {
const result = await nextElPromise
return result.done
? null // if you have a `Result<T>` or `Either<T, E>` type, use it here instead of null
: result.value
}
return it
}
I'd like to contribute my way of solving this, based off of Shlang's answer, by creating a Peekable
class.
Here it is in TypeScript:
class Peekable<T> implements Iterator<T, void> {
public peek: IteratorResult<T, void>;
constructor(private iterator: Iterator<T, void>) {
this.peek = iterator.next();
}
next() {
const curr = this.peek;
this.peek = this.iterator.next();
return curr;
}
[Symbol.iterator]() {
return this;
}
}
It can be used like any other iterator, only with an extra peek
property that shows the result of the next iteration:
function* countDownFrom(n: number) {
for (n; n > 0; n--) yield n;
}
const countDown = new Peekable(countDownFrom(15));
for (const num of countDown) {
console.log(num);
if (num === 7) {
console.log("