Come mappare / ridurre / filtrare un set in JavaScript?


131

C'è un modo per map/ reduce/ filter/ etc a Setin JavaScript o dovrò scriverne uno mio?

Ecco alcune Set.prototypeestensioni sensate

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

Facciamo un piccolo set

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

E alcune stupide piccole funzioni

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

E guarda come funzionano

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

Non è carino? Sì, lo penso anche io. Confrontalo con il brutto utilizzo dell'iteratore

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

E

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

Esiste un modo migliore per realizzare mape reduceutilizzare un Setin JavaScript?


Il problema con la riduzione della mappa a Setè che i set non sono funzionari.
Bartek Banachewicz,

@BartekBanachewicz sì, è un problema ... giusto?
Grazie

2
Bene, considera var s = new Set([1,2,3,4]); s.map((a) => 42);. Cambia il numero di elementi, che in mapgenere non dovrebbe fare. Ancora peggio se stai solo confrontando parti degli oggetti conservati, perché tecnicamente non è specificato quale otterrai.
Bartek Banachewicz,

L'avevo considerato, ma non sono sicuro che (personalmente) lo considererei non valido. OK, almeno forEachesiste per quello scenario, ma perché no reduceallora?
Grazie,

Risposte:


105

Un modo breve per farlo è convertirlo in un array tramite l'operatore spread ES6.

Quindi tutte le funzioni dell'array sono disponibili.

const mySet = new Set([1,2,3,4]);
[...mySet].reduce()

1
Perché le funzioni non sono disponibili per Set! Questa è una soluzione completa, guidata e compresa che non è ancora presente in questo argomento. Il fatto che "impieghi più tempo" è un prezzo triste da pagare per una soluzione alternativa fino a quando Set non implementa queste funzionalità!
ZephDavies

1
Qual è la differenza tra questo e Array.from
pete

9
Almeno per me, la differenza tra questo ed Array.fromè che Array.fromfunziona con TypeScript. L'utilizzo [...mySet]dà l'errore:TS2461: Type 'Set<number>' is not an array type.
Mikal Madsen,

1
Per spread vs Array.from (), consultare stackoverflow.com/a/40549565/5516454 Fondamentalmente, entrambi sono utilizzabili qui. Array.from () può inoltre eseguire oggetti simili a array che non implementano il @@iteratormetodo.
ZephDavies,

non funziona ancora per me con dattiloscritto. RicevoERROR TypeError: this.sausages.slice is not a function
Simon_Weaver il

22

Per riassumere la discussione dai commenti: anche se non ci sono ragioni tecniche per non avere set, al momento nonreduce è fornito e possiamo solo sperare che cambi in ES7.

Per quanto riguarda map, chiamarlo da solo potrebbe violare il Setvincolo, quindi la sua presenza qui potrebbe essere discutibile.

Prendi in considerazione la mappatura con una funzione (a) => 42: cambierà la dimensione del set su 1 e questo potrebbe o meno essere quello che volevi.

Se sei d'accordo con la violazione di questo perché, ad esempio, hai intenzione di piegare comunque, puoi applicare la mapparte su ogni elemento appena prima di passarli reduce, accettando così che la raccolta intermedia ( che non è un Set a questo punto ) che è sta per essere ridotto potrebbe avere elementi duplicati. Ciò equivale essenzialmente alla conversione in array per eseguire l'elaborazione.


1
Questo è per lo più buono, tranne (usando il codice sopra), s.map(a => 42)si tradurrà Set { 42 }quindi il risultato mappato avrà una lunghezza diversa ma non ci saranno elementi "duplicati". Forse aggiorno il testo e accetterò questa risposta.
Grazie

@naomik Oh derp Stavo finendo il mio primo caffè mentre lo scrivevo. Al secondo aspetto, la raccolta intermedia passata alla riduzione potrebbe avere elementi immediati se si accetta che non è un set - intendevo.
Bartek Banachewicz,

Oh, ho capito: la mappa deve essere dello stesso tipo, quindi possibili collisioni nel set di destinazione. Quando ho trovato questa domanda pensavo che la mappa sarebbe stata mappata su un array da un set. (come se avessi impostato set.toArray (). map () `
Simon_Weaver

2
In Scala e Haskell, i set supportano un'operazione sulla mappa: può ridurre il numero di elementi nel set.
Velizar Hristov,

8

La causa della mancanza di map/ reduce/ filteron Map/ Setcollezioni sembra essere principalmente preoccupazioni concettuali. Ogni tipo di raccolta in Javascript dovrebbe effettivamente specificare i propri metodi iterativi solo per consentire ciò

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

invece di

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

Un'alternativa era specificare mappa / ridurre / filtro come parte del protocollo iterabile / iteratore, poiché entries/ values/ keysreturn Iterators. È ipotizzabile che non tutti gli iterabili siano anche "mappabili". Un'altra alternativa era quella di specificare un "protocollo di raccolta" separato proprio per questo scopo.

Tuttavia, non conosco l'attuale discussione su questo argomento in ES.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.