Использование map () в итераторе

88

Скажем, у нас есть Map : let m = new Map();, использование m.values()возвращает итератор карты.

Но я не могу использовать forEach()или map()на этом итераторе, и реализация цикла while на этом итераторе кажется анти-шаблоном, поскольку ES6 предлагает такие функции, как map().

Так есть ли способ использовать map()итератор?

Синдзо
источник
Не из коробки, но вы можете использовать сторонние библиотеки, такие как lodash mapфункция, которая также поддерживает карту.
опасный
Сама карта имеет forEach для итерации по парам ключ-значение.
опасный
Преобразование итератора в массив и отображение на нем вроде Array.from(m.values()).map(...)работает, но я думаю, что это не лучший способ сделать это.
JiminP
какую проблему, как вы, решить с помощью итератора, в то время как массив лучше подходит для использования Array#map?
Нина Шольц
1
@NinaScholz Я использую общий набор, как здесь: stackoverflow.com/a/29783624/4279201
shinzou

Ответы:

81

Самый простой и наименее эффективный способ сделать это:

Array.from(m).map(([key,value]) => /* whatever */)

Еще лучше

Array.from(m, ([key, value]) => /* whatever */))

Array.fromберет любую итеративную или похожую на массив вещь и преобразует ее в массив! Как указывает Даниэль в комментариях, мы можем добавить функцию сопоставления к преобразованию, чтобы удалить итерацию, а затем и промежуточный массив.

Использование Array.fromпереместит вашу производительность с O(1)на, O(n)как отмечает @hraban в комментариях. Поскольку mэто a Map, и они не могут быть бесконечными, нам не нужно беспокоиться о бесконечной последовательности. В большинстве случаев этого будет достаточно.

Есть еще пара способов пролистать карту.

С помощью forEach

m.forEach((value,key) => /* stuff */ )

С помощью for..of

var myMap = new Map();
myMap.set(0, 'zero');
myMap.set(1, 'one');
for (var [key, value] of myMap) {
  console.log(key + ' = ' + value);
}
// 0 = zero
// 1 = one
Ktilcu
источник
Могут ли карты иметь бесконечную длину?
ktilcu
2
@ktilcu для итератора: да. .map на итераторе можно рассматривать как преобразование генератора, которое возвращает сам итератор. выталкивание одного элемента вызывает базовый итератор, преобразует элемент и возвращает его.
hraban
7
Проблема с этим ответом заключается в том, что он превращает алгоритм памяти O (1) в алгоритм O (n), что довольно серьезно для больших наборов данных. За исключением, конечно, необходимости конечных непотоковых итераторов. Название вопроса - «Использование map () на итераторе», я не согласен с тем, что ленивые и бесконечные последовательности не являются частью вопроса. Именно так люди используют итераторы. «Карта» была только примером («Скажи ...»). В этом ответе хорошо то, что он очень важен.
hraban
1
@hraban Спасибо, что добавили в это обсуждение. Я могу обновить ответ, включив в него несколько предостережений, чтобы будущие путешественники были в центре внимания. Когда дело доходит до этого, нам часто приходится выбирать между простой и оптимальной производительностью. Обычно я предпочитаю более простую (отладку, поддержку, объяснение) над производительностью.
ktilcu
3
@ktilcu Вместо этого вы можете вызвать Array.from(m, ([key,value]) => /* whatever */)(обратите внимание, что функция сопоставления находится внутри from), и тогда промежуточный массив не будет создан ( источник ). Он по-прежнему перемещается от O (1) к O (n), но по крайней мере итерация и отображение выполняются всего за одну полную итерацию.
Дэниел
18

Вы можете определить другую функцию итератора, чтобы перебрать это:

function* generator() {
    for(let i = 0; i < 10; i++) {
        console.log(i);
        yield i;
    }
}

function* mapIterator(iterator, mapping) {
    while (true) {
        let result = iterator.next();
        if (result.done) {
            break;
        }
        yield mapping(result.value);
    }
}

let values = generator();
let mapped = mapIterator(values, (i) => {
    let result = i*2;
    console.log(`x2 = ${result}`);
    return result;
});

console.log('The values will be generated right now.');
console.log(Array.from(mapped).join(','));

Теперь вы можете спросить: почему бы просто не использовать Array.fromвместо этого? Поскольку это будет проходить через весь итератор, сохраните его во (временном) массиве, повторите его снова и затем выполните сопоставление. Если список огромен (или даже потенциально бесконечен), это приведет к ненужному использованию памяти.

Конечно, если список предметов невелик, использования Array.fromдолжно быть более чем достаточно.

Sheean
источник
Как конечный объем памяти может содержать бесконечную структуру данных?
shinzou
3
это не так, в том-то и дело. Используя это, вы можете создавать «потоки данных», связывая источник итератора с набором преобразований итератора и, наконец, потребителем. Например, для обработки потокового аудио, работы с огромными файлами, агрегаторов в базах данных и т. Д.
храбан
1
Мне нравится этот ответ. Может ли кто-нибудь порекомендовать библиотеку, которая предлагает методы типа Array для итераций?
Джоэл Мэлоун
1
mapIterator()не гарантирует, что базовый итератор будет правильно закрыт ( iterator.return()вызван), если только возвращаемое значение next не было вызвано хотя бы один раз. См .: Repeater.js.org/docs/safety
Яка Янчар,
11

Самый простой и эффективный способ - использовать второй аргумент Array.fromдля достижения этой цели:

const map = new Map()
map.set('a', 1)
map.set('b', 2)

Array.from(map, ([key, value]) => `${key}:${value}`)
// ['a:1', 'b:2']

Этот подход работает для любой , не бесконечно итерации. И это позволяет избежать использования отдельного вызова, Array.from(map).map(...)который будет повторять итерацию дважды и ухудшить производительность.

Ян Сторм Тейлор
источник
3

Вы можете получить итератор для итератора, а затем вернуть другой итератор, который вызывает функцию обратного вызова сопоставления для каждого повторяемого элемента.

const map = (iterable, callback) => {
  return {
    [Symbol.iterator]() {
      const iterator = iterable[Symbol.iterator]();
      return {
        next() {
          const r = iterator.next();
          if (r.done)
            return r;
          else {
            return {
              value: callback(r.value),
              done: false,
            };
          }
        }
      }
    }
  }
};

// Arrays are iterable
console.log(...map([0, 1, 2, 3, 4], (num) => 2 * num)); // 0 2 4 6 8
МартиO256
источник
2

Вы можете использовать itiriri, который реализует методы, подобные массивам, для итераций:

import { query } from 'itiriri';

let m = new Map();
// set map ...

query(m).filter([k, v] => k < 10).forEach([k, v] => console.log(v));
let arr = query(m.values()).map(v => v * 10).toArray();
dimadeveatii
источник
Ницца! Вот как должны были быть реализованы JS API. Как всегда, Rust понимает
летающие овцы
1

Взгляните на https://www.npmjs.com/package/fluent-iterable

Работает со всеми итерациями (карта, функция генератора, массив) и асинхронными итерациями.

const map = new Map();
...
console.log(fluent(map).filter(..).map(..));
катайк
источник