【问题标题】:How to get the nth value of a JavaScript generator?如何获取 JavaScript 生成器的第 n 个值?
【发布时间】:2015-08-05 06:47:19
【问题描述】:

如何获得生成器的第 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

【问题讨论】:

  • 你试过循环吗?

标签: javascript generator ecmascript-6


【解决方案1】:

你可以定义像in 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/

同样,也可以重新实现 pythonic 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/

现实世界的实现需要更多的工作,但你明白了。

【讨论】:

    【解决方案2】:

    As T.J. Crowder pointed out,无法直接获取nth 元素,因为值是按需生成的,并且只能使用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 ]
    

    【讨论】:

      【解决方案3】:

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

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

      【讨论】:

      • 因为the iterator interface 除了next 之外没有定义其他任何东西。 :-)
      • 我确实喜欢 Qantas 94 Heavy 与 Array.from 的横向思考,尽管它当然会产生后果(消耗所有生成器的值,正如你在评论中指出的那样,他现在已删除 CW,这将是......有问题......使用无限生成器)。
      • 当然可以Array.from(iter_take(index(), n))[n-1]
      • ……甚至更好,iter_drop(index(), n-1).next()
      • 这些只是你自己想出来的名字,对吧?对于像上面这样写成循环的假设函数? (只是为了避免疑问,因为它们不在规范中[也不在规范中使用的通常命名约定中]。)
      【解决方案4】:

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

      var iter = gen();
      

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

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

      ...当您只对 nth 值感兴趣时,您可以跳过 map 部分,然后:

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

      或者:

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

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

      【讨论】:

        【解决方案5】:

        我想避免不必要地创建数组或其他中间值。这就是我对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
        

        展开下面的 sn-p 以在浏览器中验证结果 -

        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))

        【讨论】:

          猜你喜欢
          • 2017-11-20
          • 2020-01-13
          • 1970-01-01
          • 2017-10-23
          • 2018-11-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多