.map () um Mapa Javascript ES6?

98

Como você faria isso? Instintivamente, eu quero fazer:

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 }

Não obtive muito da documentação sobre o novo protocolo de iteração .

Estou ciente de wu.js , mas estou executando um projeto Babel e não quero incluir Traceur , do qual parece que depende atualmente .

Também não tenho ideia de como extrair como fitzgen / wu.js fez isso em meu próprio projeto.

Adoraria uma explicação clara e concisa do que estou perdendo aqui. Obrigado!


Docs for ES6 Map , FYI

neezer
fonte
Você consegue usar Array.from?
Ry-
@minitech Possivelmente, com um polyfill ... não há como fazer isso sem ele?
neezer
Bem, você pode escrever sua própria mapfunção para uso em iteráveis, usando for of.
Ry-
Super nitpick, mas se você realmente fosse mapear sobre um mapa, você receberia um mapa no final. Caso contrário, você está apenas convertendo um Mapa em um Array e mapeando sobre um Array, não .map () ing um Mapa. Você poderia facilmente mapear sobre um Mapa usando um ISO: dimap (x => [... x], x => novo Mapa (x));
Dtipson
@Ry bem, provavelmente podemos escrever nossa própria linguagem de programação, mas por que ..? É uma coisa muito simples e existe na maioria das linguagens de programação por muitas décadas.
ruX

Respostas:

79

Portanto, .mapele próprio oferece apenas um valor com o qual você se preocupa ... Dito isso, existem algumas maneiras de lidar com isso:

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

Observe, estou usando muitas coisas novas naquele segundo exemplo ... ... Array.frompega qualquer iterável (a qualquer momento que você usaria [].slice.call( ), mais Sets e Maps) e o transforma em um array ... ... Maps , quando coagidos em um array, se transformam em um array de arrays, onde el[0] === key && el[1] === value;(basicamente, no mesmo formato que eu pré-preenchi meu mapa de exemplo, acima).

Estou usando a desestruturação do array na posição do argumento do lambda, para atribuir esses pontos do array a valores, antes de retornar um objeto para cada el.

Se você estiver usando o Babel, em produção, precisará usar o polyfill do navegador do Babel (que inclui "core-js" e o "regenerador" do Facebook).
Tenho certeza de que contém Array.from.

Norguard
fonte
Sim, só percebendo que parece que vou precisar Array.frompara fazer isso. Seu primeiro exemplo não retorna uma matriz, aliás.
neezer
@neezer não, não importa. .forEachretorna undefineddireto, o tempo todo, pois o uso esperado de forEaché uma iteração simples baseada em efeitos colaterais (o mesmo que tem sido desde ES5). Para usar isso forEach, você deseja construir um array e preenchê-lo manualmente, por meio de dito forEachou por meio do iterador retornado por .entries()ou .keys( )ou .values( ). Array.fromnão está fazendo nada extraordinariamente único, está apenas escondendo aquela feiura particular de fazer essa travessia. E sim, verifique se incluindo babel-core/browser.js(ou seja lá o que for) você obtém .from...
Norguard
4
Veja minha resposta, Array.fromleva uma função de mapa como um parâmetro, você não precisa criar um array temporário apenas para mapear sobre ele e descartá-lo.
loganfsmyth
Você pode explicar por que o objeto precisa de parênteses ao redor dele? Ainda sou um pouco novo no ES6 e estou tendo problemas para entender essa parte. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… diz que é interpretado como um rótulo e não tenho certeza do que isso significa
dtc
1
@dtc sim, quando você escreve um objeto você escreve const obj = { x: 1 };, quando você escreve um bloco de código, você escreve if (...) { /* block */ }, quando você escreve uma função de seta você escreve () => { return 1; }, então como saber quando é o corpo de uma função, versus as chaves de um objeto? E a resposta está entre parênteses. Caso contrário, ele espera que o nome seja um rótulo para um bloco de código, um recurso raramente usado, mas você pode rotular um switchou um loop, de modo que possa break;retirá-los pelo nome. Então, se estiver faltando, você tem um corpo de função, com um rótulo e a instrução 1, com base no exemplo MDN
Norguard
35

Você deve apenas usar o operador 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
fonte
Parece que ainda requer Array.from, FYI.
neezer
1
@neezer você absolutamente, positivamente, deve usar o polyfill de Babel, a fim de usar o código transpilado por Babel em sua página, na produção. Não há como contornar isso, a menos que você implemente todos esses métodos (incluindo o Regenerator do Facebook) por você mesmo, à mão ...
Norguard
@Norguard Entendi - apenas uma alteração na configuração que preciso fazer é tudo o que eu quis dizer. Mais um aparte, realmente. :)
neezer
5
Apenas para observar, [...myMap].map(mapFn)vai criar um array temporário e, em seguida, descartá-lo. Array.fromrecebe a mapFncomo seu segundo argumento, basta usá-lo.
loganfsmyth
2
@wZVanG Por que não Array.from(myMap.values(), val => val + 1);?
loganfsmyth
22

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

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

var newArr = Array.from(myMap.values(), value => value + 1);
loganfsmyth
fonte
Obrigado por esta resposta, gostaria que fosse mais acima. Eu uso o padrão de espalhar um mapa em um array temporário o tempo todo, mas felizmente desconhecia o uso de Array.from()uma função de mapeamento como um segundo parâmetro opcional.
Andru
Isso é tão legal. E também funciona bem com o Flow, ao usar Map.values(), ao contrário de Array.values ​​() que ainda não funciona: facepalm:
ericsoco
4

Você pode usar esta função:

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

uso:

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é
fonte
3

Usando Array.from, escrevi uma função Typescript que mapeia os valores:

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
fonte
3

Na verdade, você ainda pode ter um Mapcom as chaves originais depois de converter para um array com Array.from. Isso é possível retornando um array , onde o primeiro item é o keye o segundo é o transformado 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 você não retornar essa chave como o primeiro item do array, perderá suas Mapchaves.

mayid
fonte
1

Eu prefiro estender o mapa

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
fonte
1

Você pode usar myMap.forEach e, em cada loop, usar map.set para alterar o valor.

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

Hunter Liu
fonte
Eu acho que isso erra o ponto. Array.map não muda nada.
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
fonte
0

Se você não quiser converter o Map inteiro em uma matriz de antemão e / ou desestruturar matrizes de valores-chave, você pode usar esta função boba:

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

Depende do seu fervor religioso em evitar a edição de protótipos, mas acho que isso me permite mantê-lo intuitivo.

Commi
fonte
0

Você pode mapear matrizes (), mas não existe tal operação para Mapas. A solução do Dr. Axel Rauschmayer :

  • Converta o mapa em uma matriz de pares [chave, valor].
  • Mapeie ou filtre a matriz.
  • Converta o resultado de volta em um mapa.

Exemplo:

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

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

resultou em

{2 => '_a', 4 => '_b', 6 => '_c'}
romano
fonte
-1

Talvez desta forma:

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

Você só precisa fazer o monkey patch Mapantes de:

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

Poderíamos ter escrito uma forma mais simples deste patch:

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

Mas teríamos nos forçado a escrever, o m.map(([k, v]) => [k, v * 2]);que me parece um pouco mais doloroso e feio.

Somente valores de mapeamento

Também poderíamos mapear apenas valores, mas eu não recomendaria essa solução, pois é muito específica. No entanto, isso pode ser feito e teríamos a seguinte API:

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

Exatamente como antes de corrigir desta forma:

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

Talvez você possa ter ambos, nomeando o segundo mapValuespara deixar claro que você não está realmente mapeando o objeto como seria de se esperar.

cglacet
fonte