209

The old school way of adding all values of an array into the Set is:

// for the sake of this example imagine this set was created somewhere else 
// and I cannot construct a new one out of an array
let mySet = new Set()

for(let item of array) {
  mySet.add(item)
}

Is there a more elegant way of doing this? Maybe mySet.add(array) or mySet.add(...array)?

PS: I know both do not work

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Fuzzyma
  • 7,619
  • 6
  • 28
  • 60
  • 26
    There is a stage 1 proposal for [`Set.prototype.addAll`](https://tc39.github.io/proposal-collection-methods/#Set.prototype.add-elements) – Yury Tarabanko Jun 15 '18 at 19:17
  • 1
    @YuryTarabanko Just a bit weird why it wasn't included in `Set#add` in the first place. Somebody must have thought `Array#push` taking multiple arguments is a bad thing. – Robert Apr 01 '20 at 12:33
  • 1
    Can't you use the set constructor? See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set under Relation with Array objects. – TrueWill May 18 '20 at 17:45
  • 3
    ah , when we have arr.push(...items) why have set.add(value) : / . I tripped on this recently. Poor API design – Sainath S.R Apr 02 '21 at 17:12

11 Answers11

208

While Set API is still very minimalistic, you can use Array.prototype.forEach and shorten your code a bit:

array.forEach(item => mySet.add(item))

// alternative, without anonymous arrow function
array.forEach(mySet.add, mySet)
amankkg
  • 4,503
  • 1
  • 19
  • 30
  • 1
    added a version without arrow function creation – amankkg Dec 05 '19 at 18:11
  • 8
    I don't think this is really any better than just a `for of` loop. The second one especially is much more confusing. – Timmmm May 15 '20 at 13:50
  • 131
    I like how *poor API* was called _minimalistic_! – ruX Jul 19 '20 at 18:58
  • 3
    Why do we need the second argument in: array.forEach(mySet.add, mySet) ? – ed22 Oct 19 '20 at 13:02
  • 6
    @ed22 see [forEach docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach): `thisArg` (optional) - value to use as `this` when executing _callback_ – amankkg Oct 20 '20 at 16:06
  • @amankkg so without passing thisArg, the "add" method would be executed in some other context than mySet? – ed22 Oct 20 '20 at 22:24
  • @ed22 yeah, the context will be undefined – amankkg Oct 25 '20 at 08:11
  • 4
    @amankkg I will never get used to the fact that mySet.add does not use the context of the object before the dot but is like a completely detached method. – ed22 Oct 26 '20 at 10:40
  • @RyanShillington not true, it used to be but browsers have optimised a lot since that answer was posted. see https://jsben.ch/fxaYy (credit goes to @Redu) (originally posted [here](https://stackoverflow.com/questions/50881453/how-to-add-an-array-of-values-to-a-set#comment114865000_58996448) ) – AncientSwordRage Jan 30 '22 at 09:46
  • @ed22 thanks for this clarification. so even if longer in terms of characters, the syntax `array.forEach(Set.prototype.add, mySet)` looks clearer to me. – Jean Paul Jun 12 '22 at 10:18
  • @ed22 Almost all functions do that, the only ones that don't are functions that you have already added a thisArg to using e.g. `Function::bind` or arrow functions (which inherit `this` from its parent context). This allows easy inheritance using prototypes, and allows 'borrowing' methods from other classes by assigning them as a property of an instance (or even the instance's shared prototype). Sorry for the rant. – PoolloverNathan Nov 21 '22 at 18:02
  • The version without the arrow function is terrible advice. See https://jakearchibald.com/2021/function-callback-risks/ This is exactly the reason why `Set.add()` can now never be improved to take multiple parameters. The callback in `Array.forEach()` is fired with multiple arguments, so if a browser wants to ship with functionality like `mySet.add(item1, item2)` they will break existing sites. – Jespertheend Aug 16 '23 at 11:28
  • @Jespertheend there are no user-defined functions above, otherwise one has to be cautious when using point-free style in JS – amankkg Aug 23 '23 at 16:31
  • @amankkg It's true for web platform features as well. The article uses `requestAnimationFrame` as an example, but historically other functions have gained new parameters too. Either way, not using an arrow function sets a bad example and teaches people to use it in other places where it might do more harm. – Jespertheend Aug 23 '23 at 19:10
108

Here's a functional way, returning a new set:

const set = new Set(['a', 'b', 'c'])
const arr = ['d', 'e', 'f']
const extendedSet = new Set([ ...set, ...arr ])
// Set { 'a', 'b', 'c', 'd', 'e', 'f' }
Julien
  • 5,243
  • 4
  • 34
  • 35
42

This is IMO the most elegant

// for a new Set 
const x = new Set([1,2,3,4]);

// for an existing Set
const y = new Set();

[1,2,3,4].forEach(y.add, y);
WiR3D
  • 1,465
  • 20
  • 23
15

How about using the spread operator to easily blend your new array items into an existing set?

let mySet = new Set([1,2,3,4])
const additionalSet = [5,6,7,8,9]
mySet = new Set([...mySet, ...additionalSet])

[JSFIDDLE][1] [1]: https://jsfiddle.net/clayperez/yjkxh9d8/9/

Richrd
  • 6,722
  • 1
  • 17
  • 12
clayperez
  • 807
  • 1
  • 9
  • 18
10

You can also use Array.reduce():

const mySet = new Set();
mySet.add(42); // Just to illustrate that an existing Set is used

[1, 2, 3].reduce((s, e) => s.add(e), mySet);
JHH
  • 8,567
  • 8
  • 47
  • 91
  • 38
    Using `reduce` doesn't make sense here when all you need is the iteration of `forEach`. See @WiR3D's answer. – Paul Irish Dec 03 '19 at 19:58
  • 2
    I don't really get your objection. Sometimes you want a single-statement solution, like in a lambda where you don't want a curly brace block, or in an initialization. While `reduce` may not be the most obvious choice, I fail to see how it doesn't "make sense" - you're reducing an array into something else - a Set - which is exactly what `reduce` is intended for. – JHH Dec 10 '20 at 12:16
  • I'd say you're mapping it, not reducing it here. – Sean Morris Apr 09 '21 at 08:19
  • 2
    @JHH The point is that `[1, 2, 3].forEach(e => mySet.add(e))` or `for (const e of [1, 2, 3]) mySet.add(e)` would make more sense. – CherryDT Sep 07 '21 at 07:28
  • 1
    @CherryDT I think we have to agree to disagree on what making sense means. I wouldn't personally choose the reduce solution, but I added it as an option. Surprised 19 people thinks it doesn't "make sense". – JHH Sep 15 '21 at 14:27
  • @JHH that's because this is using reduce incorrectly. It shouldn't be used for side effects, that's what forEach is for. – Alexis Tyler Dec 03 '21 at 10:19
  • 4
    foreach is not an expression, on the other hand reduce is, so you can both return and reduce in a single shot. – mindlid Jul 23 '22 at 07:13
  • 1
    Thanks! "Reduce" faster for me in Chrome: https://jsben.ch/UmpYA – andronick83 Jun 22 '23 at 23:18
4

create a new Set:

    //Existing Set
    let mySet = new Set([1,2,3,4,5]);
    //Existing Array
    let array = [6,7,8,9,0];
        
    mySet = new Set(array.concat([...mySet]));
    console.log([...mySet]);
    
    //or single line
    console.log([...new Set([6,7,8,9,0].concat([...new Set([1,2,3,4,5])]))]);
montelof
  • 491
  • 1
  • 6
  • 13
  • Is there an advantage in this combination of spread and concat? I think Julien's answer is more comprehensible. – Robert Mar 29 '20 at 13:55
  • @Robert the array already exists, in my example im creating the new set from the existing array + existing Set, Julien example he creates a new array from the other two existing object to create a fourth object thats not a good practice. – montelof Apr 01 '20 at 01:28
  • `Array#concat` also creates a new array. Your solutions express the same thing with different code, but surely it's always good to have a choice. (`array.concat([...set])` vs `[...array, ...set]`) – Robert Apr 01 '20 at 12:15
3

Just post that here for inspiration .. Creating a class that extends Set, and add a addRange method.

    class MegaSet extends Set {
    
      constructor(iterable) {
       super(iterable);
      }
      
      addRange(range) {
        for (var elem of range) {
          this.add(elem);
        }
      }
    }
    
    const array = [1,2,3,5,5,6];
    let mySet = new MegaSet([1,2,3,4]);
    
    mySet.addRange(array);
    console.log([...mySet]);
0

Input the array in the first place, when you create the set. You'll get the set with all items in the array.

const array = [1,2,3,4,5]
let mySet = new Set(array)
console.log(mySet)
//Add new one element
mySet.add(6)
console.log(mySet)

//Add exist element
mySet.add(6)
console.log(mySet)
Maxim Zakopaylov
  • 546
  • 2
  • 5
  • 23
Steve Hsu
  • 51
  • 1
  • 1
  • 8
0
let mySet = new Set(['a', 'b']);
let arrayToBeAdded = ['c', 'd'];

//convert the Set to an array, then concatenate both arrays, finally convert into a Set
mySet = Array.form(mySet).concat(arrayToBeAdded);
mySet = new Set(mySet);

//in single line
mySet = new Set(Array.form(mySet).concat(arrayToBeAdded));
Asanga Ranasinghe
  • 72
  • 1
  • 1
  • 11
-1

There's currently no addAll method for Sets, but you have two options to simplify your life when working with them. The first one would be to extend the prototype. Before you do that, read this post and decide afterwards if the possible consequences are ok for your project/intended use.

if (!Set.prototype.addAll) {
  Set.prototype.addAll = function(items) {
    if (!Array.isArray(items)) throw new TypeError('passed item is not an array');
    // or (not sure what the real Set.prototype will get sometime)
    // if (!Array.isArray(items)) items = [items];
    for (let it of items) {
      this.add(it);
    }
    return this;
  }
}

If you decided not to extend the prototype, just create a function that you can reuse in your project

function addAll(_set, items) {
    // check set and items
    for (let it of items) {
         _set.add(it);
    }
    return _set;
}
baao
  • 71,625
  • 17
  • 143
  • 203
-4

@Fuzzyma, I'll suggest you to use Prototyping of JavaScript to define new method on Set.

Do not use in-built method name defined on Set.

If you still prefer to use the same function name as in-built function name like add then the better approach would be to inherit the Set and override add() method.

This is better way to add methods to existing objects without affecting their methods and use our own methods with same name. The charisma of Method overriding, a nice OOP concept.

Here in the below code, I have defined addItems() on Set.

Try it online at http://rextester.com/LGPQC98007.

var arr = [3, 7, 8, 75, 65, 32, 98, 32, 3];
var array = [100, 3, 200, 98, 65, 300]; 

// Create a Set
var mySet = new Set(arr);
console.log(mySet);

// Adding items of array to mySet
Set.prototype.addItems = function(array) {
    for(var item of array){
        this.add(item)
    }
}

mySet.addItems(array);
console.log(mySet)

» Output

Set { 3, 7, 8, 75, 65, 32, 98 }
Set { 3, 7, 8, 75, 65, 32, 98, 100, 200, 300 }
hygull
  • 8,464
  • 2
  • 43
  • 52
  • Obvisouly you didnt read the comment in the code where I stated that the set is created somewhere else and I **cannot** create a new one – Fuzzyma Jun 15 '18 at 19:20
  • Okay got. Mean to say you already have a **Set** created and you want to add all values of any to that? Let me update my code and let you know. Thanks for reminding my mistake. – hygull Jun 15 '18 at 19:25
  • **@Fuzzyma**, I think you should define one method on **Set** using prototyping feature of JavaScript. Make sure to not use the in-built method names defined on **Set**. I have updated my answer with little example. Please check it let me know your suggestion. – hygull Jun 15 '18 at 19:51
  • 3
    You might want to take a look at [Why is extending native objects a bad practice?](https://stackoverflow.com/q/14034180). Also, your answer (and comments) would be more readable if you used less formatting. Finally, Stack Overflow has a feature called Stack Snippets. In the editor you can click the button with angle brackets (`<>`) and paste your JavaScript code into it. That will make it runnable for others. – Heretic Monkey Jun 15 '18 at 20:13
  • **@Mike**, I have already mentioned this in my code even I have not used it. Actually I am using mobile phone so I'm unable to see JS editor here. Thanks. – hygull Jun 15 '18 at 20:18