Jak zmapować / zmniejszyć / przefiltrować zestaw w JavaScript?

131

Czy istnieje sposób na map/ reduce/ filter/ etc a Setw JavaScript, czy będę musiał napisać własny?

Oto kilka rozsądnych Set.prototyperozszerzeń

Set.prototype.map = function map(f) {
  var newSet = new Set();
  for (var v of this.values()) newSet.add(f(v));
  return newSet;
};

Set.prototype.reduce = function(f,initial) {
  var result = initial;
  for (var v of this) result = f(result, v);
  return result;
};

Set.prototype.filter = function filter(f) {
  var newSet = new Set();
  for (var v of this) if(f(v)) newSet.add(v);
  return newSet;
};

Set.prototype.every = function every(f) {
  for (var v of this) if (!f(v)) return false;
  return true;
};

Set.prototype.some = function some(f) {
  for (var v of this) if (f(v)) return true;
  return false;
};

Weźmy mały zestaw

let s = new Set([1,2,3,4]);

I kilka głupich małych funkcji

const times10 = x => x * 10;
const add = (x,y) => x + y;
const even = x => x % 2 === 0;

I zobacz, jak działają

s.map(times10);    //=> Set {10,20,30,40}
s.reduce(add, 0);  //=> 10
s.filter(even);    //=> Set {2,4}
s.every(even);     //=> false
s.some(even);      //=> true

Czy to nie miłe? Tak, też tak myślę. Porównaj to z brzydkim użyciem iteratora

// puke
let newSet = new Set();
for (let v in s) {
  newSet.add(times10(v));
}

I

// barf
let sum = 0;
for (let v in s) {
  sum = sum + v;
}

Czy jest lepszy sposób na wykonanie mapi reduceużycie Setw JavaScript?

Dziękuję Ci
źródło
Problem z redukcją mapowania a Setpolega na tym, że zestawy nie są funktorami.
Bartek Banachewicz
@BartekBanachewicz tak, to jest pewien problem ... prawda?
Dziękuję
2
Cóż, zastanów się var s = new Set([1,2,3,4]); s.map((a) => 42);. Zmienia liczbę elementów, co mapzwykle nie powinno. Jeszcze gorzej, jeśli porównujesz tylko części zachowanych obiektów, ponieważ wtedy technicznie nie jest określone, który z nich otrzymasz.
Bartek Banachewicz
Rozważyłem to, ale nie jestem pewien, czy (osobiście) uznałbym to za nieważne. OK, więc przynajmniej forEachistnieje dla tego scenariusza, ale dlaczego nie reduce?
Dziękuję

Odpowiedzi:

105

Krótkim sposobem na to jest przekonwertowanie go na tablicę za pośrednictwem operatora rozproszenia ES6.

Wtedy wszystkie funkcje macierzy są dostępne.

const mySet = new Set([1,2,3,4]);
[...mySet].reduce()
ZephDavies
źródło
1
Ponieważ funkcje nie są dostępne dla Set! Jest to kompletne, kierowane i zrozumiałe obejście, którego jeszcze nie ma w tym temacie. Fakt, że trwa to dłużej, jest smutną ceną za obejście tego problemu, dopóki Set nie wdroży tych funkcji!
ZephDavies
1
Jaka jest różnica między tym a Array.from
pete
9
Przynajmniej dla mnie różnica między tym a tym, Array.fromże Array.fromdziała z TypeScript. Użycie [...mySet]daje błąd:TS2461: Type 'Set<number>' is not an array type.
Mikal Madsen
1
Dla spreadu vs Array.from (), zobacz stackoverflow.com/a/40549565/5516454 Zasadniczo oba są tutaj użyteczne. Array.from () może dodatkowo wykonywać obiekty podobne do tablic, które nie implementują tej @@iteratormetody.
ZephDavies
nadal nie działa dla mnie z maszynopisem. DostajęERROR TypeError: this.sausages.slice is not a function
Simon_Weaver
22

Podsumowując dyskusję na podstawie komentarzy: chociaż nie ma technicznych powodów, aby nie mieć zestawureduce , obecnie nie jest dostarczany i możemy mieć tylko nadzieję, że zmieni się w ES7.

Jeśli chodzi o mapwywołanie tego samego, mogłoby to naruszyćSet ograniczenie, więc jego obecność tutaj może być dyskusyjna.

Rozważ mapowanie za pomocą funkcji (a) => 42- zmieni to rozmiar zestawu na 1 i może to być lub nie to, czego chciałeś.

Jeśli nie masz nic przeciwko naruszaniu tego, ponieważ np. I tak zamierzasz spasować, możesz zastosować map część na każdym elemencie tuż przed przekazaniem go reduce, akceptując w ten sposób, że kolekcja pośrednia ( która w tym momencie nie jest zestawem ) jest zostaną zredukowane, mogą mieć zduplikowane elementy. Jest to zasadniczo równoważne z konwersją do Array w celu przetwarzania.

Bartek Banachewicz
źródło
1
Jest to w większości dobre, z wyjątkiem tego, że (użycie powyższego kodu) s.map(a => 42)spowoduje Set { 42 }, że zmapowany wynik będzie miał inną długość, ale nie będzie "zduplikowanych" elementów. Może zaktualizuj sformułowanie, a zaakceptuję tę odpowiedź.
Dziękuję
@naomik Oh derp Właśnie kończyłem pierwszą kawę, kiedy to pisałem. Przy drugim spojrzeniu, kolekcja pośrednia przekazana do redukcji może mieć elementy natychmiastowe, jeśli zaakceptujesz, że nie jest to zestaw - to miałem na myśli.
Bartek Banachewicz
Aha rozumiem - mapa ma mapować do tego samego typu, stąd możliwe kolizje w zestawie docelowym. Kiedy znalazłem to pytanie, myślałem, że mapa odwzoruje na tablicę ze zbioru. (jakbyś zrobił set.toArray (). map () `
Simon_Weaver,
2
W Scali i Haskell zestawy obsługują operacje na mapie - może to zmniejszyć liczbę elementów w zestawie.
Velizar Hristov
8

Wydaje się, że przyczyną braku map/ reduce/ filterna Map/ Setkolekcji są głównie problemy koncepcyjne. Czy każdy typ kolekcji w Javascript rzeczywiście określa swoje własne iteracyjne metody, aby to umożliwić

const mySet = new Set([1,2,3]);
const myMap = new Map([[1,1],[2,2],[3,3]]);

mySet.map(x => x + 1);
myMap.map(([k, x]) => [k, x + 1]);

zamiast

new Set(Array.from(mySet.values(), x => x + 1));
new Map(Array.from(myMap.entries(), ([k, x]) => [k, x + 1]));

Alternatywą było określenie map / redukuj / filtruj jako części iterowalnego / iteracyjnego protokołu, ponieważ entries/ values/ keysreturnIterator s. Można sobie jednak wyobrazić, że nie każdy element iterowalny jest również „mapowalny”. Inną alternatywą było określenie w tym celu odrębnego „protokołu zbiórki”.

Nie znam jednak aktualnej dyskusji na ten temat w ES.


źródło