30

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.

serv-inc
  • 35,772
  • 9
  • 166
  • 188
Snorlax
  • 4,425
  • 8
  • 37
  • 68
  • 3
    Uh, `Set`s don't have `shift`, `push` and `filter` methods? You're using code for arrays. – Bergi Aug 10 '15 at 23:49
  • Possibly relevant: http://stackoverflow.com/questions/31128855/comparing-ecma6-sets-for-equality/31129482#31129482 –  Aug 11 '15 at 07:33
  • Instead of set, maybe if we keep the arrays sorted, we could do it more efficiently. – Abhishek Choudhary May 12 '22 at 16:52
  • See also https://stackoverflow.com/questions/1885557/simplest-code-for-array-intersection-in-javascript/73064403#73064403 . – serv-inc Jul 21 '22 at 10:17

10 Answers10

46

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)
jackotonye
  • 3,537
  • 23
  • 31
Kit Sunde
  • 35,972
  • 25
  • 125
  • 179
7

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));
Oriol
  • 274,082
  • 63
  • 437
  • 513
  • 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 Aug 10 '15 at 23:52
  • 1
    @Snorlax Sorry that was non-standard JS1.7 syntax. I have included how to do it in ES6 and the proposed ES7 syntax. – Oriol Aug 10 '15 at 23:58
  • 2
    I'd really love if those comprehensions would create an iterator, not an array. – Bergi Aug 11 '15 at 00:02
  • 1
    @Bergi Like [generator comprehensions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Generator_comprehensions)? Would it perform better? – Oriol Aug 11 '15 at 00:09
  • @Oriol: Yes, that's perfect for sets :-) Omitting that intermediate array should definitely have better performance, though I cannot say anything about actual implementations. – Bergi Aug 11 '15 at 00:11
  • Why can't I run the ES7 code in the newest version of Chrome? I get `Unexpected token for` – JacobIRR Jun 06 '18 at 17:27
  • 4
    @JacobIRR because generator comprehensions were not accepted into the standard. – Patrick Roberts Mar 07 '19 at 22:00
5

With the currently Stage 3 proposal https://github.com/tc39/proposal-set-methods, you could 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/core-js-bundle@3.27.1/minified.js"></script>

Until then, you could use Immutable.js's Set, which inspired that proposal:

Immutable.Set(mySet).intersect(mySet2);
double-beep
  • 5,031
  • 17
  • 33
  • 41
serv-inc
  • 35,772
  • 9
  • 166
  • 188
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;
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Is finding the smallest worth it? It will have less values to iterate, but the lookups in the other ones might be slower. – Oriol Aug 11 '15 at 00:02
  • 1
    @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< 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`. – Bergi Aug 11 '15 at 00:06
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;
}
rovyko
  • 4,068
  • 5
  • 32
  • 44
  • 1
    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. – grddev Nov 21 '22 at 20:26
  • 1
    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. – rovyko Nov 23 '22 at 17:59
  • Ah right, I meant to also change the loop start to zero, but this works as well. – grddev Nov 24 '22 at 10:08
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));
VLAZ
  • 26,331
  • 9
  • 49
  • 67
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
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.

mpen
  • 272,448
  • 266
  • 850
  • 1,236
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]
Community
  • 1
  • 1
dannypaz
  • 1,294
  • 1
  • 12
  • 16
  • 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? – Snorlax Aug 11 '15 at 00:16
  • Also, the code doesn't print out any similar values after 1, if I put 6 in both sets, it ignores it. – Snorlax Aug 11 '15 at 00:20
  • 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. – dannypaz Aug 11 '15 at 06:13
  • @Snorlax Please see the recent edit and try running the code locally. – dannypaz Aug 11 '15 at 06:14
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;
}
Victory
  • 5,811
  • 2
  • 26
  • 45
0

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 ]
Anjan Talatam
  • 2,212
  • 1
  • 12
  • 26