Использование 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

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должно быть более чем достаточно.


Как конечный объем памяти может содержать бесконечную структуру данных?
— 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

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

Ницца! Вот как должны были быть реализованы JS API. Как всегда, Rust понимает
— летающие овцы

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.