【问题标题】:yield from a list of generators created from an array从从数组创建的生成器列表中产生
【发布时间】:2018-01-24 07:27:19
【问题描述】:

我有这个递归生成器

var obj = [1,2,3,[4,5,[6,7,8],9],10]

function *flat(x) {
    if (Array.isArray(x))
        for (let y of x)
            yield *flat(y)
    else
        yield 'foo' + x;

}

console.log([...flat(obj)])

它工作正常,但我不喜欢for 部分。有没有办法在功能上编写它?我试过了

if (Array.isArray(x))
   yield *x.map(flat)

这没用。

有没有办法在没有for循环的情况下编写上述函数?

【问题讨论】:

    标签: javascript ecmascript-6 generator yield


    【解决方案1】:

    有没有办法在没有for 循环的情况下按功能编写它?

    不,不是真的。 (当然,您总是可以选择递归,但我会质疑这种方法的用处)。

    我们正在寻找的是迭代器的功能组合器:

    function* of(x) { // also known as `pure` or `return`
        yield x;
    }
    function map(f) { return function* (xs) { // also known as `fmap`
        for (const x of xs)
            yield f(x);
    }
    function* join(xss) { // also known as `concat` (not `append`!) or `flatten` (but non-recursive!)
        for (const xs of xss)
            for (const x of xs)
                yield x;
    }
    function chain(f) { return function* (xs) { // also known as `concatMap` or `bind`
        for (const x of xs)
            const ys = f(x);
            for (const y of ys)
                yield y;
    }
    // or const chain = f => compose(concat, map(f)) :-)
    

    现在我们可以将迭代器视为一个monad,不再关心实现。

    如您所见,我没有使用上面的语法yield* xs,它(基本上)只是糖

    for (const x of xs)
        yield x;
    

    在您的实现中看起来很奇怪的是外部循环和内部非循环之间的差异。在一个最佳世界中,会有一个 yield** 语法 可以完成 join 所做的事情,但实际上并没有。所以我们只能用上面的辅助函数很好地实现你的函数:

    function* flat(x) {
        if (Array.isArray(x))
            yield* chain(flat)(x);
        else
            yield* of('foo' + x); // foreshadowing
    }
    

    或者只是

    function flat(x) {
        return Array.isArray(x) ? chain(flat)(x) : of('foo' + x);
    }
    

    【讨论】:

    • 不错!要求“功能性”为“单子”答案做好准备;)对您的代码提出一个问题:为什么ysfor(const y of f(x)) 有什么问题还是只是风格偏好?
    • @georg 这只是通过变量名显示f 返回一个没有类型签名的列表,并强调yx 处于不同的级别。不过看起来很奇怪,这不是我的风格偏好:-)
    【解决方案2】:

    您可以使用 rest parameters ... 并检查其余数组的长度以获取生成器的另一个调用

    function* flat(a, ...r) {
        if (Array.isArray(a)) {
            yield* flat(...a);
        } else {
            yield 'foo' + a;
        }
        if (r.length) {
            yield* flat(...r);
        }
    }
    
    var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
    console.log([...flat(obj)])
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    类似的方法,但使用 spread 生成器,用于调用具有扩展值的移交的生成器。

    function* spread(g, a, ...r) {
        yield* g(a);
        if (r.length) {
            yield* spread(g, ...r);
        }
    }
    
    function* flat(a) {
        if (Array.isArray(a)) {
            yield* spread(flat, ...a);
        } else {
            yield 'foo' + a;
        }
    }
    
    var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
    console.log([...flat(obj)])
    .as-console-wrapper { max-height: 100% !important; top: 0; }

    【讨论】:

      【解决方案3】:

      map 是个好主意,但您需要将生成的生成器对象数组减少为一个生成器对象:

      function *flat(x) {
          if (Array.isArray(x)) 
              yield *x.map(flat).reduce((a, b) => function*() { yield *a; yield *b }());
          else
              yield 'foo' + x;
      }
      
      var obj = [1,2,3,[4,5,[6,7,8],9],10];
      console.log([...flat(obj)]);
      .as-console-wrapper { max-height: 100% !important; top: 0; }

      【讨论】:

      • 如果我们要去arr.map(fn).reduce(y* a; y* b),我们不能把它重写为一个reduce arr.reduce(y* a; y* fn(b))吗?
      • @Paul,然后a 将在某些时候成为一个原始值,它是不可迭代的,因此yield* a 将引发异常。
      • @PaulS.:这就是 Yury 所做的,如果您这样做,则必须提供一个 init 值(一个空生成器)。
      • @trincot:很好的解决方案,我们可以将reduce分解成一个单独的函数(在python中称为chain),然后使用yield *chain(x.map(....)
      【解决方案4】:

      可能是这样的

      var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10];
      
      function* flat(x) {
          if (Array.isArray(x)) {
              yield x.map(v => {
                  return [...flat(v)].join();
              });
          } else yield "foo" + x;
      }
      
      console.log([...flat(obj)]);
      

      【讨论】:

      • 谢谢,但这不是我想要的。
      【解决方案5】:

      您可以将数组简化为生成器。但这对我来说看起来比 for 循环更糟糕(虽然是功能性的:))

      var obj = [1, 2, 3, [4, 5, [6, 7, 8], 9], 10]
      
      function* flat(x) {
        if (Array.isArray(x))
          yield * x.reduceRight(
            (f, y) => function*() {
              yield * flat(y);
              yield * f()
            },
            function*() {}
          )()
        else
          yield 'foo' + x;
      
      }
      
      console.log([...flat(obj)])

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-04-11
        • 2023-03-09
        • 2017-10-11
        • 2016-07-10
        • 2020-06-14
        • 2016-12-18
        • 2018-07-29
        • 1970-01-01
        相关资源
        最近更新 更多