【问题标题】:Filter and map functions for generators生成器的过滤器和映射函数
【发布时间】:2021-09-15 04:01:59
【问题描述】:

在 JavaScript 中传递包含过滤 + 映射逻辑的生成器的推荐方法是什么?

不知何故,JavaScript 生成器缺少诸如 filtermap 操作数之类的基本内容,类似于数组,以便能够创建包含该逻辑的生成器,而无需先运行迭代。

我的正面方法是实现应用逻辑的自定义函数:

function * filter(g, cb) {
    let a;
    do {
        a = g.next();
        if (!a.done && cb(a.value)) {
            yield a.value;
        }
    } while (!a.done);
    return a.value;
}

function * map(g, cb) {
    let a;
    do {
        a = g.next();
        if (!a.done) {
            yield cb(a.value);
        }
    } while (!a.done);
    return a.value;
}

但这会造成回调地狱。我想简单地链接一个生成器,就像一个常规数组:

// create a filtered & re-mapped generator, without running it:
const gen = myGenerator().filter(a => a > 0).map(b => ({value: b})); 

// pass generator into a function that will run it:
processGenerator(gen);

有没有办法扩展生成器以自动访问这些基本功能?

另外,如果有人想强调为什么这些基本的东西不是生成器实现的一部分,那就太棒了!我认为过滤和映射是序列需要的两个最基本的东西。

【问题讨论】:

标签: javascript generator


【解决方案1】:

您的解决方案的替代方法是使用for...of 循环而不是do...while

我也更喜欢 filtermap 函数来使用和生成生成器函数,如下所示:

function filter(gen, predicate) {
  return function*() {
    for (let e of gen()) {
       if (predicate(e)) {
         yield e; 
       }
     }
  }
}

function map(gen, fn) {
  return function*() {
    for (let e of gen()) {
      yield fn(e);
    }
  }
}

function generatorWrapper(gen) {
  return {
    call: () => gen(),
    filter: predicate => generatorWrapper(filter(gen, predicate)),
    map: fn => generatorWrapper(map(gen, fn))
  };
}

function* generator() {
  yield 1;
  yield 2;
  yield 3;
}

const it = generatorWrapper(generator)
  .filter(x => x > 1)
  .map(x => x * 2)
  .call();

for (let e of it) {
  console.log(e);
}

【讨论】:

  • 它可能会改进自定义操作数的实现,但它不能解决链接问题,这就是问题所在。您的解决方案仍然依赖于嵌套回调,只是方式不同。
  • 因为filtermap 默认不提供,如果你想链接操作,你需要使用包装器。我更新了我的答案
  • 我们可以用多种不同的方式封装生成器,这些都是变通方法,而不是正确的解决方案。然后,您必须记住包装生成器的每个调用。
  • b.t.w. mapfilter 的实现还不够完整,它们还必须返回最后一个值,传递序列,因为这是默认生成器的工作方式。否则,您的自定义处理程序将丢失返回值。
  • 我更喜欢使用和生成生成器函数” - 我没有看到任何优势。但是如果你这样做,你应该将参数传递给生成器函数。
【解决方案2】:

通过管道装饰器获取原始可迭代和产生值的管道函数怎么样?

const pipe = function* (iterable, decorators) {
  // First build the pipeline by iterating over the decorators
  // and applying them in sequence.
  for(const decorator of decorators) {
    iterable = decorator(iterable)
  }

  // Then yield the values of the composed iterable.
  for(const value of iterable) {
    yield value;
  }
};

const filter = predicate =>
  function* (iterable) {
    for (const value of iterable) {
      if (predicate(value)) {
        yield value;
      }
    }
  };

const map = cb =>
  function* (iterable) {
    for (const value of iterable) {
      yield cb(value);
    }
  };

const mergeMap = cb =>
  function* (iterable) {
    for (const value of iterable) {
      for (const mapped of cb(value)) {
        yield mapped;
      }
    }
  };

const take = n =>
  function* (iterable) {
    for (const value of iterable) {
      if (!n--) {
        break;
      }
      yield value;
    }
  };

function* test(value1, value2) {
  yield value1;
  yield value2;
}

function* infinite() {
  for (;;) yield Math.random();
}

for (const value of pipe(test(111, 222), [
  filter(f => f > 0),
  map(m => ({ value: m }))
])) {
  console.log(value);
}

for (const value of pipe(infinite(), [
  take(5),
  mergeMap(v => [v, { timesTwo: v * 2 }])
])) {
  console.log(value);
}
.as-console-wrapper {
  max-height: 100% !important;
}

【讨论】:

    【解决方案3】:

    我可能已经找到了一个合适的解决方案......

    我创建了 Iterable 类,它在 this answer 上扩展:

    class Iterable {
        constructor(generator) {
            this[Symbol.iterator] = generator;
        }
    
        static extend(generator, cc) {
            // cc - optional calling context, when generator is a class method;
            return function () {
                return new Iterable(generator.bind(cc ?? this, ...arguments));
            }
        }
    }
    
    Iterable.prototype.filter = function (predicate) {
        let iterable = this;
        return new Iterable(function* () {
            for (let value of iterable)
                if (predicate(value)) {
                    yield value;
                }
        });
    };
    
    Iterable.prototype.map = function (cb) {
        let iterable = this;
        return new Iterable(function* () {
            for (let value of iterable) {
                yield cb(value);
            }
        });
    };
    

    现在我们可以采用现有的生成器函数,如下所示:

    function* test(value1, value2) {
        yield value1;
        yield value2;
    }
    

    并把它变成一个扩展的迭代器:

    const extTest = Iterable.extend(test);
    

    然后用它代替原来的生成器:

    const i = extTest(111, 222).filter(f => f > 0).map(m => ({value: m}));
    

    现在可以正常工作了:

    const values = [...i];
    //=> [ { value: 111 }, { value: 222 } ]
    

    更新

    经过更多研究,我确信RXJS / IXJS 是正确的选择。这些包装迭代器并提供开箱即用的扩展。

    【讨论】:

    • 我认为你应该更好地区分迭代器、生成器和生成器函数。 test 不是迭代器(甚至不可迭代)。
    • 现在很好区分了。当你发布它时,我仍在重构我的答案。
    • 自从我发表评论后,代码中没有任何变化。我正在挑剔在迭代器上调用 .bind() 方法 - 它们只有 .next()(以及可选的 .return/.throw
    • 它被重命名为generator,因为它就是这样。
    • 刚刚,是的。但是不,它不是生成器 - 它是生成器 function
    猜你喜欢
    • 2011-06-30
    • 1970-01-01
    • 1970-01-01
    • 2019-05-01
    • 2019-05-19
    • 2017-11-13
    • 2014-07-23
    • 1970-01-01
    • 2010-10-23
    相关资源
    最近更新 更多