comparing ECMA6 sets for equality

JavascriptSetEcmascript 6

Javascript Problem Overview


How do you compare two javascript sets? I tried using == and === but both return false.

a = new Set([1,2,3]);
b = new Set([1,3,2]);
a == b; //=> false
a === b; //=> false

These two sets are equivalent, because by definition, sets do not have order (at least not usually). I've looked at the documentation for Set on MDN and found nothing useful. Anyone know how to do this?

Javascript Solutions


Solution 1 - Javascript

Try this:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    if (as.size !== bs.size) return false;
    for (var a of as) if (!bs.has(a)) return false;
    return true;
}

A more functional approach would be:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

alert(eqSet(a, b)); // true

function eqSet(as, bs) {
    return as.size === bs.size && all(isIn(bs), as);
}

function all(pred, as) {
    for (var a of as) if (!pred(a)) return false;
    return true;
}

function isIn(as) {
    return function (a) {
        return as.has(a);
    };
}

The all function works for all iterable objects (e.g. Set and Map).

If Array.from was more widely supported then we could have implemented the all function as:

function all(pred, as) {
    return Array.from(as).every(pred);
}

Hope that helps.

Solution 2 - Javascript

You can also try:

var a = new Set([1,2,3]);
var b = new Set([1,3,2]);

let areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));

console.log(areSetsEqual(a,b)) 

Solution 3 - Javascript

lodash provides _.isEqual(), which does deep comparisons. This is very handy if you don't want to write your own. As of lodash 4, _.isEqual() properly compares Sets.

const _ = require("lodash");

let s1 = new Set([1,2,3]);
let s2 = new Set([1,2,3]);
let s3 = new Set([2,3,4]);

console.log(_.isEqual(s1, s2)); // true
console.log(_.isEqual(s1, s3)); // false

Solution 4 - Javascript

None of these solutions bring “back” the expected functionality to a data structure such as set of sets. In its current state, the Javascript Set is useless for this purpose because the superset will contain duplicate subsets, which Javascript wrongly sees as distinct. The only solution I can think of is converting each subset to Array, sorting it and then encoding as String (for example JSON).

Solution
var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));
Basic usage

var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var fromJsonSet = jset => new Set(JSON.parse(jset));

var [s1,s2] = [new Set([1,2,3]), new Set([3,2,1])];
var [js1,js2] = [toJsonSet([1,2,3]), toJsonSet([3,2,1])]; // even better

var r = document.querySelectorAll("td:nth-child(2)");
r[0].innerHTML = (toJsonSet(s1) === toJsonSet(s2)); // true
r[1].innerHTML = (toJsonSet(s1) == toJsonSet(s2)); // true, too
r[2].innerHTML = (js1 === js2); // true
r[3].innerHTML = (js1 == js2); // true, too

// Make it normal Set:
console.log(fromJsonSet(js1), fromJsonSet(js2)); // type is Set

<style>td:nth-child(2) {color: red;}</style>

<table>
<tr><td>toJsonSet(s1) === toJsonSet(s2)</td><td>...</td></tr>
<tr><td>toJsonSet(s1) == toJsonSet(s2)</td><td>...</td></tr>
<tr><td>js1 === js2</td><td>...</td></tr>
<tr><td>js1 == js2</td><td>...</td></tr>
</table>

Ultimate test: set of sets

var toSet = arr => new Set(arr);
var toJsonSet = aset /* array or set */ => JSON.stringify([...new Set(aset)].sort()); 
var toJsonSet_WRONG = set => JSON.stringify([...set]); // no sorting!

var output = document.getElementsByTagName("code"); 
var superarray = [[1,2,3],[1,2,3],[3,2,1],[3,6,2],[4,5,6]];
var superset;

Experiment1:
    superset = toSet(superarray.map(toSet));
    output[0].innerHTML = superset.size; // incorrect: 5 unique subsets
Experiment2:
    superset = toSet([...superset].map(toJsonSet_WRONG));
    output[1].innerHTML = superset.size; // incorrect: 4 unique subsets
Experiment3:
    superset = toSet([...superset].map(toJsonSet));
    output[2].innerHTML = superset.size; // 3 unique subsets
Experiment4:
    superset = toSet(superarray.map(toJsonSet));
    output[3].innerHTML = superset.size; // 3 unique subsets

code {border: 1px solid #88f; background-color: #ddf; padding: 0 0.5em;}

<h3>Experiment 1</h3><p>Superset contains 3 unique subsets but Javascript sees <code>...</code>.<br>Let’s fix this... I’ll encode each subset as a string.</p>
<h3>Experiment 2</h3><p>Now Javascript sees <code>...</code> unique subsets.<br>Better! But still not perfect.<br>That’s because we didn’t sort each subset.<br>Let’s sort it out...</p>
<h3>Experiment 3</h3><p>Now Javascript sees <code>...</code> unique subsets. At long last!<br>Let’s try everything again from the beginning.</p>
<h3>Experiment 4</h3><p>Superset contains 3 unique subsets and Javascript sees <code>...</code>.<br><b>Bravo!</b></p>

Solution 5 - Javascript

Maybe a little late, but I usually do the following:

const a = new Set([1,2,3]);
const b = new Set([1,3,2]);

// option 1
console.log(a.size === b.size && new Set([...a, ...b]).size === a.size)

// option 2
console.log([...a].sort().join() === [...b].sort().join())

Solution 6 - Javascript

The other answer will work fine; here is another alternative.

// Create function to check if an element is in a specified set.
function isIn(s)          { return elt => s.has(elt); }

// Check if one set contains another (all members of s2 are in s1).
function contains(s1, s2) { return [...s2] . every(isIn(s1)); }

// Set equality: a contains b, and b contains a
function eqSet(a, b)      { return contains(a, b) && contains(b, a); }

// Alternative, check size first
function eqSet(a, b)      { return a.size === b.size && contains(a, b); }
    

However, be aware that this does not do deep equality comparison. So

eqSet(Set([{ a: 1 }], Set([{ a: 1 }])

will return false. If the above two sets are to be considered equal, we need to iterate through both sets doing deep quality comparisons on each element. We stipulate the existence of a deepEqual routine. Then the logic would be

// Find a member in "s" deeply equal to some value
function findDeepEqual(s, v) { return [...s] . find(m => deepEqual(v, m)); }

// See if sets s1 and s1 are deeply equal. DESTROYS s2.
function eqSetDeep(s1, s2) {
  return [...s1] . every(a1 => {
    var m1 = findDeepEqual(s2, a1);
    if (m1) { s2.delete(m1); return true; }
  }) && !s2.size;
}

What this does: for each member of s1, look for a deeply equal member of s2. If found, delete it so it can't be used again. The two sets are deeply equal if all the elements in s1 are found in s2, and s2 is exhausted. Untested.

You may find this useful: http://www.2ality.com/2015/01/es6-set-operations.html.

Solution 7 - Javascript

If sets contains only primitive data types or object inside sets have reference equality, then there is simpler way

const isEqualSets = (set1, set2) => (set1.size === set2.size) && (set1.size === new Set([...set1, ...set2]).size);

Solution 8 - Javascript

Very slight modification based on @Aadit M Shah's answer:

/**
 * check if two sets are equal in the sense that
 * they have a matching set of values.
 *
 * @param {Set} a 
 * @param {Set} b
 * @returns {Boolean} 
 */
const areSetsEqual = (a, b) => (
        (a.size === b.size) ? 
        [...a].every( value => b.has(value) ) : false
);

If anyone else is having an issue as I did due to some quirk of the latest babel, had to add an explicit conditional here.

(Also for plural I think are is just a bit more intuitive to read aloud )

Solution 9 - Javascript

The reason why your approach returns false is because you are comparing two different objects (even if they got the same content), thus comparing two different objects (not references, but objects) always returns you falsy.

The following approach merges two sets into one and just stupidly compares the size. If it's the same, it's the same:

const a1 = [1,2,3];
const a2 = [1,3,2];
const set1 = new Set(a1);
const set2 = new Set(a2);

const compareSet = new Set([...a1, ...a2]);
const isSetEqual = compareSet.size === set2.size && compareSet.size === set1.size;
console.log(isSetEqual);

Upside: It's very simple and short. No external library only vanilla JS

Downside: It's probably going to be a slower than just iterating over the values and you need more space.

Solution 10 - Javascript

Comparing two objects with ==, ===

When using == or === operator to compare two objects, you will always get false unless those object reference the same object. For example:

var a = b = new Set([1,2,3]); // NOTE: b will become a global variable
a == b; // <-- true: a and b share the same object reference

Otherwise, == equates to false even though the object contains the same values:

var a = new Set([1,2,3]);
var b = new Set([1,2,3]);
a == b; // <-- false: a and b are not referencing the same object

You may need to consider manual comparison

In ECMAScript 6, you may convert sets to arrays beforehand so you can spot the difference between them:

function setsEqual(a,b){
    if (a.size !== b.size)
        return false;
    let aa = Array.from(a); 
    let bb = Array.from(b);
    return aa.filter(function(i){return bb.indexOf(i)<0}).length==0;
}

NOTE: Array.from is one of the standard ECMAScript 6 features but it is not widely supported in modern browsers. Check the compatibility table here : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from#Browser_compatibility

Solution 11 - Javascript

I follow this approach in tests :

let setA = new Set(arrayA);
let setB = new Set(arrayB);
let diff = new Set([...setA].filter(x => !setB.has(x)));
expect([...diff].length).toBe(0);

Solution 12 - Javascript

I created a quick polyfill for Set.prototype.isEqual()

Set.prototype.isEqual = function(otherSet) {
    if(this.size !== otherSet.size) return false;
    for(let item of this) if(!otherSet.has(item)) return false;
    return true;
}

Github Gist - Set.prototype.isEqual

Solution 13 - Javascript

Based on the accepted answer, assuming support of Array.from, here is a one-liner:

function eqSet(a, b) {
    return a.size === b.size && Array.from(a).every(b.has.bind(b));
}

Solution 14 - Javascript

With Ramda : equals(set1, set2)

const s1 = new Set([1, 2, 3]);
const s2 = new Set([3, 1, 2]);

console.log( R.equals(s1, s2) );

<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>

Solution 15 - Javascript

  1. Check if sizes are equal . If not, then they are not equal.

  2. iterate over each elem of A and check in that exists in B. If one fails return unequal

  3. If the above 2 conditions fails that means they are equal.

let isEql = (setA, setB) => {
  if (setA.size !== setB.size)
    return false;
  
  setA.forEach((val) => {
    if (!setB.has(val))
      return false;
  });
  return true;
}

let setA = new Set([1, 2, {
  3: 4
}]);
let setB = new Set([2, {
    3: 4
  },
  1
]);

console.log(isEql(setA, setB));

2) Method 2

let isEql = (A, B) => {
  return JSON.stringify([...A].sort()) == JSON.stringify([...B].sort());
}

let res = isEql(new Set([1, 2, {3:4}]), new Set([{3:4},1, 2]));
console.log(res);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionwilliamcodesView Question on Stackoverflow
Solution 1 - JavascriptAadit M ShahView Answer on Stackoverflow
Solution 2 - JavascriptMax LeizerovichView Answer on Stackoverflow
Solution 3 - JavascriptanthonyseriousView Answer on Stackoverflow
Solution 4 - Javascript7vujy0f0hyView Answer on Stackoverflow
Solution 5 - JavascriptpedrobernView Answer on Stackoverflow
Solution 6 - Javascriptuser663031View Answer on Stackoverflow
Solution 7 - JavascriptMikhail UnenovView Answer on Stackoverflow
Solution 8 - Javascriptrob2dView Answer on Stackoverflow
Solution 9 - JavascriptthadeuszlayView Answer on Stackoverflow
Solution 10 - JavascriptTaoPRView Answer on Stackoverflow
Solution 11 - JavascriptFranciscoView Answer on Stackoverflow
Solution 12 - JavascriptLennyView Answer on Stackoverflow
Solution 13 - JavascriptJohn HofferView Answer on Stackoverflow
Solution 14 - JavascriptYairoproView Answer on Stackoverflow
Solution 15 - JavascriptsapyView Answer on Stackoverflow