Używanie map () na iteratorze

88

Powiedzmy, że mamy Map : let m = new Map();, użycie m.values()zwraca iterator mapy.

Ale nie mogę używać forEach()lub map()na tym iteratorze, a implementacja pętli while na tym iteratorze wydaje się być anty-wzorcem, ponieważ ES6 oferuje funkcje takie jak map().

Czy jest więc sposób użycia map()na iteratorze?

shinzou
źródło
Nie po wyjęciu z pudełka, ale możesz korzystać z bibliotek innych firm, takich jak lodash mapfunkcja, która obsługuje również Mapę.
niebezpieczne
Sama mapa ma forEach do iteracji po swoich parach klucz-wartość.
niebezpieczne
Konwersja iteratora na tablicę i mapowanie na niej Array.from(m.values()).map(...)działa jak działa, ale myślę, że nie jest to najlepszy sposób na zrobienie tego.
JiminP
który problem, tak jak ty, rozwiązujesz za pomocą iteratora, podczas gdy tablica pasowałaby lepiej do użycia Array#map?
Nina Scholz
1
@NinaScholz Używam ogólnego zestawu takiego jak tutaj: stackoverflow.com/a/29783624/4279201
shinzou

Odpowiedzi:

81

Najprostszym i najmniej wydajnych sposobem, aby to zrobić, to:

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

Jeszcze lepiej

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

Array.frompobiera dowolną iterowalną lub podobną do tablicy rzecz i konwertuje ją na tablicę! Jak Daniel wskazuje w komentarzach, możemy dodać funkcję odwzorowującą do konwersji, aby usunąć iterację, a następnie tablicę pośrednią.

Użycie Array.fromspowoduje przeniesienie wydajności z O(1)do, O(n)jak wskazuje @hraban w komentarzach. Ponieważ mjest a Mapi nie mogą być nieskończone, nie musimy się martwić o nieskończoną sekwencję. W większości przypadków to wystarczy.

Istnieje kilka innych sposobów przeglądania mapy.

Za pomocą forEach

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

Za pomocą 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
źródło
Czy mapy mogą mieć nieskończoną długość?
ktilcu
2
@ktilcu dla iteratora: tak. .mapę na iteratorze można traktować jako transformację generatora, która zwraca sam iterator. popping jednego elementu wywołuje bazowy iterator, przekształca element i zwraca go.
hraban
7
Problem z tą odpowiedzią polega na tym, że zamienia to, co mogłoby być algorytmem pamięci O (1), w algorytm O (n), co jest dość poważne w przypadku większych zbiorów danych. Oprócz, oczywiście, wymagania skończonych, nieprzekraczalnych iteratorów. Tytuł pytania brzmi „Używając map () na iteratorze”, nie zgadzam się, że leniwe i nieskończone sekwencje nie są częścią pytania. Dokładnie tak ludzie używają iteratorów. „Mapa” była tylko przykładem („Powiedz…”). Zaletą tej odpowiedzi jest jej prostota, która jest bardzo ważna.
hraban
1
@hraban Dzięki za dodanie do tej dyskusji. Mogę zaktualizować odpowiedź, dodając kilka zastrzeżeń, aby przyszli podróżnicy mieli informacje z przodu i na środku. Kiedy do tego dojdzie, często będziemy musieli podjąć decyzję między prostą a optymalną wydajnością. Zwykle będę opowiadał się za prostszym (debugowaniem, konserwacją, wyjaśnianiem) w porównaniu z wydajnością.
ktilcu
3
@ktilcu Możesz zamiast tego wywołać Array.from(m, ([key,value]) => /* whatever */)(zauważ, że funkcja mapowania jest wewnątrz from), a następnie nie zostanie utworzona żadna pośrednia tablica ( źródło ). Nadal porusza się od O (1) do O (n), ale przynajmniej iteracja i mapowanie odbywa się tylko w jednej pełnej iteracji.
Daniel
18

Możesz zdefiniować inną funkcję iteratora, aby zapętlić to:

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

Teraz możesz zapytać: dlaczego po prostu nie użyć Array.fromzamiast tego? Ponieważ będzie to przebiegać przez cały iterator, zapisz go w (tymczasowej) tablicy, wykonaj iterację ponownie, a następnie wykonaj mapowanie. Jeśli lista jest ogromna (lub nawet potencjalnie nieskończona), doprowadzi to do niepotrzebnego zużycia pamięci.

Oczywiście, jeśli lista pozycji jest dość mała, użycie Array.frompowinno być więcej niż wystarczające.

Sheean
źródło
W jaki sposób ograniczona ilość pamięci może pomieścić nieskończoną strukturę danych?
shinzou
3
tak nie jest, o to chodzi. Korzystając z tego, można tworzyć „strumienie danych”, łącząc źródło iteratora z grupą transformacji iteratora i wreszcie ujściem konsumenta. Np. Do strumieniowego przetwarzania dźwięku, pracy z dużymi plikami, agregatorów w bazach danych itp.
hraban
1
Podoba mi się ta odpowiedź. Czy ktoś może polecić bibliotekę, która oferuje metody podobne do tablic w iterable?
Joel Malone
1
mapIterator()nie gwarantuje, że bazowy iterator zostanie poprawnie zamknięty ( iterator.return()wywołany), chyba że następna wartość zwracana została wywołana przynajmniej raz. Zobacz: repeater.js.org/docs/safety
Jaka Jančar
11

Ten najprostszy i najbardziej wydajny sposób polega na użyciu drugiego argumentu, aby Array.fromto osiągnąć:

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

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

To podejście działa dla każdej nieskończonej iteracji. I unika konieczności używania oddzielnego wywołania, Array.from(map).map(...)które mogłoby dwukrotnie iterować przez iterowalne i pogorszyć wydajność.

Ian Storm Taylor
źródło
3

Możesz pobrać iterator po iterowalnym, a następnie zwrócić inny iterator, który wywołuje funkcję wywołania zwrotnego odwzorowania na każdym iterowanym elemencie.

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
MartyO256
źródło
2

Możesz użyć itiriri, który implementuje metody podobne do tablic dla iterable:

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
źródło
Ładny! Tak powinno być zrobione API JS. Jak zawsze, Rust robi to dobrze: doc.rust-lang.org/std/iter/trait.Iterator.html
latające owce
1

Spójrz na https://www.npmjs.com/package/fluent-iterable

Działa ze wszystkimi iteracjami (mapa, funkcja generatora, tablica) i asynchronicznymi.

const map = new Map();
...
console.log(fluent(map).filter(..).map(..));
kataik
źródło