Como mapear / reduzir / filtrar um conjunto em JavaScript?


131

Existe alguma maneira de map/ reduce/ filter/ etc a Setem JavaScript ou terei que escrever minha própria?

Aqui estão algumas Set.prototypeextensões sensatas

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

Vamos dar um pequeno set

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

E algumas pequenas funções estúpidas

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

E veja como eles funcionam

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

Isso não é legal? Sim, eu também acho. Compare isso com o feio uso do iterador

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

Existe alguma maneira melhor de realizar mape reduceusar um Setem JavaScript?


O problema com a redução de mapa a Seté que Sets não são Functors.
Bartek Banachewicz

@BartekBanachewicz yeah, isso é meio que um problema ... certo?
Obrigado

2
Bem, considere var s = new Set([1,2,3,4]); s.map((a) => 42);. Ele altera o número de elementos, o que mapnormalmente não deveria ser feito. Pior ainda, se você estiver comparando apenas partes dos objetos mantidos, porque, tecnicamente, não é especificado qual deles você obterá.
Bartek Banachewicz

Eu tinha considerado isso, mas não tenho certeza se eu (pessoalmente) consideraria isso inválido. OK, pelo menos forEachexiste para esse cenário, mas por que não reduce?
Obrigado

Respostas:


105

Uma maneira abreviada de fazer isso é convertê-lo em uma matriz por meio do operador de spread ES6.

Todas as funções da matriz estão disponíveis para você.

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

1
Porque as funções não estão disponíveis para o Set! Esta é uma solução alternativa completa, guiada e compreendida que ainda não está presente neste tópico. O fato de "demorar mais" é um preço triste a ser pago por uma solução alternativa até que o Set implemente esses recursos!
precisa saber é o seguinte

1
Qual é a diferença entre this e Array
pete

9
Para mim, pelo menos, a diferença entre isso e Array.fromé que Array.fromfunciona com o TypeScript. Usando [...mySet]dá o erro:TS2461: Type 'Set<number>' is not an array type.
Mikal Madsen 14/07

1
Para spread versus Array.from (), consulte stackoverflow.com/a/40549565/5516454 Basicamente, ambos são utilizáveis ​​aqui. Array.from () também pode executar objetos do tipo array que não implementam o @@iteratormétodo.
precisa saber é o seguinte

ainda não funciona para mim com texto datilografado. Eu receboERROR TypeError: this.sausages.slice is not a function
Simon_Weaver

22

Para resumir a discussão a partir dos comentários: embora não haja motivos técnicos para o conjunto não ter reduce, ele não é fornecido no momento e só podemos esperar que ele mude no ES7.

Quanto a mapchamá-lo por si só, pode violar a Setrestrição; portanto, sua presença aqui pode ser discutível.

Considere o mapeamento com uma função (a) => 42- ele mudará o tamanho do conjunto para 1, e isso pode ou não ser o que você queria.

Se você concorda em violar isso, porque, por exemplo, você vai dobrar de qualquer maneira, pode aplicar a mapparte em todos os elementos antes de passá-los para reduce, aceitando assim que a coleção intermediária ( que não é um conjunto neste momento ) seja será reduzido pode ter elementos duplicados. Isso é essencialmente equivalente à conversão em Array para processamento.


1
Isso geralmente é bom, exceto (usando o código acima) s.map(a => 42)resultará em Set { 42 }um resultado diferente, mas não haverá elementos "duplicados". Talvez atualize o texto e eu aceito esta resposta.
Obrigado

@naomik Oh derp Eu estava terminando meu primeiro café ao escrever isso. No segundo aspecto, a coleção intermediária passada para reduzir pode ter elementos imediatos se você aceitar que não é um conjunto - foi isso que eu quis dizer.
Bartek Banachewicz

Ah, entendi - o mapa precisa ser mapeado para o mesmo tipo, portanto, possíveis colisões no conjunto de destinos. Quando encontrei essa pergunta, pensei que o mapa seria mapeado para uma matriz de um conjunto. (como se você configurasse set.toArray (). map () `
Simon_Weaver 4/19/19

2
Em Scala e Haskell, os conjuntos suportam uma operação de mapa - isso pode reduzir o número de elementos no conjunto.
Velizar Hristov 21/01/19

8

A causa da falta de map/ reduce/ filteron Map/ Setcoleções parece ser principalmente preocupações conceituais. Cada tipo de coleção em Javascript deve realmente especificar seus próprios métodos iterativos apenas para permitir isso

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

ao invés de

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

Uma alternativa era especificar mapear / reduzir / filtrar como parte do protocolo iterável / iterador, já que entries/ values/ keysreturn Iterators. É concebível, porém, que nem todo iterável também seja "mapeável". Outra alternativa era especificar um "protocolo de coleta" separado para esse objetivo.

No entanto, não conheço a discussão atual sobre este tópico no ES.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.