.map () una mappa Javascript ES6?


98

come lo faresti? Istintivamente, voglio fare:

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 }

Non ho raccolto molto dalla documentazione sul nuovo protocollo di iterazione .

Sono a conoscenza di wu.js , ma sto conducendo un progetto Babel e non voglio includere Traceur , da cui sembra che attualmente dipenda .

Sono anche un po ' incapace di come estrarre come fitzgen / wu.js lo ha fatto nel mio progetto.

Mi piacerebbe una spiegazione chiara e concisa di ciò che mi manca qui. Grazie!


Documenti per la mappa ES6 , FYI


Sei in grado di utilizzare Array.from?
Ry-

@minitech Possibilmente, con un polyfill ... non c'è un modo per farlo senza di esso?
neezer

Bene, potresti scrivere la tua mapfunzione da usare sugli iterabili, usando for of.
Ry-

Super nitpick, ma se avessi davvero intenzione di mappare su una mappa, alla fine otterrai una mappa. Altrimenti devi solo convertire prima una mappa in un array e mappare su un array, non .map () in una mappa. Puoi facilmente mappare su una mappa usando una ISO: dimap (x => [... x], x => new Map (x));
Dtipson

@ Rey bene, probabilmente possiamo scrivere il nostro linguaggio di programmazione, ma perché ..? È una cosa abbastanza semplice ed esiste nella maggior parte dei linguaggi di programmazione da molti decenni.
ruX

Risposte:


79

Quindi di per .mapsé offre solo un valore a cui tieni ... Detto questo, ci sono alcuni modi per affrontare questo:

// 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);
}

Nota, sto usando molte cose nuove in quel secondo esempio ... ... Array.fromprende qualsiasi iterabile (ogni volta che useresti [].slice.call( ), più Set e Mappe) e lo trasforma in un array ... ... Mappe , quando viene forzato in un array, si trasforma in un array di array, dove el[0] === key && el[1] === value;(fondamentalmente, nello stesso formato con cui ho precompilato la mia mappa di esempio, sopra).

Sto usando la destrutturazione dell'array nella posizione dell'argomento del lambda, per assegnare quei punti dell'array ai valori, prima di restituire un oggetto per ogni el.

Se stai usando Babel, in produzione, dovrai usare il polyfill del browser di Babel (che include "core-js" e il "rigeneratore" di Facebook).
Sono abbastanza certo che contenga Array.from.


Sì, sto solo notando che sembra che avrò bisogno Array.fromper farlo. Il tuo primo esempio non restituisce un array, btw.
neezer

@neezer no, non è così. .forEachrestituisce undefinedcostantemente, in quanto l'uso previsto di forEachè una semplice iterazione basata sugli effetti collaterali (lo stesso che è stato da ES5). Per usarlo forEach, dovresti costruire un array e popolarlo a mano, tramite detto forEacho tramite l'iteratore restituito da .entries()o .keys( )o .values( ). Array.fromnon sta facendo niente di incredibilmente unico, sta solo nascondendo quella particolare bruttezza di fare detto attraversamento. E sì, controlla che includendo babel-core/browser.js(o qualunque cosa sia) ottieni .from...
Norguard

4
Vedi la mia risposta, Array.fromaccetta una funzione di mappa come parametro, non è necessario creare un array temporaneo solo per mapparlo e scartarlo.
loganfsmyth

Puoi spiegare perché l'oggetto necessita di parentesi avvolte attorno ad esso? Sono ancora un po 'nuovo su ES6 e ho problemi a capire quella parte. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… dice che viene interpretato come un'etichetta e non sono sicuro di cosa significhi
dtc

1
@dtc sì, quando scrivi un oggetto scrivi const obj = { x: 1 };, quando scrivi un blocco di codice, scrivi if (...) { /* block */ }, quando scrivi una funzione freccia scrivi () => { return 1; }, quindi come fa a sapere quando è un corpo di funzione, rispetto alle parentesi graffe di un oggetto? E la risposta è tra parentesi. Altrimenti, si aspetta che il nome sia un'etichetta per un blocco di codice una funzionalità usata raramente, ma puoi etichettare a switcho un ciclo, in modo da poterli break;uscire per nome. Quindi, se manca, hai un corpo della funzione, con un'etichetta e la dichiarazione 1, basata sull'esempio MDN
Norguard

35

Dovresti semplicemente usare l' operatore 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]


Sembra che sia ancora necessario Array.from, FYI.
neezer

1
@neezer si assolutamente, positivamente necessario utilizzare polyfill di Babel, al fine di utilizzare il codice Babel-transpiled sulla tua pagina, in produzione. Non c'è modo di
aggirarlo

@Norguard Capito - solo una modifica alla configurazione che dovrò fare è tutto ciò che intendevo. Più di un a parte, davvero. :)
neezer

5
Solo per notare, [...myMap].map(mapFn)creerà un array temporaneo e quindi lo eliminerà. Array.fromprende mapFncome secondo argomento, usalo.
loganfsmyth

2
@wZVanG Perché no Array.from(myMap.values(), val => val + 1);?
loganfsmyth

22

Basta usare Array.from(iterable, [mapFn]).

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

var newArr = Array.from(myMap.values(), value => value + 1);

Grazie per questa risposta, vorrei che fosse più in alto. Uso sempre il modello di diffusione di una mappa in un array temporaneo, allegramente inconsapevole ha Array.from()preso una funzione di mappatura come secondo parametro opzionale.
Andru

È così carino. E funziona bene anche con Flow, quando si utilizza Map.values(), a differenza di Array.values ​​() che ancora non funziona: facepalm:
ericsoco

4

Puoi usare questa funzione:

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

utilizzo:

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 }
*/

3

Utilizzando Array.fromho scritto una funzione Typescript che mappa i valori:

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 }

3

In realtà puoi ancora avere un Mapcon le chiavi originali dopo la conversione in array con Array.from. Ciò è possibile restituendo un array , in cui il primo elemento è il keye il secondo è il trasformato 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 }

Se non restituisci quella chiave come primo elemento dell'array, perdi le Mapchiavi.


1

Preferisco estendere la mappa

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;
  };
}

1

È possibile utilizzare myMap.forEach e in ogni ciclo utilizzando map.set per modificare il valore.

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


Penso che questo non colga il punto. Array.map non modifica nulla.
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 }


0

Se non desideri convertire in anticipo l'intera mappa in un array e / o destrutturare gli array di valori-chiave, puoi utilizzare questa sciocca funzione:

/**
 * 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


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)

Dipende dal tuo fervore religioso nell'evitare di modificare i prototipi, ma trovo che questo mi consenta di mantenerlo intuitivo.


0

Puoi map () array, ma non esiste tale operazione per Maps. La soluzione del dottor Axel Rauschmayer :

  • Converti la mappa in un array di coppie [chiave, valore].
  • Mappa o filtra la matrice.
  • Converti il ​​risultato in una mappa.

Esempio:

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

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

provocato

{2 => '_a', 4 => '_b', 6 => '_c'}

-1

Forse in questo modo:

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

Avresti solo bisogno di scimmia patch Mapprima:

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

Avremmo potuto scrivere una forma più semplice di questa patch:

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

Ma ci saremmo costretti a scrivere poi m.map(([k, v]) => [k, v * 2]);che mi sembra un po 'più doloroso e brutto.

Solo valori di mappatura

Potremmo anche mappare solo i valori, ma non consiglierei di optare per quella soluzione poiché è troppo specifica. Tuttavia può essere fatto e avremmo la seguente API:

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

Proprio come prima di applicare la patch in questo modo:

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

Forse puoi avere entrambi, nominando il secondo mapValuesper chiarire che non stai effettivamente mappando l'oggetto come probabilmente ci si aspetterebbe.

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.