生成器的过滤和映射功能

问题描述 投票:0回答:4

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

不知何故,JavaScript 生成器缺少诸如

filter
map
操作数之类的基本内容(类似于数组),以便能够创建包含该逻辑的生成器,而无需先运行迭代。

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

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

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

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

更新

这一切以我编写自己的 iter-ops 库而结束:)

javascript generator
4个回答
5
投票

解决方案的替代方案是使用

for...of
循环而不是
do...while

我还更喜欢

filter
map
函数来使用和生成如下生成器函数:

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


1
投票

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

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


1
投票

我可能已经找到了正确的解决方案......

我创建了类

Iterable
,它扩展了这个答案

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 } ]

更新

最终,我编写了自己的 iter-ops 库。


0
投票

自从 ECMAScript 2025 引入了迭代器辅助方法以来,您希望的代码已经可以工作了,现在实际上可以开箱即用了:

// Your code:
const gen = myGenerator().filter(a => a > 0).map(b => ({value: b})); 
processGenerator(gen);

// Some functions that above code is calling:
function* myGenerator() { // A generator for the demo
    for (let i = 1; i < 20; i = -i*2) yield i;
}

function processGenerator(gen) { // A consumer of the generator
    for (const obj of gen) console.log(obj);
}

filter
map
方法是迭代器辅助对象的方法,即继承自
Iterator
的迭代器。请注意,生成器返回这样的迭代器,就像所有返回迭代器的原生 JS 方法一样,例如
Array.prototype.values
Object.entries
、...

© www.soinside.com 2019 - 2024. All rights reserved.