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?


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

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.


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ść.


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


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.