web-dev-qa-db-fra.com

Utiliser map () sur un itérateur

Disons que nous avons une carte: let m = new Map();, en utilisant m.values() renvoie un itérateur de carte.

Mais je ne peux pas utiliser forEach() ou map() sur cet itérateur et implémenter une boucle while sur cet itérateur ressemble à un anti-motif puisque les fonctions offertes par ES6 sont telles que map().

Alors, y a-t-il moyen d'utiliser map() sur un itérateur?

60
shinzou

La façon la plus simple et la moins performante est la suivante:

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

Mieux encore

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

Array.from prend n'importe quelle chose itérable ou semblable à un tableau et le convertit en tableau! Comme Daniel le souligne dans les commentaires, nous pouvons ajouter une fonction de mappage à la conversion pour supprimer une itération et, par la suite, un tableau intermédiaire.

Utiliser _Array.from_ fera passer votre performance de O(1) à O(n), comme le fait remarquer @hraban dans les commentaires. Puisque m est un Map, et qu'ils ne peuvent pas être infinis, nous n'avons pas à nous soucier d'une séquence infinie. Dans la plupart des cas, cela suffira.

Il existe deux autres moyens de parcourir en boucle une carte.

Utiliser forEach

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

Utilisation de _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
_
52
ktilcu

Vous pouvez définir une autre fonction d'itérateur à boucler sur ceci:

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

Maintenant, vous pourriez demander: pourquoi ne pas simplement utiliser Array.from à la place? Parce que cela va parcourir l'itérateur en entier, enregistrez-le dans un tableau (temporaire), itérez-le à nouveau et then faites le mappage. Si la liste est longue (voire potentiellement infinie), cela entraînera une utilisation inutile de la mémoire.

Bien sûr, si la liste d'éléments est assez petite, utiliser Array.from devrait être plus que suffisant.

15
sheean

Cette méthode la plus simple et la plus performante consiste à utiliser le deuxième argument de Array.from pour obtenir ceci:

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

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

Cette approche fonctionne pour tout non-infini itérable. Et cela évite d’avoir à utiliser un appel séparé à Array.from(map).map(...) qui parcourrait deux fois l’itéré et serait pire pour la performance.

3

Vous pouvez récupérer un itérateur sur l'itérable, puis renvoyer un autre itérateur qui appelle la fonction de rappel de mappage sur chaque élément itéré.

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
MartyO256

Vous pouvez utiliser itiriri qui implémente des méthodes de type tableau pour les 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();
0
dimadeveatii