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.