How can I get the nth value of a generator?
function *index() {
let x = 0;
while(true)
yield x++;
}
// the 1st value
let a = index();
console.log(a.
A simple loop will do:
let n = 10,
iter = index();
while (--n > 0) iter.next();
console.log(iter.next().value); // 9
You can define an enumeration method like in python:
function *enumerate(it, start) {
start = start || 0;
for(let x of it)
yield [start++, x];
}
and then:
for(let [n, x] of enumerate(index()))
if(n == 6) {
console.log(x);
break;
}
http://www.es6fiddle.net/ia0rkxut/
Along the same lines, one can also reimplement pythonic range and islice:
function *range(start, stop, step) {
while(start < stop) {
yield start;
start += step;
}
}
function *islice(it, start, stop, step) {
let r = range(start || 0, stop || Number.MAX_SAFE_INTEGER, step || 1);
let i = r.next().value;
for(var [n, x] of enumerate(it)) {
if(n === i) {
yield x;
i = r.next().value;
}
}
}
and then:
console.log(islice(index(), 6, 7).next().value);
http://www.es6fiddle.net/ia0s6amd/
A real-world implementation would require a bit more work, but you got the idea.
You could create an array of size n, and use Array.from and its second argument to fetch the values you need. So assuming iter is the iterator from generator gen:
var iter = gen();
Then the first n values can be fetched as follows:
var values = Array.from(Array(n), iter.next, iter).map(o => o.value)
...and when you are only interested in the nth value, you could skip the map part, and do:
var value = Array.from(Array(n), iter.next, iter).pop().value
Or:
var value = [...Array(n)].reduce(iter.next.bind(iter), 1).value
The downside is that you still (temporarily) allocate an array of size n.
As T.J. Crowder pointed out, there is no way to get to the nth element directly, as the values are generated on demand and only the immediate value can be retrieved with the next function. So, we need to explicitly keep track of the number of items consumed.
The only solution is using a loop and I prefer iterating it with for..of.
We can create a function like this
function elementAt(generator, n) {
"use strict";
let i = 0;
if (n < 0) {
throw new Error("Invalid index");
}
for (let value of generator) {
if (i++ == n) {
return value;
}
}
throw new Error("Generator has fewer than " + n + " elements");
}
and then invoke it like this
console.log(elementAt(index(), 10));
// 10
Another useful function might be, take, which would allow you to take first n elements from a generator, like this
function take(generator, n) {
"use strict";
let i = 1,
result = [];
if (n <= 0) {
throw new Error("Invalid index");
}
for (let value of generator) {
result.push(value);
if (i++ == n) {
return result;
}
}
throw new Error("Generator has fewer than " + n + " elements");
}
console.log(take(index(), 10))
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]