.map () a Javascript ES6 Map?

89

Jak byś to zrobił? Instynktownie chcę zrobić:

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 }

Nie zebrałem zbyt wiele z dokumentacji nowego protokołu iteracji .

Znam wu.js , ale prowadzę projekt Babel i nie chcę włączać Traceur , od którego wydaje się, że obecnie zależy .

Nie mam też pojęcia, jak wyodrębnić sposób, w jaki fitzgen / wu.js zrobił to w moim własnym projekcie.

Chciałbym jasne, zwięzłe wyjaśnienie tego, czego tutaj brakuje. Dzięki!


Dokumenty dla mapy ES6 , do Twojej wiadomości

neezer
źródło
Czy jesteś w stanie użyć Array.from?
Ry-
@minitech Ewentualnie z polyfillem ... czy nie da się tego zrobić bez niego?
neezer
Cóż, możesz napisać własną mapfunkcję do użycia w iterable, używając for of.
Ry-
Super czubek, ale jeśli naprawdę zamierzasz mapować na mapie, na końcu odzyskasz mapę. W przeciwnym razie najpierw konwertujesz Mapę na Array i mapujesz na Array, a nie .map () ing Map. Możesz łatwo odwzorować mapę za pomocą ISO: dimap (x => [... x], x => new Map (x));
Dtipson
@ No cóż, prawdopodobnie możemy napisać własny język programowania, ale dlaczego…? To dość prosta rzecz i istnieje w większości języków programowania od wielu dziesięcioleci.
ruX

Odpowiedzi:

73

Więc .mapsam w sobie oferuje tylko jedną wartość, na której Ci zależy ... To powiedziawszy, jest kilka sposobów rozwiązania tego problemu:

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

Uwaga, w tym drugim przykładzie używam wielu nowych rzeczy ... ... Array.frompobiera dowolne iterowalne elementy (za każdym razem, gdy używasz [].slice.call( ), oraz zestawy i mapy) i zamienia je w tablicę ... ... Mapy , po przekształceniu w tablicę zamienia się w tablicę tablic, gdzie el[0] === key && el[1] === value;(w zasadzie w tym samym formacie, w którym wstępnie wypełniłem moją przykładową Mapę, powyżej).

Używam destrukturyzacji tablicy w pozycji argumentu lambdy, aby przypisać te miejsca tablicy do wartości, przed zwróceniem obiektu dla każdego el.

Jeśli używasz Babel w produkcji, będziesz musiał użyć wypełnienia przeglądarki Babel (który zawiera „core-js” i „regenerator” Facebooka).
Jestem całkiem pewien, że zawiera Array.from.

Norguard
źródło
Tak, po prostu zauważam, że wygląda na to, że będę potrzebować Array.from, aby to zrobić. Twój pierwszy przykład nie zwraca tablicy, przy okazji.
neezer
@neezer nie, nie działa. .forEachzwraca się undefinedcałkowicie, przez cały czas, ponieważ oczekiwane użycie forEachjest prostą iteracją opartą na efektach ubocznych (tak samo jak od czasu ES5). Aby z tego skorzystać forEach, chciałbyś zbudować tablicę i zapełnić ją ręcznie, albo przez said, forEachalbo przez iterator zwracany przez .entries()lub .keys( )lub .values( ). Array.fromnie robi niczego niezwykłego, po prostu ukrywa tę szczególną brzydotę wykonywania wspomnianego przemierzania. I tak, sprawdź to, babel-core/browser.js.from
dołączając
4
Zobacz moją odpowiedź, Array.fromprzyjmuje funkcję mapy jako parametr, nie musisz tworzyć tymczasowej tablicy tylko po to, aby ją zmapować i odrzucić.
loganfsmyth
Czy możesz wyjaśnić, dlaczego obiekt musi być otoczony nawiasami? Wciąż jestem trochę nowy w ES6 i nie rozumiem tej części. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… mówi, że jest interpretowany jako etykieta i nie jestem pewien, co to oznacza
dtc
1
@dtc tak, kiedy piszesz obiekt, który piszesz const obj = { x: 1 };, kiedy piszesz blok kodu, piszesz if (...) { /* block */ }, kiedy piszesz funkcję strzałkową, którą piszesz () => { return 1; }, więc skąd ona wie, kiedy jest to treść funkcji, w porównaniu z nawiasami klamrowymi obiektu? A odpowiedź to nawiasy. W przeciwnym razie oczekuje, że nazwa jest etykietą dla rzadko używanego bloku kodu, ale można oznaczyć switchpętlę lub pętlę, aby można break;było z nich wyjść po nazwie. Więc jeśli go brakuje, masz 1
treść
34

Powinieneś po prostu użyć operatora 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
źródło
Wygląda na to, że to nadal wymaga Array.from, FYI.
neezer
1
@neezer absolutnie, zdecydowanie musisz użyć polyfill Babel, aby użyć kodu transponowanego Babel na twojej stronie, w produkcji. Nie
da
@Norguard Rozumiem - tylko zmiana konfiguracji, którą muszę wprowadzić, to wszystko, co miałem na myśli. Bardziej na bok, naprawdę. :)
neezer
5
Uwaga: [...myMap].map(mapFn)utworzy tymczasową tablicę, a następnie ją odrzuci. Array.fromprzyjmuje mapFnjako drugi argument, po prostu użyj tego.
loganfsmyth
2
@wZVanG Dlaczego nie Array.from(myMap.values(), val => val + 1);?
loganfsmyth
21

Po prostu użyj Array.from(iterable, [mapFn]).

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

var newArr = Array.from(myMap.values(), value => value + 1);
loganfsmyth
źródło
Dzięki za tę odpowiedź, szkoda, że ​​nie było wyżej. Cały czas używam schematu rozprzestrzeniania mapy w tymczasowej tablicy, beztrosko nieświadomy Array.from()wziął funkcję mapowania jako opcjonalny drugi parametr.
Andru
4

Możesz użyć tej funkcji:

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

stosowanie:

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é
źródło
3

Używając Array.fromnapisałem funkcję Typescript, która mapuje wartości:

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
źródło
3

Właściwie nadal możesz mieć Mapz oryginalnymi kluczami po konwersji na tablicę z Array.from. Jest to możliwe, zwracając tablicę , w której pierwszy element to key, a drugi to przekształcenie 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 }

Jeśli nie zwrócisz tego klucza jako pierwszego elementu tablicy, stracisz Mapklucze.

mayid
źródło
1

Wolę rozszerzać mapę

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
źródło
1

Możesz użyć myMap.forEach, aw każdej pętli użyć map.set do zmiany wartości.

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
źródło
Myślę, że to mija się z celem. Array.map niczego nie modyfikuje.
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
źródło
0

Jeśli nie chcesz wcześniej konwertować całej mapy na tablicę i / lub zniszczyć tablice klucz-wartość, możesz użyć tej głupiej funkcji:

/**
 * 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
źródło
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)

Zależy od twojego religijnego zapału w unikaniu edycji prototypów, ale uważam, że pozwala mi to zachować intuicję.

Commi
źródło
0

Możesz mapować () tablice, ale nie ma takiej operacji dla Map. Rozwiązanie Dr. Axela Rauschmayera :

  • Przekształć mapę w tablicę par [klucz, wartość].
  • Mapuj lub filtruj tablicę.
  • Przekształć wynik z powrotem w mapę.

Przykład:

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

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

doprowadzony

{2 => '_a', 4 => '_b', 6 => '_c'}
rzymski
źródło
0

Może w ten sposób:

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

Musisz tylko do plastra małpa Mapprzed:

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

Mogliśmy napisać prostszą formę tej łatki:

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

Zmusilibyśmy nas jednak do napisania, m.map(([k, v]) => [k, v * 2]);co wydaje mi się nieco bardziej bolesne i brzydkie.

Mapowanie tylko wartości

Moglibyśmy również mapować tylko wartości, ale nie radziłbym wybierać tego rozwiązania, ponieważ jest zbyt szczegółowe. Niemniej jednak można to zrobić i mielibyśmy następujące API:

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

Tak jak przed łataniem w ten sposób:

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

Może możesz mieć oba, nazywając drugą, mapValuesaby było jasne, że w rzeczywistości nie mapujesz obiektu, jak prawdopodobnie by się tego spodziewał.

cglacet
źródło