如何获得JavaScript生成器的第n个值

How to get the nth value of a JavaScript generator?

本文关键字:个值 何获得 JavaScript      更新时间:2023-09-26

如何获得生成器的第n个值?

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

你可以像在python中那样定义一个枚举方法:

function *enumerate(it, start) {
   start = start || 0;
   for(let x of it)
     yield [start++, x];
}

然后:

for(let [n, x] of enumerate(index()))
  if(n == 6) {
    console.log(x);
    break;
  }
http://www.es6fiddle.net/ia0rkxut/

按照同样的思路,也可以重新实现python的rangeislice:
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;
    }
  }
}

然后:

console.log(islice(index(), 6, 7).next().value);
http://www.es6fiddle.net/ia0s6amd/

实际的实现需要更多的工作,但是您已经明白了。

正如T.J. Crowder指出的那样,没有办法直接获得n元素,因为值是按需生成的,只有next函数可以检索到立即的值。因此,我们需要显式地跟踪所消耗的项目数量。

唯一的解决方案是使用循环,我更喜欢用for..of迭代它。

我们可以创建一个像这样的函数

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");
}

,然后像这样调用

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

另一个有用的函数可能是take,它允许您从生成器中获取第一个n元素,如

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 ]

一个简单的循环就可以了:

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

您可以创建一个大小为n的数组,并使用Array.from和它的第二个参数来获取所需的值。假设iter是生成器gen的迭代器:

var iter = gen();

那么第一个n值可以按如下方式获取:

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

…当您只对n值感兴趣时,您可以跳过map部分,并执行:

var value = Array.from(Array(n), iter.next, iter).pop().value

或:

var value = [...Array(n)].reduce(iter.next.bind(iter), 1).value

缺点是您仍然(暂时)分配大小为n的数组。

我想避免不必要的创建数组或其他中间值。这是我的nth实现的结果-

function nth (iter, n)
{ for (const v of iter)
    if (--n < 0)
      return v
}

原题中的例子-

// the 1st value
console.log(nth(index(), 0))
// the 3rd value
console.log(nth(index(), 2))
// the 10th value
console.log(nth(index(), 9))
0
2
9

对于有限生成器,如果索引超出边界,结果将是undefined -

function* foo ()
{ yield 1
  yield 2
  yield 3
}
console.log(nth(foo(), 99))
undefined

展开下面的代码片段,在浏览器中验证结果-

function *index ()
{ let x = 0
  while (true)
    yield x++
}
function* foo ()
{ yield 1
  yield 2
  yield 3
}
function nth (iter, n) {
  for (const v of iter)
    if (--n < 0)
      return v
}
// the 1st value
console.log(nth(index(), 0))
// the 3rd value
console.log(nth(index(), 2))
// the 10th value?
console.log(nth(index(), 9))
// out-of-bounds?
console.log(nth(foo(), 99))