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

134

Есть ли способ 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?

Спасибо
источник
Проблема с map-reduce-ing a в Setтом, что наборы не являются функторами.
Bartek Banachewicz
@BartekBanachewicz да, это проблема ... да?
Спасибо
2
Хорошо, посмотрим var s = new Set([1,2,3,4]); s.map((a) => 42);. Он изменяет количество элементов, чего mapобычно не должно быть. Еще хуже, если вы сравниваете только части сохраненных объектов, потому что тогда технически не указано, какой из них вы получите.
Bartek Banachewicz
Я думал об этом, но не уверен, что (лично) считаю это недействительным. Хорошо, по крайней мере, forEachсуществует для этого сценария, но почему reduceтогда нет ?
Спасибо
4
Некоторые материалы по теме: esdiscuss.org/topic/set-some-every-reduce-filter-map-methods
CodingIntrigue

Ответы:

106

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

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

const mySet = new Set([1,2,3,4]);
[...mySet].reduce()
ZephDavies
источник
1
Поскольку функции недоступны для Set! Это полный, управляемый и понятный обходной путь, которого пока нет в этой теме. Тот факт, что «требуется больше времени», - это печальная цена, которую нужно заплатить за обходной путь, пока Set не реализует эти функции!
ZephDavies
1
В чем разница между этим и Array.from
Pete
9
По крайней мере, для меня разница между этим и в Array.fromтом, что Array.fromработает с TypeScript. Использование [...mySet]дает ошибку:TS2461: Type 'Set<number>' is not an array type.
Mikal Madsen
1
Для распространения по сравнению с Array.from () см. Stackoverflow.com/a/40549565/5516454 В принципе, здесь можно использовать оба. Array.from () может дополнительно создавать объекты, похожие на массивы, которые не реализуют @@iteratorметод.
ZephDavies
у меня все еще не работает с машинописным текстом. Я получаюERROR TypeError: this.sausages.slice is not a function
Simon_Weaver
22

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

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

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

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

Бартек Баначевич
источник
1
Это в основном хорошо, за исключением (используя код выше), s.map(a => 42)приведет к Set { 42 }таким отображенный результат будет разной длины , но не будет «дублируются» элементы. Возможно, обновите формулировку, и я приму этот ответ.
Спасибо
@naomik О, черт, я как раз допивал свой первый кофе, когда писал это. На второй взгляд, промежуточная коллекция, переданная в сокращение, может иметь непосредственные элементы, если вы примете это не как набор - я имел в виду.
Bartek Banachewicz
О, я понял - карта должна соответствовать одному и тому же типу, следовательно, возможны коллизии в целевом наборе. Когда я нашел этот вопрос, я подумал, что карта будет отображаться в массив из набора. (как будто вы сделали set.toArray (). map () `
Simon_Weaver
2
В Scala и Haskell наборы поддерживают операцию сопоставления - это может уменьшить количество элементов в наборе.
Велизар Христов
8

Причина отсутствия коллекций map/ reduce/ filteron 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 как часть протокола итерации / итератора, поскольку entries/ values/ keysreturn Iterators. Вполне возможно, однако, что не все итерации также могут быть «отображены». Другой альтернативой было определение отдельного «протокола сбора» именно для этой цели.

Однако я не знаю, как сейчас обсуждают эту тему на ES.


источник