.map () a Javascript ES6 Map?

98

Bagaimana Anda melakukan ini? Secara naluriah, saya ingin melakukan:

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 }

Saya belum mengumpulkan banyak dari dokumentasi tentang protokol iterasi baru .

Saya mengetahui wu.js , tetapi saya menjalankan proyek Babel dan tidak ingin menyertakan Traceur , yang tampaknya saat ini bergantung padanya .

Saya juga sedikit tidak mengerti bagaimana cara mengekstrak bagaimana fitzgen / wu.js melakukannya ke dalam proyek saya sendiri.

Akan sangat menyukai penjelasan yang jelas dan ringkas tentang apa yang saya lewatkan di sini. Terima kasih!


Docs untuk Peta ES6 , FYI

neezer
sumber
Apakah Anda bisa menggunakan Array.from?
Ry-
@minitech Mungkin, dengan polyfill ... apakah tidak ada cara untuk melakukan ini tanpanya?
neezer
Nah, Anda bisa menulis mapfungsi Anda sendiri untuk digunakan di iterable, menggunakan for of.
Ry-
Super nitpick, tetapi jika Anda benar-benar akan memetakan Peta, Anda akan mendapatkan kembali Peta pada akhirnya. Jika tidak, Anda baru pertama kali mengubah Map menjadi Array dan memetakan melalui Array, bukan .map () pada Map. Anda dapat dengan mudah memetakan Peta dengan menggunakan ISO: dimap (x => [... x], x => Peta baru (x));
Dtipson
@Ry baiklah, kita mungkin bisa menulis bahasa pemrograman kita sendiri, tapi kenapa ..? Ini hal yang cukup sederhana dan ada di sebagian besar bahasa pemrograman selama beberapa dekade.
ruX

Jawaban:

79

Jadi .mapitu sendiri hanya menawarkan satu nilai yang Anda pedulikan ... Meskipun demikian, ada beberapa cara untuk mengatasi ini:

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

Perhatikan, saya menggunakan banyak hal baru dalam contoh kedua itu ... ... Array.frommengambil iterable (kapan pun Anda akan menggunakan [].slice.call( ), ditambah Set dan Maps) dan mengubahnya menjadi sebuah array ... ... Maps , ketika dipaksa menjadi sebuah array, berubah menjadi array dari array, di mana el[0] === key && el[1] === value;(pada dasarnya, dalam format yang sama yang saya isi sebelumnya dengan contoh Map, di atas).

Saya menggunakan destrukturisasi array dalam posisi argumen lambda, untuk menetapkan tempat array tersebut ke nilai, sebelum mengembalikan objek untuk setiap el.

Jika Anda menggunakan Babel, dalam produksi, Anda akan perlu menggunakan polyfill browser Babel (yang menyertakan "core-js" dan "regenerator" Facebook).
Saya cukup yakin itu berisi Array.from.

Norguard
sumber
Ya, hanya menyadari bahwa sepertinya aku perlu Array.frommelakukan ini. Contoh pertama Anda tidak mengembalikan larik, btw.
neezer
@ neezer tidak, tidak. .forEachkembali undefineddatar, sepanjang waktu, karena penggunaan yang diharapkan dari forEachadalah iterasi berbasis efek samping sederhana (sama seperti sejak ES5). Untuk menggunakannya forEach, Anda ingin membangun sebuah array dan mengisinya dengan tangan, baik melalui kata forEachatau melalui iterator yang dikembalikan oleh .entries()atau .keys( )atau .values( ). Array.fromtidak melakukan sesuatu yang benar-benar unik, itu hanya menyembunyikan keburukan tertentu dari melakukan traversal tersebut. Dan ya, periksa dengan memasukkan babel-core/browser.js(atau apa pun itu) yang Anda dapatkan .from...
Norguard
4
Lihat jawaban saya, Array.frommengambil fungsi peta sebagai param, Anda tidak perlu membuat array sementara hanya untuk memetakannya dan membuangnya.
loganfsmyth
Dapatkah Anda menjelaskan mengapa objek tersebut membutuhkan tanda kurung yang dililitkan? Saya masih sedikit baru mengenal ES6 dan kesulitan memahami bagian itu. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… mengatakan bahwa itu ditafsirkan sebagai label dan saya tidak yakin apa artinya
dtc
1
@dtc ya, ketika Anda menulis objek yang Anda tulis const obj = { x: 1 };, ketika Anda menulis blok kode, Anda menulis if (...) { /* block */ }, ketika Anda menulis fungsi panah yang Anda tulis () => { return 1; }, jadi bagaimana ia tahu kapan itu adalah badan fungsi, versus tanda kurung dari suatu objek? Dan jawabannya adalah tanda kurung. Jika tidak, ia mengharapkan bahwa nama adalah label untuk blok kode fitur yang jarang digunakan, tetapi Anda dapat memberi label switchatau loop, sehingga Anda dapat break;keluar dari mereka dengan nama. Jadi jika tidak ada, Anda memiliki badan fungsi, dengan label, dan pernyataan 1, berdasarkan contoh MDN
Norguard
35

Anda hanya harus menggunakan operator 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
sumber
Sepertinya itu masih membutuhkan Array.from, FYI.
neezer
1
@neezer Anda tentu saja, secara positif harus menggunakan polyfill Babel, untuk menggunakan kode Babel-transpiled pada halaman Anda, dalam produksi. Tidak ada jalan lain, kecuali jika Anda menerapkan semua metode itu (termasuk Regenerator Facebook) sendiri, dengan tangan ...
Norguard
@Norguard Mengerti - hanya perubahan konfigurasi yang harus saya lakukan adalah yang saya maksud. Lebih dari sebuah samping, sungguh. :)
neezer
5
Sekadar catatan, [...myMap].map(mapFn)akan membuat array sementara dan kemudian membuangnya. Array.frommengambil mapFnargumen kedua, gunakan saja itu.
loganfsmyth
2
@wZVanG Mengapa tidak Array.from(myMap.values(), val => val + 1);?
loganfsmyth
22

Gunakan saja Array.from(iterable, [mapFn]).

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

var newArr = Array.from(myMap.values(), value => value + 1);
loganfsmyth
sumber
Terima kasih atas jawaban ini, saya berharap itu lebih tinggi. Saya menggunakan pola menyebarkan Peta ke dalam larik sementara sepanjang waktu, tanpa sadar Array.from()mengambil fungsi pemetaan sebagai parameter opsional kedua.
Andru
Ini sangat bagus. Dan itu bermain bagus dengan Flow juga, saat digunakan Map.values(), tidak seperti Array.values ​​() yang masih tidak: facepalm:
ericsoco
4

Anda dapat menggunakan fungsi ini:

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

pemakaian:

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

Menggunakan Array.fromsaya menulis fungsi Ketikan yang memetakan nilai:

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

Sebenarnya Anda masih dapat memiliki a Mapdengan kunci asli setelah mengonversi ke array dengan Array.from. Itu dimungkinkan dengan mengembalikan larik , di mana item pertama adalah key, dan yang kedua adalah transformasi 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 }

Jika Anda tidak mengembalikan kunci itu sebagai item larik pertama, Anda kehilangan Mapkunci.

mayid
sumber
1

Saya lebih suka memperluas peta

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

Anda dapat menggunakan myMap.forEach, dan di setiap loop, menggunakan map.set untuk mengubah nilai.

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
sumber
Saya pikir ini melenceng. Array.map tidak mengubah apapun.
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
sumber
0

Jika Anda tidak ingin mengonversi seluruh Map menjadi array sebelumnya, dan / atau merusak susunan nilai-kunci, Anda dapat menggunakan fungsi konyol ini:

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

Bergantung pada semangat religius Anda dalam menghindari prototipe pengeditan, tetapi, menurut saya ini memungkinkan saya membuatnya tetap intuitif.

Commi
sumber
0

Anda bisa memetakan () array, tetapi tidak ada operasi seperti itu untuk Maps. Solusi dari Dr. Axel Rauschmayer :

  • Ubah peta menjadi larik pasangan [key, value].
  • Petakan atau filter array.
  • Ubah hasilnya kembali menjadi peta.

Contoh:

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

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

menghasilkan

{2 => '_a', 4 => '_b', 6 => '_c'}
Roma
sumber
-1

Mungkin begini:

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

Anda hanya perlu menambal monyet Mapsebelumnya:

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

Kami bisa saja menulis bentuk yang lebih sederhana dari tambalan ini:

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

Tapi kami akan memaksa kami untuk kemudian menulis m.map(([k, v]) => [k, v * 2]); yang tampaknya sedikit lebih menyakitkan dan jelek bagi saya.

Nilai pemetaan saja

Kami juga dapat memetakan nilai saja, tetapi saya tidak akan menyarankan solusi itu karena terlalu spesifik. Namun demikian itu bisa dilakukan dan kami akan memiliki API berikut:

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

Sama seperti sebelum menambal dengan cara ini:

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

Mungkin Anda dapat memiliki keduanya, memberi nama yang kedua mapValuesuntuk memperjelas bahwa Anda sebenarnya tidak memetakan objek seperti yang diharapkan.

cglacet.dll
sumber