34

Trying to get two data sets to intersect but I can't do it. For example, in my code below, intersecting mySet and mySet2 should yield "1" since they both have a value of "1" in their set.

var mySet = new Set(); var mySet2=new Set(); mySet.add(1); mySet.add(2); mySet.add("HELLOOOOO"); mySet2.add("hi"); mySet2.add(1); var a = Array(mySet, mySet2); console.log(a); mySet.forEach(function(value) { console.log(value); }); mySet2.forEach(function(value) { console.log(value); }); function intersection_destructive(a, b) { var result = new Array(); while( mySet.length > 0 && mySet2.length > 0 ) { if (mySet[0] < mySet2[0] ){ mySet.shift(); } else if (mySet[0] > mySet2[0] ){ mySet2.shift(); } else /* they're equal */ { result.push(mySet.shift()); mySet2.shift(); } } return result; } 

Set 1 and Set 2 both have "1" in it but my function (intersection_destructive) doesn't return it. I'm not sure how to intersect them, I searched stackoverflow and found intersection_destructive but it didn't work for me, I also tried:

array1.filter(function(n) { return array2.indexOf(n) != -1 }); 

as per this: Simplest code for array intersection in javascript

but I get an error on filter when I try to run it.

4
  • 3
    Uh, Sets don't have shift, push and filter methods? You're using code for arrays. Commented Aug 10, 2015 at 23:49
  • Possibly relevant: stackoverflow.com/questions/31128855/… Commented Aug 11, 2015 at 7:33
  • Instead of set, maybe if we keep the arrays sorted, we could do it more efficiently. Commented May 12, 2022 at 16:52
  • See also stackoverflow.com/questions/1885557/… . Commented Jul 21, 2022 at 10:17

11 Answers 11

59

Sadly as you've figured out there are no native intersection or union operations. It's not terribly complex to find the intersection though:

let a = new Set([1,2,3]) let b = new Set([1,2,4]) let intersect = new Set([...a].filter(i => b.has(i))); console.log(...intersect)

Sign up to request clarification or add additional context in comments.

2 Comments

linear n for this. since it's still gonna do iteration
making set a is pointless, since you iterate over it anyway
14

As of June 2024, Set has been added to the spec: you can use:

mySet.intersection(mySet2); 

const set1 = new Set([1, 2, 3]); const set2 = new Set([1, 2, 4]); const intersect = set1.intersection(set2); console.log(...intersect);
<script src="https://unpkg.com/[email protected]/minified.js"></script>

It is live in Safari 17 and Chrome 122 and SerenityOS, and also supported in Node 22.

For other engines and backwards compatibility, you could use Immutable.js's Set, which inspired that proposal:

Immutable.Set(mySet).intersect(mySet2); 

Comments

8

To get the intersection, you can iterate the items of a set and check if they belong to the other one:

var intersect = new Set(); for(var x of mySet1) if(mySet2.has(x)) intersect.add(x); 

In ES7 you can simplify it with array comprehensions or generator comprehensions:

var intersect = new Set((for (x of mySet1) if (mySet2.has(x)) x)); 

7 Comments

I get red error underlines between "x" and "of" and "mySet1" and ")" after mySet1 and "])", I'm using WebStorm to run these javascripts, not sure if that has an impact on the result or not.
@Snorlax Sorry that was non-standard JS1.7 syntax. I have included how to do it in ES6 and the proposed ES7 syntax.
I'd really love if those comprehensions would create an iterator, not an array.
@Bergi Like generator comprehensions? Would it perform better?
@JacobIRR because generator comprehensions were not accepted into the standard.
|
4

You have been trying array intersections methods. You cannot use them on ES6 sets. Instead, use

function intersect(...sets) { if (!sets.length) return new Set(); const i = sets.reduce((m, s, i) => s.size < sets[m].size ? i : m, 0); const [smallest] = sets.splice(i, 1); const res = new Set(); for (let val of smallest) if (sets.every(s => s.has(val))) res.add(val); return res; } 

2 Comments

Is finding the smallest worth it? It will have less values to iterate, but the lookups in the other ones might be slower.
@Oriol: I think so, because it's less lookups. If we have two sets of sizes m and n, then even for logarithmic access m<<n => m * log(n) < n * log(m) (as per the spec, has is sublinear). Of course, for raw speed I'd probably have to inline that every.
4

One technique for intersecting sets is to take the smallest set and check for each of its values in the other sets, removing the value if any set does not contain it. Although runtime is O(n x m), where n is the set length and m is the number of sets, the n is likely to be much smaller if the sets are not all the same length.

function setsIntersection(...sets) { if (sets.length < 1) { return new Set(); } let min_size = sets[0].size; let min_set_index = 0; for (let i = 1; i < sets.length; i++) { const size = sets[i].size; if (size < min_size) { min_size = size; min_set_index = i; } } const temp_swap_set = sets[0]; sets[0] = sets[min_set_index]; sets[min_set_index] = temp_swap_set; const result = new Set(sets[min_set_index]); for (let i = 1; i < sets.length; i++) { for (const v of result) { if (!sets[i].has(v)) { result.delete(v); } } } return result; } 

3 Comments

This seems to work by accident, as the second loop would skip the first element and stop at the first smallest cardinality set, but since the first loop incorrectly uses the length property on the sets (instead of size), this doesn't matter as the first loop essentially does nothing and we always use the first set.
Good catch, but this will fail because the first set is skipped for comparison (e.g. {1, 100, 200}, {1, 2, 3}, {1, 2}). We can fix this by swapping the first set with the smallest one.
Ah right, I meant to also change the loop start to zero, but this works as well.
3

You could iterate the set and create a new set for the common values.

const intersectSets = (a, b) => { const c = new Set; a.forEach(v => b.has(v) && c.add(v)); return c; }, a = new Set([1, 2, 3]), b = new Set([1, 2, 4]), c = new Set([1, 2, 4, 5]), n = new Set([1, 2]); console.log(...[a, b, c, n].reduce(intersectSets));

Comments

2

This will work on both Sets and Arrays. It will return whatever the type of set1 is. Arguments can be mixed types.

/** * Returns the intersection of ES6 Set objects. i.e., returns a new set with only elements contained in all the given sets. * * @param {Set|Array} set1 First set * @param {Array<Array|Set>} sets Other sets * @returns {Set|Array} Intersection */ export function intersection(set1, ...sets) { if(!sets.length) { return set1; } const tmp = [...set1].filter(x => sets.every(y => Array.isArray(y) ? y.includes(x) : y.has(x))); return Array.isArray(set1) ? tmp : new Set(tmp); } 

I built it for working with Sets, but then I realized it's probably more expensive to convert an array into a set just so I can use .has on it once than to just use .includes.

1 Comment

It seems to need to remove spread from param → intersection(set1, sets)
1

From the example: intersection_destructive takes 2 ARRAYS not 2 SETS. Here is your code working with the intersection_destructive example.

// These must be sorted arrays! var mySet = [1,2,6]; var mySet2 = [1,2,5]; // Takes 2 Arrays // array properties: shift function intersection_destructive(a, b) { var result = []; while( a.length > 0 && b.length > 0 ) { if (a[0] < b[0] ){ b.shift(); } else if (a[0] > b[0] ){ b.shift(); } else /* they're equal */ { result.push(a.shift()); b.shift(); } } return result; }; var result = intersection_destructive(mySet, mySet2); console.log(result); // Output: [1,2] 

4 Comments

Thanks, worked perfectly. But something's bothering me, why is it that when I do this: var mySet = new Set(); var mySet2=new Set(); mySet.add(1); mySet.add(2); mySet.add("HELLOOOOO"); mySet2.add("hi"); mySet2.add(1); , the result is empty but when I declare the sets as you did with the array loaded, it returns 1?
Also, the code doesn't print out any similar values after 1, if I put 6 in both sets, it ignores it.
the 'Set' object does not the 'shift()' method, which is probably why it is returning nothing. For the second question: if you change the arrays to [1,2,5] and [1,2,6] - result will = [1,2]. Per the example, the arrays need to be sorted. If you are adding 6 to the end of the array, you will receive [1] instead of the expected values.
@Snorlax Please see the recent edit and try running the code locally.
1

Intersection of two arrays

function intersection(arr1, arr2) { const set2 = new Set(arr2) const com = arr1.filter(a => set2.has(a)) return [...new Set(com)] } console.log(intersection([1, 2, 3], [1, 2, 4])) // [ 1, 2 ] console.log(intersection([1, 2, 4], [2, 2, 4, 4])) // [ 2, 4 ]

Comments

0

To use the Array.filter() is a useful pattern. You need to convert your sets to array's using Array.from(). Also make sure that you filter through smaller set into the bigger one. see fiddle

var mySet = new Set(); var mySet2=new Set(); mySet.add(1); mySet.add(2); mySet.add("HELLOOOOO"); mySet2.add("hi"); mySet2.add(1); console.log(intersection_destructive(mySet, mySet2)); function intersection_destructive(a, b) { // filter over the shorter set and convert sets to Array's so we have filter if (a.length > b.length) { var array1 = Array.from(a); var array2 = Array.from(b); } else { var array1 = Array.from(b); var array2 = Array.from(a); } var result = array1.filter(function(n) { return array2.indexOf(n) != -1 }); return result; } 

Comments

0

Micro-optimizers might find it worth noting that, given small-enough input sets, intersections can be computed without loops.

 const BANANA = 1 const LEMON = 2 const APPLE = 4 const CARROT = 8 const CORN = 16 const fruits = BANANA | LEMON | APPLE const yellowFoods = BANANA | LEMON | CORN const intersection = fruits & yellowFoods console.log('BANANA in set:', Boolean(BANANA & intersection)) console.log('LEMON in set:', Boolean(LEMON & intersection)) console.log('APPLE in set:', Boolean(APPLE & intersection)) console.log('CARROT in set:', Boolean(CARROT & intersection)) console.log('CORN in set:', Boolean(CORN & intersection)) 

This works because, when using powers of two, bitwise OR and XOR become equivalent to set union and intersection operations, respectively.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.