Отображение функции на генератор в JavaScript

У меня есть генератор под названием generateNumbers в JavaScript и другой генератор generateLargerNumbers который принимает каждое значение, сгенерированное generateNumbers и применяет функцию addOne к нему как таковой:

function addOne(value) {
  return value + 1
}

function* generateNumbers() {
  yield 1
  yield 2
  yield 3
}

function* generateLargerNumbers() {
  for (const number of generateNumbers()) {
    yield addOne(number)
  }
}

Есть ли более краткий способ сделать это без создания массива из сгенерированных значений? Я думаю что-то вроде:

function* generateLargerNumbers() {
  yield* generateNumbers().map(addOne) // obviously doesn't work
}

4 ответа

Решение

Там нет встроенного способа для отображения на Generator объекты, но вы можете свернуть свою собственную функцию:

const Generator = Object.getPrototypeOf(function* () {});
Generator.prototype.map = function* (mapper, thisArg) {
  for (const val of this) {
    yield mapper.call(thisArg, val);
  }
};

Теперь вы можете сделать:

function generateLargerNumbers() {
  return generateNumbers().map(addOne);
}

const Generator = Object.getPrototypeOf(function* () {});
Generator.prototype.map = function* (mapper, thisArg) {
  for (const val of this) {
    yield mapper.call(thisArg, val);
  }
};

function addOne(value) {
  return value + 1
}

function* generateNumbers() {
  yield 1
  yield 2
  yield 3
}

function generateLargerNumbers() {
  return generateNumbers().map(addOne)
}

console.log(...generateLargerNumbers())

генераторы высшего порядка

Вы можете сами управлять функциями генератора

const Generator =
  {
    map: (f,g) => function* (...args)
      {
        for (const x of g (...args))
          yield f (x)
      },
    filter: (f,g) => function* (...args)
      {
        for (const x of g (...args))
          if (f (x))
            yield x
      }
  }

// some functions !
const square = x =>
  x * x

const isEven = x =>
  (x & 1) === 0
  
// a generator !
const range = function* (x = 0, y = 1)
  {
    while (x < y)
      yield x++
  }

// higher order generator !
for (const x of Generator.map (square, Generator.filter (isEven, range)) (0,10))
  console.log('evens squared', x)

итераторы высшего порядка

Или вы можете управлять итераторами

const Iterator =
  {
    map: (f, it) => function* ()
      {
        for (const x of it)
          yield f (x)
      } (),
    filter: (f, it) => function* ()
      {
        for (const x of it)
          if (f (x))
            yield x
      } ()
  }

// some functions !
const square = x =>
  x * x
  
const isEven = x =>
  (x & 1) === 0

// a generator !
const range = function* (x = 0, y = 1)
  {
    while (x < y)
      yield x++
  }
  
// higher-order iterators !
for (const x of Iterator.map (square, Iterator.filter (isEven, range (0, 10))))
  console.log('evens squared', x)

рекомендация

В большинстве случаев я считаю более практичным манипулировать итератором из-за его четко определенного (хотя и не очень) интерфейса. Это позволяет вам сделать что-то вроде

Iterator.map (square, Iterator.filter (isEven, [10,11,12,13]))

Тогда как другой подход

Generator.map (square, Generator.filter (isEven, Array.from)) ([10,11,12,13])

У обоих есть сценарий использования, но я считаю, что первое намного приятнее, чем второе


постоянные итераторы

Состоящие итераторы JavaScript раздражают меня - каждый последующий вызов .next изменяет внутреннее состояние необратимо.

Но! ничто не мешает вам создавать свои собственные итераторы, а затем создавать адаптер для подключения к механизму безопасного создания стека в JavaScript

Если вас это интересует, вам могут понравиться другие сопровождающие примеры, найденные здесь: Цикл к структуре файловой системы в моем объекте, чтобы получить все файлы

Единственное преимущество не в том, что мы можем повторно использовать постоянный итератор, а в том, что в этой реализации последующее чтение даже быстрее, чем первое из-за запоминания - оценка: JavaScript 0, постоянные итераторы 2

// -------------------------------------------------------------------
const Memo = (f, memo) => () =>
  memo === undefined
    ? (memo = f (), memo)
    : memo

// -------------------------------------------------------------------
const Yield = (value, next = Return) =>
  ({ done: false, value, next: Memo (next) })
  
const Return = value =>
  ({ done: true, value })
  
// -------------------------------------------------------------------
const MappedIterator = (f, it = Return ()) =>
  it.done
    ? Return ()
    : Yield (f (it.value), () => MappedIterator (f, it.next ()))
    
const FilteredIterator = (f, it = Return ()) =>
  it.done
    ? Return ()
    : f (it.value)
      ? Yield (it.value, () => FilteredIterator (f, it.next ()))
      : FilteredIterator (f, it.next ())

// -------------------------------------------------------------------
const Generator = function* (it = Return ())
  {
    while (it.done === false)
      (yield it.value, it = it.next ())
    return it.value
  }

// -------------------------------------------------------------------  
const Range = (x = 0, y = 1) =>
  x < y
    ? Yield (x, () => Range (x + 1, y))
    : Return ()

const square = x =>
  x * x

const isEven = x =>
  (x & 1) === 0

// -------------------------------------------------------------------
for (const x of Generator (MappedIterator (square, FilteredIterator (isEven, Range (0,10)))))
  console.log ('evens squared', x)

Как насчет составления объекта итератора вместо использования вложенных генераторов?

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

function generateGreaterNumbers(){
 return { next(){ var r = this.gen.next(); r.value+=1; return r; }, gen: generateNumbers() };`
}

Если вам действительно нужно передать значения в ваш генератор, то вы не можете сделать это с помощью for...of, вы должны передать каждое значение через

  const mapGenerator = (generatorFunc, mapper) =>
    function*(...args) {
      let gen = generatorFunc(...args),
        i = 0,
        value;
      while (true) {
        const it = gen.next(value);
        if (it.done) return mapper(it.value, i);
        value = yield mapper(it.value, i);
        i++;
      }
    };

function* generator() {
  console.log('generator received', yield 1);
  console.log('generator received', yield 2);
  console.log('generator received', yield 3);
  return 4;
}

const mapGenerator = (generatorFunc, mapper) =>
  function*(...args) {
    let gen = generatorFunc(...args),
      i = 0,
      value;
    while (true) {
      const it = gen.next(value);
      if (it.done) return mapper(it.value, i);
      value = yield mapper(it.value, i);
      i++;
    }
  };

const otherGenerator = mapGenerator(generator, x => x + 1)

const it = otherGenerator();
console.log(
  it.next().value,
  it.next('a').value, 
  it.next('b').value,
  it.next('c').value
);

Другие вопросы по тегам