Використання map () на ітераторі


89

Скажімо, у нас є Map:, let m = new Map();за допомогою m.values()повертає ітератор карти.

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

Тож чи є спосіб використовувати map()на ітераторі?


Не звичайно, але ви можете використовувати сторонні бібліотеки, такі як lodash mapфункція, яка також підтримує 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є 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 на ітераторі можна розглядати як перетворення на генераторі, яке повертає сам ітератор. popping один елемент викликає базовий ітератор, перетворює елемент і повертає його.
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
ні, це суть. Використовуючи це, ви можете створювати "потоки даних", прив'язуючи джерело ітератора до групи перетворень ітераторів і, нарешті, споживача. Наприклад, для потокової обробки звуку, роботи з величезними файлами, агрегаторів в базах даних тощо
hraban

1
Мені подобається така відповідь. Хтось може порекомендувати бібліотеку, яка пропонує подібні до масиву методи на ітерабелях?
Джоел Малоун

1
mapIterator()не гарантує, що базовий ітератор буде належним чином закритий ( iterator.return()викликаний), якщо повернене значення next не було викликано хоча б один раз. Див .: repeater.js.org/docs/safety
Яка Янчар,

Чому ви використовуєте ітератор вручну замість просто a for .. of .. loop?
ковтки

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, який реалізує схожі на масив методи для iterables:

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

Приємно! Ось як слід робити API JS. Як завжди, Руст все робить правильно: doc.rust-lang.org/std/iter/ Portrait.Iterator.html
літаюча вівця


0

Інші відповіді тут ... Дивно. Здається, вони повторно впроваджують частини протоколу ітерацій. Ви можете просто зробити це:

function* mapIter(iterable, callback) {
  for (let x of iterable) {
    yield callback(x);
  }
}

а якщо ви хочете отримати конкретний результат, просто використовуйте оператор поширення ....

[...iterMap([1, 2, 3], x => x**2)]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.