Как отображать/уменьшать/фильтровать набор в JavaScript?

Есть ли способ map/reduce/filter/etc a Set в JavaScript или мне нужно написать свой собственный?

Вот некоторые разумные расширения Set.prototype

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

Возьмем небольшой набор

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

И некоторые глупые маленькие функции

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

И посмотрите, как они работают.

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

Разве это не приятно? Да, я тоже так думаю. Сравните это с уродливым использованием итератора

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

и

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

Есть ли лучший способ выполнить map и reduce с помощью Set в JavaScript?

Ответ 1

Краткий способ сделать это - преобразовать его в массив с помощью оператора распространения ES6.

Тогда все функции массива доступны вам.

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

Ответ 2

Подводя итог обсуждению с комментариями: пока нет технических причин для установки не иметь reduce, в настоящее время он не предоставлен, и мы можем только надеяться, что он изменится в ES7.

Как и для map, вызов одного из них может нарушить ограничение Set, поэтому его присутствие здесь может быть спорным.

Рассмотрим сопоставление с функцией (a) => 42 - он изменит размер набора на 1, и это может быть или не быть тем, что вы хотели.

Если вы согласны с нарушением этого, потому что, например, вы все равно сложите, вы можете применить часть map на каждом элементе непосредственно перед передачей их в reduce, тем самым принимая промежуточную коллекцию (, которая не является Set в этой точке), которые могут быть уменьшены, могут иметь дублированные элементы. Это по существу эквивалентно преобразованию в Array для обработки.

Ответ 3

Причина отсутствия коллекций map/reduce/filter в коллекциях map/Set представляется в основном концептуальными проблемами. Если каждый тип коллекции в Javascript на самом деле указывает свои собственные итерационные методы, только для этого

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

вместо

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

Альтернативой было указать map/reduce/filter как часть протокола итератора /iterator, так как entries/values/keys return Iterator s. Разумеется, хотя не все итерабельны также "отображаемы". Другой альтернативой было указание отдельного "протокола сбора" для этой самой цели.

Однако, я не знаю текущую дискуссию по этой теме в ES.