添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

Is there any way to map / reduce / filter /etc a Set in JavaScript or will I have to write my own?

Here's some sensible Set.prototype extensions

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 take a little set

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

And some stupid little functions

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

And see how they work

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
  

Isn't that nice ? Yeah, I think so too. Compare that to the ugly iterator usage

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

Is there any better way to accomplish map and reduce using a Set in JavaScript?

Well, consider var s = new Set([1,2,3,4]); s.map((a) => 42);. It changes the number of elements, which map typically isn't supposed to do. Even worse if you're only comparing parts of the kept objects, because then technically it's unspecified which one you'll get. – Bartek Banachewicz Oct 20, 2015 at 10:55 I had considered that, but I'm not sure I (personally) would consider that invalid. OK so at least forEach exists for that scenario, but why no reduce then ? – Mulan Oct 20, 2015 at 10:58

A short-hand way to do it is to convert it to an array via the ES6 spread operator.

Then all the array functions are available to you.

const mySet = new Set([1,2,3,4]);
[...mySet].reduce(...);
                Because the functions are not available for Set! This is a complete, guided and understood workaround that is not as yet present in this topic. The fact it 'takes longer' is a sad price to pay for a workaround until Set implements these features!
– ZephDavies
                Mar 7, 2017 at 15:37
                For me at least, the difference between this and Array.from is that Array.from works with TypeScript. Using [...mySet] gives the error: TS2461: Type 'Set<number>' is not an array type.
– Mikal Madsen
                Jul 14, 2017 at 13:18
                For spread vs Array.from(), see stackoverflow.com/a/40549565/5516454  Basically, both are usable here. Array.from() can additionally do array-like objects which do not implement the @@iterator method.
– ZephDavies
                Jan 24, 2018 at 11:23
                In V8 at least, another difference is that [...mySet] will fail with stack overflow for large Sets. Array.from() does not use stack memory per element, so it is less risky when the number of elements may be large.
– peterflynn
                May 25, 2022 at 21:20

To sum up the discussion from comments: while there are no technical reasons for set to not have reduce, it's not currently provided and we can only hope it changes in ES7.

As for map, calling it alone could violate the Set constraint, so its presence here might be debatable.

Consider mapping with a function (a) => 42 - it will change the set's size to 1, and this might or might not be what you wanted.

If you're ok with violating that because e.g. you're going to fold anyway, you can apply the map part on every element just before passing them to reduce, thus accepting that the intermediate collection (which isn't a Set at this point) that's going to be reduced might have duplicated elements. This is essentially equivalent to converting to Array to do processing.

This is mostly good, except (using the code above), s.map(a => 42) will result in Set { 42 } so the mapped result will be a different length but there will not be "duplicated" elements. Maybe update the wording and I'll accept this answer. – Mulan Oct 20, 2015 at 11:19 @naomik Oh derp I was just finishing my first coffee when writing that. On the second look, the intermediate collection passed to reduce might have immediate elements if you accept it's not a set - that's I meant. – Bartek Banachewicz Oct 20, 2015 at 11:27 Oh I get it - map has to map to the same type, hence possible collisions in the destination set. When I found this question I was thinking map would map to an array from a set. (as if you did set.toArray().map()` – Simon_Weaver Jan 4, 2019 at 5:24 In Scala and Haskell, sets support a map operation - it can reduce the number of elements in the set. – Velizar Hristov Jan 21, 2019 at 17:08

The cause of the lack of map/reduce/filter on Map/Set collections seem to be mainly conceptual concerns. Should each collection type in Javascript actually specify its own iterative methods only to allow this

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

instead of

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

An alternative were to specify map/reduce/filter as part of the iterable/iterator protocol, since entries/values/keys return Iterators. It is conceivable though that not every iterable is also "mappable". Another alternative were to specify a separate "collection protocol" for this very purpose.

However, I do not know the current discussion on this topic at ES.

Should each collection type in Javascript actually specify its own iterative methods only to allow this? Yes. All that Array.from with new Set is a workaround and is much less readable than myArray.filter(isBiggerThan6Predicate); Now I have to write my own filterSet function, so I can write clean code: filterSet(setWithNumbers, biggerThan6Predicate); – KulaGGin Apr 7, 2022 at 12:18 How is it about performance? I could imagine that new Set(Array.from(mySet...)) has a lot of overhead compared to an own iterative method. – Cadoiz Mar 14 at 7:33

Thought this was worth mentioning in terms of speed, if you are deciding between the Set Methods forEach() which invokes a callback for each element, or values() which returns an iterator with all the values in a Set.

As an example lets filer out the even numbers from a Set:

const generateSet = (n, m) => {
  // Generate list of length n with random numbers between 0 and m
  let arr = Array.from({ length: n }, () =>
    Math.floor(Math.random() * m)
  // Convert to Set
  var set = new Set(arr);
  return set;

Our two filter functions:

const filterValues = (set) => {
  // Using Iterator
  const it = set.values();
  let result = it.next();
  while (!result.done) {
    if (result.value % 2 === 0) {
      set.delete(result.value);
    result = it.next();
const filterForEach = (set) => {
  // invokes a callback
  set.forEach((item) => {
    if (item % 2 === 0) {
      set.delete(item);

For timing we use Timing these on random array including numbers from range [0, 10,000,000] containing 5,000,000 items:

let setValues = generateSet(5000000, 10000000);
console.time("Filter with values()");
filterValues(setValues);
console.timeEnd("Filter with values()");
let setForEach = generateSet(5000000, 10000000);
console.time("Filter with forEach()");
filterForEach(setForEach);
console.timeEnd("Filter with forEach()");

Results are:

Filter with values(): 399.456ms
Filter with forEach(): 374.698ms

Or you could just stick with the Array method:

const arrMethod = (set) => {
  // Using Array method
  const filter = [...set].filter((item) => item % 2 === 0);
  return filter;
let setArray = generateSet(5000000, 10000000);
console.time("Filter with array");
filterForEach(setArray);
console.timeEnd("Filter with array");

It seems consistently faster...

Filter with values(): 356.486ms
Filter with forEach(): 386.825ms
Filter with array: 342.358ms
                Hi there, sorry but I really need to caution – no, forbid – against doing this. Converting to and from an array is common, but Set makes no guarantees that the ordering will be preserved. In your example here it happened to be the same, but in general that is not true. A very easy way to break your code looks like this:  const set = new Set([1, 1, 2]); filterSet(2); // what does this even mean now?
– colinta
                Dec 19, 2022 at 17:07
        

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid …

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.