How do I get the nth value of a JavaScript generator?

How can I get the nth value of the generator?

function *index() {
  let x = 0;
  while(true)
    yield x++;
}

// the 1st value
let a = index();
console.log(a.next().value); // 0

// the 3rd value
let b = index();
b.next();
b.next();
console.log(b.next().value); // 2

// the nth value?
let c = index();
let n = 10;
console.log(...); // 9

      

+3


source to share


4 answers


You can define an enumeration method, for example 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/

In the same lines, you can also implement 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/

Real world implementation will take a little more work, but you got the idea.

+4


source


As TJ Crowder pointed out , there is no way to go directly to the n

th element , since the values ​​are generated on demand and only the instantaneous value can be retrieved using the function next

. Thus, we need to explicitly track the number of consumed items.

The only solution is to use a loop, and I prefer to repeat 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 call it like this:



console.log(elementAt(index(), 10));
// 10

      


Another useful function might be take

that will allow you to take the first n

elements from the generator, for example

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 ]

      

+3


source


A simple loop will do:

let n = 10,
    iter = index();
while (--n > 0) iter.next();
console.log(iter.next().value); // 9

      

+1


source


You can create an array of size n and use Array.from

its second argument to get the values ​​you want. So, assuming which iter

is an iterator from a generator gen

:

var iter = gen();

      

Then the first n values ​​can be chosen as follows:

var values = Array.from(Array(n), iter.next, iter).map(o => o.value)

      

... and when you are only interested in the value of n th you can skip the part map

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 are still (temporarily) allocating an array of size n.

0


source







All Articles