.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


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.


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]


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

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

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 }

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.


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

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


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 }


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


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ę.


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

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ł.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.