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


131

Чи є спосіб map/ reduce/ filter/ тощо 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використовувати SetJavaScript в JavaScript?


Проблема зменшення карти 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

Відповіді:


105

Короткий спосіб зробити це - перетворити його в масив через оператор розповсюдження ES6.

Тоді вам доступні всі функції масиву.

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

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, таким чином, приймаючи, що проміжна колекція ( яка в даний момент не є набором ) це скорочення може мати дублюючі елементи. Це по суті еквівалентно перетворенню в масив для обробки.


1
Це здебільшого добре, за винятком (використовуючи код вище), s.map(a => 42)це призведе до Set { 42 }того, що відображений результат буде різної довжини, але не буде "дублюються" елементів. Можливо, оновіть формулювання, і я прийму цю відповідь.
Дякую

@naomik О дерпе, я просто закінчував свою першу каву, коли писав це. По-друге, проміжний збірник, який передається для зменшення, може мати негайні елементи, якщо ви приймаєте, що це не набір - це я мав на увазі.
Bartek Banachewicz

О, я розумію - карта має відповідати одному типу, отже, можливі зіткнення в наборі пункту призначення. Коли я знайшов це питання, я думав, що карта буде відображати масив із набору. (як ніби ви зробили set.toArray (). map () `
Simon_Weaver

2
У програмах Scala та Haskell набори підтримують операцію з картою - це може зменшити кількість елементів у наборі.
Велізар Христов

8

Причина відсутності колекції 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]));

Альтернативою було вказати карту / зменшити / фільтр як частину ітерабельного / ітераторного протоколу, оскільки entries/ values/ keysreturn Iterators. Можливо, що не кожен ітерабельний також можна «відобразити». Іншою альтернативою було вказати окремий "протокол збору" саме для цієї мети.

Однак я не знаю нинішньої дискусії на цю тему в ES.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.