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?
–
–
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(...);
–
–
–
–
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.
–
–
–
–
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.
–
–
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
–
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.