Utilizzando map () su un iteratore


92

Supponiamo di avere una mappa:, let m = new Map();utilizzando m.values()restituisce un iteratore di mappa.

Ma non posso usare forEach()o map()su quell'iteratore e l'implementazione di un ciclo while su quell'iteratore sembra un anti-pattern poiché ES6 offre funzioni come map().

Quindi c'è un modo per usarlo map()su un iteratore?


Non fuori dagli schemi, ma puoi usare librerie di terze parti come la lodash mapfunzione che supporta anche la mappa.
pericoloso

La mappa stessa ha un forEach per l'iterazione sulle sue coppie chiave-valore.
pericoloso

Convertire l'iteratore in un array e mapparlo come Array.from(m.values()).map(...)funziona, ma penso che non sia il modo migliore per farlo.
JiminP

quale problema come te risolvere usando un iteratore mentre un array si adatterebbe meglio per l'uso Array#map?
Nina Scholz

1
@NinaScholz Io sto usando un insieme generale come qui: stackoverflow.com/a/29783624/4279201
Shinzō

Risposte:


84

Il modo più semplice e meno performante per farlo è:

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

Meglio ancora

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

Array.fromprende qualsiasi cosa iterabile o simile ad un array e lo converte in un array! Come sottolinea Daniel nei commenti, possiamo aggiungere una funzione di mappatura alla conversione per rimuovere un'iterazione e successivamente un array intermedio.

L'uso Array.fromsposterà la tua performance da O(1)a O(n)come indica @hraban nei commenti. Poiché mè a Map, e non possono essere infiniti, non dobbiamo preoccuparci di una sequenza infinita. Per la maggior parte dei casi, questo sarà sufficiente.

Ci sono un paio di altri modi per scorrere una mappa.

Utilizzando forEach

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

Utilizzando 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

Le mappe possono avere una lunghezza infinita?
ktilcu

2
@ktilcu per un iteratore: sì. un .map su un iteratore può essere pensato come una trasformazione sul generatore, che restituisce un iteratore stesso. lo scoppio di un elemento chiama l'iteratore sottostante, trasforma l'elemento e lo restituisce.
Hraban

8
Il problema con questa risposta è che trasforma quello che potrebbe essere un algoritmo di memoria O (1) in uno O (n), il che è abbastanza serio per set di dati più grandi. A parte, ovviamente, la necessità di iteratori finiti e non trasmettibili. Il titolo della domanda è "Utilizzando map () su un iteratore", non sono d'accordo sul fatto che le sequenze pigre e infinite non fanno parte della domanda. È esattamente così che le persone usano gli iteratori. La "mappa" era solo un esempio ("Say .."). La cosa buona di questa risposta è la sua semplicità, che è molto importante.
hraban

1
@ Hraban Grazie per aver aggiunto a questa discussione. Posso aggiornare la risposta per includere alcuni avvertimenti solo in modo che i futuri viaggiatori abbiano le informazioni frontali e centrali. Quando si arriva al punto dovremo spesso decidere tra prestazioni semplici e ottimali. Di solito mi schiererò dalla parte più semplice (per eseguire il debug, mantenere, spiegare) rispetto alle prestazioni.
ktilcu

3
@ktilcu Puoi invece chiamare Array.from(m, ([key,value]) => /* whatever */)(notare che la funzione di mappatura è all'interno di from) e quindi non viene creato alcun array intermedio ( sorgente ). Si sposta ancora da O (1) a O (n), ma almeno l'iterazione e la mappatura avvengono in una sola iterazione completa.
Daniel

19

Potresti definire un'altra funzione iteratore per eseguire il ciclo su questo:

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

function* mapIterator(iterator, mapping) {
    for (let i of iterator) {
        yield mapping(i);
    }
}

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

Ora potresti chiederti: perché non usarlo semplicemente Array.from? Poiché questo eseguirà l'intero iteratore, lo salverà in un array (temporaneo), lo itererà di nuovo e quindi eseguirà la mappatura. Se l'elenco è enorme (o addirittura potenzialmente infinito), ciò comporterà un utilizzo della memoria non necessario.

Ovviamente, se l'elenco degli elementi è abbastanza piccolo, l'utilizzo Array.fromdovrebbe essere più che sufficiente.


Come può una quantità finita di memoria contenere una struttura dati infinita?
shinzou

3
non è così, questo è il punto. Usando questo è possibile creare "flussi di dati" concatenando una sorgente iteratore a un gruppo di trasformazioni iteratore e infine a un sink consumer. Ad esempio, per l'elaborazione audio in streaming, lavorare con file enormi, aggregatori su database, ecc.
hraban

1
Mi piace questa risposta. Qualcuno può consigliare una libreria che offre metodi simili a Array su iterabili?
Joel Malone

1
mapIterator()non garantisce che l'iteratore sottostante venga correttamente chiuso ( iterator.return()chiamato) a meno che il valore di ritorno successivo non sia stato chiamato almeno una volta. Vedi: repeater.js.org/docs/safety
Jaka Jančar

Perché stai usando manualmente il protocollo iteratore invece di un semplice for .. of .. loop?
Cowlicks

11

Questo modo più semplice ed efficiente è utilizzare il secondo argomento Array.fromper ottenere ciò:

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

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

Questo approccio funziona per qualsiasi iterabile non infinito . Ed evita di dover utilizzare una chiamata separata per Array.from(map).map(...)cui iterare due volte attraverso l'iterabile e peggiorare le prestazioni.


3

È possibile recuperare un iteratore sull'iterabile, quindi restituire un altro iteratore che chiama la funzione di callback di mappatura su ogni elemento iterato.

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

Potresti usare itiriri che implementa metodi di tipo array per iterabili:

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

Bello! Ecco come avrebbero dovuto essere fatte le API di JS. Come sempre, Rust ha ragione: doc.rust-lang.org/std/iter/trait.Iterator.html
pecora volante

"Come sempre, Rust ha ragione" certo ... C'è una proposta di standardizzazione per tutti i tipi di funzioni di supporto per l'interfaccia iteratore github.com/tc39/proposal-iterator-helpers Puoi usarlo oggi con corejs importando il file fromfn da "core-js-pure / features / iterator" che restituisce il "nuovo" iteratore.
chpio


1

C'è una proposta, che sta portando più funzioni di supporto a Iterator: https://github.com/tc39/proposal-iterator-helpers ( renderizzato )

Puoi usarlo oggi utilizzando core-js:

import { from as iterFrom } from "core-js-pure/features/iterator";

// or if it's working for you:
// import iterFrom from "core-js-pure/features/iterator/from";

let m = new Map();

m.set("13", 37);
m.set("42", 42);

const arr = iterFrom(m.values())
  .map((val) => val * 2)
  .toArray();

// prints "[74, 84]"
console.log(arr);

0

Altre risposte qui sono ... Strane. Sembra che stiano reimplementando parti del protocollo di iterazione. Puoi semplicemente fare questo:

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

e se vuoi un risultato concreto usa l'operatore spread ....

[...iterMap([1, 2, 3], x => x**2)]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.