.map () une carte Javascript ES6?

89

Comment ferais-tu ceci? Instinctivement, je veux faire:

var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);

// wishful, ignorant thinking
var newMap = myMap.map((key, value) => value + 1); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }

Je n'ai pas beaucoup glané de la documentation sur le nouveau protocole d'itération .

Je connais wu.js , mais je dirige un projet Babel et je ne veux pas inclure Traceur , dont il semble dépendre actuellement .

Je ne sais pas non plus comment extraire comment fitzgen / wu.js l'a fait dans mon propre projet.

J'adorerais une explication claire et concise de ce qui me manque ici. Merci!


Docs pour ES6 Map , FYI

neezer
la source
Pouvez-vous utiliser Array.from?
Ry-
@minitech Peut-être, avec un polyfill ... n'y a-t-il pas un moyen de faire cela sans lui?
neezer
Eh bien, vous pouvez écrire votre propre mapfonction à utiliser sur les itérables, en utilisant for of.
Ry-
Super pinailler, mais si vous vouliez vraiment cartographier une carte, vous récupéreriez une carte à la fin. Sinon, vous commencez par convertir une carte en un tableau et un mappage sur un tableau, pas .map () une carte. Vous pouvez facilement mapper sur une carte en utilisant un ISO: dimap (x => [... x], x => new Map (x));
Dtipson le
@Ry eh bien, nous pouvons probablement écrire notre propre langage de programmation, mais pourquoi ..? C'est une chose assez simple et existe dans la plupart des langages de programmation depuis de nombreuses décennies.
ruX

Réponses:

73

Donc, .mapelle-même n'offre qu'une valeur qui vous tient à cœur ... Cela dit, il existe plusieurs façons de résoudre ce problème:

// instantiation
const myMap = new Map([
  [ "A", 1 ],
  [ "B", 2 ]
]);

// what's built into Map for you
myMap.forEach( (val, key) => console.log(key, val) ); // "A 1", "B 2"

// what Array can do for you
Array.from( myMap ).map(([key, value]) => ({ key, value })); // [{key:"A", value: 1}, ... ]

// less awesome iteration
let entries = myMap.entries( );
for (let entry of entries) {
  console.log(entry);
}

Notez que j'utilise beaucoup de nouvelles choses dans ce deuxième exemple ... ... Array.fromprend tout itérable (à tout moment que vous utiliseriez [].slice.call( ), plus Ensembles et Cartes) et le transforme en un tableau ... ... Cartes , lorsqu'il est contraint dans un tableau, se transforme en un tableau de tableaux, où el[0] === key && el[1] === value;(essentiellement, dans le même format que j'ai prérempli mon exemple de carte avec, ci-dessus).

J'utilise la déstructuration du tableau dans la position d'argument du lambda, pour attribuer ces points de tableau à des valeurs, avant de renvoyer un objet pour chaque el.

Si vous utilisez Babel, en production, vous allez devoir utiliser le polyfill du navigateur de Babel (qui inclut "core-js" et le "régénérateur" de Facebook).
Je suis certain qu'il contient Array.from.

Norguard
la source
Ouais, juste en remarquant que j'en aurai besoin Array.frompour faire ça. Votre premier exemple ne renvoie pas de tableau, btw.
neezer
@neezer non, ce n'est pas le cas. .forEachretourne à undefinedplat, tout le temps, car l'utilisation prévue de forEachest une simple itération basée sur les effets secondaires (comme elle l'a été depuis ES5). Pour utiliser cela forEach, vous voudrez créer un tableau et le remplir à la main, soit via ledit forEachsoit via l'itérateur retourné par .entries()ou .keys( )ou .values( ). Array.fromne fait rien d'extraordinaire unique, c'est juste cacher cette laideur particulière de faire ladite traversée. Et oui, vérifiez qu'en incluant babel-core/browser.js(ou quoi que ce soit) vous obtenez .from...
Norguard
4
Voir ma réponse, Array.fromprend une fonction de carte comme paramètre, vous n'avez pas besoin de créer un tableau temporaire juste pour mapper dessus et le supprimer.
loganfsmyth
Pouvez-vous expliquer pourquoi l'objet a besoin d'une parenthèse autour de lui? Je suis encore un peu nouveau dans ES6 et j'ai du mal à comprendre cette partie. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… dit qu'il est interprété comme une étiquette et je ne suis pas sûr de ce que cela signifie
dtc
1
@dtc ouais, quand tu écris un objet que tu écris const obj = { x: 1 };, quand tu écris un bloc de code, tu écris if (...) { /* block */ }, quand tu écris une fonction fléchée que tu écris () => { return 1; }, alors comment sait-il quand c'est un corps de fonction, par rapport aux accolades d'un objet? Et la réponse est entre parenthèses. Sinon, il s'attend à ce que le nom soit une étiquette pour un bloc de code, une fonctionnalité rarement utilisée, mais vous pouvez étiqueter une switchou une boucle, de sorte que vous puissiez en break;sortir par nom. Donc, s'il manque, vous avez un corps de fonction, avec une étiquette, et l'instruction 1, basée sur l'exemple MDN
Norguard
34

Vous devez simplement utiliser l' opérateur Spread :

var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);

var newArr = [...myMap].map(value => value[1] + 1);
console.log(newArr); //[2, 3, 4]

var newArr2 = [for(value of myMap) value = value[1] + 1];
console.log(newArr2); //[2, 3, 4]

Walter Chapilliquen - wZVanG
la source
On dirait que cela nécessite toujours Array.from, FYI.
neezer
1
@neezer vous absolument, positivement devez utiliser la polyfill de Babel, afin d'utiliser le code Babel-transpiled sur votre page, dans la production. Il n'y a pas moyen de contourner cela, à moins que vous n'implémentiez toutes ces méthodes (y compris le régénérateur de Facebook) par vous-même, à la main ...
Norguard
@Norguard J'ai compris - juste un changement de configuration que je devrais faire est tout ce que je voulais dire. Plus d'un aparté, vraiment. :)
neezer
5
Juste pour noter, [...myMap].map(mapFn)va créer un tableau temporaire, puis le supprimer. Array.fromprend a mapFncomme deuxième argument, utilisez-le simplement.
loganfsmyth
2
@wZVanG Pourquoi pas Array.from(myMap.values(), val => val + 1);?
loganfsmyth
21

Utilisez juste Array.from(iterable, [mapFn]).

var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);

var newArr = Array.from(myMap.values(), value => value + 1);
loganfsmyth
la source
Merci pour cette réponse, j'aurais aimé qu'elle soit plus haut. J'utilise le modèle de propagation d'une carte dans un tableau temporaire tout le temps, sans le savoir, a Array.from()pris une fonction de cartographie comme deuxième paramètre facultatif.
Andru
4

Vous pouvez utiliser cette fonction:

function mapMap(map, fn) {
  return new Map(Array.from(map, ([key, value]) => [key, fn(value, key, map)]));
}

usage:

var map1 = new Map([["A", 2], ["B", 3], ["C", 4]]);

var map2 = mapMap(map1, v => v * v);

console.log(map1, map2);
/*
Map { A → 2, B → 3, C → 4 }
Map { A → 4, B → 9, C → 16 }
*/
Yukulélé
la source
3

En utilisant, Array.fromj'ai écrit une fonction Typescript qui mappe les valeurs:

function mapKeys<T, V, U>(m: Map<T, V>, fn: (this: void, v: V) => U): Map<T, U> {
  function transformPair([k, v]: [T, V]): [T, U] {
    return [k, fn(v)]
  }
  return new Map(Array.from(m.entries(), transformPair));
}

const m = new Map([[1, 2], [3, 4]]);
console.log(mapKeys(m, i => i + 1));
// Map { 1 => 3, 3 => 5 }
saul.shanabrook
la source
3

En fait, vous pouvez toujours avoir un Mapavec les clés d'origine après la conversion en tableau avec Array.from. C'est possible en renvoyant un tableau , où le premier élément est le key, et le second est le transformé value.

const originalMap = new Map([
  ["thing1", 1], ["thing2", 2], ["thing3", 3]
]);

const arrayMap = Array.from(originalMap, ([key, value]) => {
    return [key, value + 1]; // return an array
});

const alteredMap = new Map(arrayMap);

console.log(originalMap); // Map { 'thing1' => 1, 'thing2' => 2, 'thing3' => 3 }
console.log(alteredMap);  // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }

Si vous ne renvoyez pas cette clé comme premier élément du tableau, vous perdez vos Mapclés.

mayid
la source
1

Je préfère étendre la carte

export class UtilMap extends Map {  
  constructor(...args) { super(args); }  
  public map(supplier) {
      const mapped = new UtilMap();
      this.forEach(((value, key) => mapped.set(key, supplier(value, key)) ));
      return mapped;
  };
}
Nils Rösel
la source
1

Vous pouvez utiliser myMap.forEach, et dans chaque boucle, utiliser map.set pour modifier la valeur.

myMap = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3]
]);

for (var [key, value] of myMap.entries()) {
  console.log(key + ' = ' + value);
}


myMap.forEach((value, key, map) => {
  map.set(key, value+1)
})

for (var [key, value] of myMap.entries()) {
  console.log(key + ' = ' + value);
}

Chasseur Liu
la source
Je pense que cela manque le point. Array.map ne mute rien.
Aaron
0

const mapMap = (callback, map) => new Map(Array.from(map).map(callback))

var myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]]);

var newMap = mapMap((pair) => [pair[0], pair[1] + 1], myMap); // Map { 'thing1' => 2, 'thing2' => 3, 'thing3' => 4 }

David Braun
la source
0

Si vous ne voulez pas convertir au préalable la carte entière en un tableau et / ou déstructurer des tableaux clé-valeur, vous pouvez utiliser cette fonction idiote:

/**
 * Map over an ES6 Map.
 *
 * @param {Map} map
 * @param {Function} cb Callback. Receives two arguments: key, value.
 * @returns {Array}
 */
function mapMap(map, cb) {
  let out = new Array(map.size);
  let i = 0;
  map.forEach((val, key) => {
    out[i++] = cb(key, val);
  });
  return out;
}

let map = new Map([
  ["a", 1],
  ["b", 2],
  ["c", 3]
]);

console.log(
  mapMap(map, (k, v) => `${k}-${v}`).join(', ')
); // a-1, b-2, c-3

mpen
la source
0
Map.prototype.map = function(callback) {
  const output = new Map()
  this.forEach((element, key)=>{
    output.set(key, callback(element, key))
  })
  return output
}

const myMap = new Map([["thing1", 1], ["thing2", 2], ["thing3", 3]])
// no longer wishful thinking
const newMap = myMap.map((value, key) => value + 1)
console.info(myMap, newMap)

Cela dépend de votre ferveur religieuse pour éviter de modifier des prototypes, mais je trouve que cela me permet de rester intuitif.

Commi
la source
0

Vous pouvez mapper des tableaux (), mais cette opération n'existe pas pour Maps. La solution du Dr Axel Rauschmayer :

  • Convertissez la carte en un tableau de paires [clé, valeur].
  • Mappez ou filtrez le tableau.
  • Convertissez le résultat en une carte.

Exemple:

let map0 = new Map([
  [1, "a"],
  [2, "b"],
  [3, "c"]
]);

const map1 = new Map(
  [...map0]
  .map(([k, v]) => [k * 2, '_' + v])
);

abouti à

{2 => '_a', 4 => '_b', 6 => '_c'}
romain
la source
0

Peut-être de cette façon:

const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map((k, v) => [k, v * 2]); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }

Vous n'auriez besoin que de monkey patch Mapavant:

Map.prototype.map = function(func){
    return new Map(Array.from(this, ([k, v]) => func(k, v)));
}

Nous aurions pu écrire une forme plus simple de ce patch:

Map.prototype.map = function(func){
    return new Map(Array.from(this, func));
}

Mais nous nous aurions forcés à écrire ensuite m.map(([k, v]) => [k, v * 2]);ce qui me paraît un peu plus douloureux et laid.

Mappage des valeurs uniquement

Nous pourrions également mapper uniquement les valeurs, mais je ne conseillerais pas d'opter pour cette solution car elle est trop spécifique. Néanmoins, cela peut être fait et nous aurions l'API suivante:

const m = new Map([["a", 1], ["b", 2], ["c", 3]]);
m.map(v => v * 2); // Map { 'a' => 2, 'b' => 4, 'c' => 6 }

Juste comme avant de patcher de cette façon:

Map.prototype.map = function(func){
    return new Map(Array.from(this, ([k, v]) => [k, func(v)]));
}

Peut-être que vous pouvez avoir les deux, en nommant le second mapValuespour indiquer clairement que vous ne mappez pas réellement l'objet comme on s'y attendrait probablement.

cglacet
la source