Check if array has element(s) from another array - javascript

I have two arrays A = [0,1,2] and B = [2,1,0]. How to check if a number in A is present in B?

NOTE: includes is not ES6, but ES2016 Mozilla docs. This will break if you transpile ES6 only.
You can use Array#every method(to iterate and check all element passes the callback function) with Array#includes method(to check number present in B).
A.every( e => B.includes(e) )
const A = [0, 1, 2],
B = [2, 1, 0],
C=[2, 1];
console.log(A.every(e => B.includes(e)));
console.log(A.every(e => C.includes(e)));
console.log(C.every(e => B.includes(e)));
To check a single element present in the second array do:
A[0].includes(e)
//^---index
Or using Array#indexOf method, for older browser.
A[0].indexOf(e) > -1
Or in case you want to check at least one element present in the second array then you need to use Array#some method(to iterate and check at least one element passes the callback function).
A.some(e => B.includes(e) )
const A = [0, 1, 2],
B = [2, 1, 0],
C=[2, 1],D=[4];
console.log(A.some(e => B.includes(e)));
console.log(A.some(e => C.includes(e)));
console.log(C.some(e => B.includes(e)));
console.log(C.some(e => D.includes(e)));

Here's a self defined function I use to compare two arrays. Returns true if array elements are similar and false if different. Note: Does not return true if arrays are equal (array.len && array.val) if duplicate elements exist.
var first = [1,2,3];
var second = [1,2,3];
var third = [3,2,1];
var fourth = [1,3];
var fifth = [0,1,2,3,4];
console.log(compareArrays(first, second));
console.log(compareArrays(first, third));
console.log(compareArrays(first, fourth));
console.log(compareArrays(first, fifth));
function compareArrays(first, second){
//write type error
return first.every((e)=> second.includes(e)) && second.every((e)=> first.includes(e));
}

If the intent is to actually compare the array, the following will also account for duplicates
const arrEq = (a, b) => {
if (a.length !== b.length) {
return false
}
const aSorted = a.sort()
const bSorted = b.sort()
return aSorted
.map((val, i) => bSorted[i] === val)
.every(isSame => isSame)
}
Hope this helps someone :D

If you just need to know whether A and B has same entries, simply
JSON.stringify(A.concat().sort()) === JSON.stringify(B.concat().sort())
Note:
If there are null and undefined in each array, it will be true. Because JSON#stringify converts undefined to null.
JSON.stringify([null].concat().sort()) === JSON.stringify([undefined].concat().sort())
// true

A.length === B.length && A.every(e => B.includes(e))

Related

Sorting an array of object given an initial value to always be first

Given an array and variable as following..
array = ['complete','in_progress','planned'];
value = 'planned';
I want to sort the array always starting with the 'value' variable
the output should display.
array = ['planned','in_progress','complete'];
eg.
array = ['complete','in_progress','planned'];
value = 'in_progress';
output would be...
array = ['in_progress','complete','planned'];
I tried different ways but I failed to come with a decent solution. Any clean and short ideas?
You can sort and give higher priority to value - if it matches one of the arguments, then it will be sorted before anything. If neither of the two items is value, then just sort normally.
const array = ['complete','in_progress','planned'];
const value = 'in_progress';
array.sort((a, b) => {
//a comes first
if (a == value) return -1;
//b comes first
if (b == value) return 1;
//neither matches `value` - compare them normally
return a.localeCompare(b);
});
console.log(array);
And this is the shorter version of the same by (somewhat) abusing type coercion:
const array = ['complete','in_progress','planned'];
const value = 'in_progress';
array.sort((a,b) => ((a == value) * -1) || b == value || a.localeCompare(b));
console.log(array);
From the top of my head you can try
array = ['complete','in_progress','planned'];
target = 'in_progress';
const answer = array.filter((element) => element !== target)
.sort()
.reduce((accumulator, value) => [...accumulator, value], [target]);
edit: I forgot it needs to be sorted.

How to find a specific array in an array?

I have got this code:
var test = [[1,1], "b","a"];
function findArray(element) {
return element == [1,1];
}
console.log(test.find(findArray));
It returns:
undefined
What should I do to find the [1,1] array inside of the test array? (I do not want to loop through it though)
You can't compare two objects with === or == since they are references and will evaluate to true only if they are pointing to the same address.
You need to match each element from first array with respective index in second array to check for similarity, you can use every.
var test = [ [1, 1], "b", "a"];
function findArray(element) {
if(Array.isArray(element)){
let arrayToMatchWith = [1,1]
if(element.length === arrayToMatchWith.length){
return element.every((v,i)=> v === arrayToMatchWith[i])
}
}
return false
}
console.log(test.find(findArray));
console.log([[1,2]].find(findArray));
console.log([[1,1,1]].find(findArray));
console.log([[1]].find(findArray));
Could I pass the searched array as an argument?
Yes you can. Here I am using a curried function:
var test = [[1, 1], "b", "a"];
let curried = (arr) => (element) => {
if (Array.isArray(element)) {
if (element.length === arr.length) {
return element.every((v, i) => v === arr[i])
}
}
return false
}
let curried1 = curried([1,1])
console.log(test.find(curried1));
let curried2 = curried([1,2,2])
console.log([[1, 2]].find(curried2));
let curried3 = curried([1,1,1])
console.log([[1, 1, 1]].find(curried3));
let curried4 = curried([1])
console.log([[1]].find(curried4));
The array literal in your comparison is a different object than the array inside your original array. If you derive a comparable entity from each array, you can check for that inside the find method. One way to do it is by calling the toString method.
This is only required if you really don't want to loop through the array for comparison.
var test = [[1,1], "b","a"];
function findArray(element) {
if (element.constructor === Array)
return element.toString() == [1,1].toString();
}
console.log(test.find(findArray));

JavaScript Sets comparison [duplicate]

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?
Try this:
const eqSet = (xs, ys) =>
xs.size === ys.size &&
[...xs].every((x) => ys.has(x));
const ws = new Set([1, 2, 3]);
const xs = new Set([1, 3, 2]);
const ys = new Set([1, 2, 4]);
const zs = new Set([1, 2, 3, 4]);
console.log(eqSet(ws, xs)); // true
console.log(eqSet(ws, ys)); // false
console.log(eqSet(ws, zs)); // false
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))
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
you can 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())
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>
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.
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);
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 🙃)
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.
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
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));
}
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);
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
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>
I believe this is the most performant version because it's not creating a new array, it's using the Set's own iterator:
function isEqualSets(a, b) {
if (a === b) return true;
if (a.size !== b.size) return false;
for (const value of a) if (!b.has(value)) return false;
return true;
}
None of the existing answers check the Set's insertion order, so here's one that does. It uses lodash _.isEqualWith to do shallow checking (since _.isEqual does deep checking and is slow for sets)
import isEqualWith from 'lodash/isEqualWith';
export function setsAreEqual (setA: Set<unknown>, setB: Set<unknown>): boolean {
return isEqualWith(setA, setB, (a: unknown, b: unknown) => {
if (a === setA) return undefined;
return a === b;
});
}
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);

Javascript - Loop through array backwards with forEach

Is there a way to loop backwards through an array using forEach (not any other kind of loop, I know how to do with with a for / standard ways) and without actually reversing the array itself?
let arr = [1, 2, 3];
arr.slice().reverse().forEach(x => console.log(x))
will print:
3
2
1
arr will still be [1, 2, 3], the .slice() creates a shallow copy.
There is a similar array method that has a reverse counter part, reduce comes together with reduceRight:
const array = ['alpha', 'beta', 'gamma'];
array.reduceRight((_, elem) => console.log(elem), null);
When using it for the requested purpose, make sure to provide a second argument. It can be null or anything else. Also note that the callback function has as first argument the accumulator, which you don't need for this purpose.
If including a library is an option:
Lodash: forEachRight.
Just use a for loop. Start at the end of the array and go backwards from there.
const array = ['blastoff', 1, 2, 3];
for (let index = array.length - 1; index >= 0; index--) {
const element = array[index];
console.log(element);
}
No, forEach only processes forward through the array. So you'd have to do something else, which you've said in your question was out of scope.
I can think of two options which just use precursors to using forEach (so, they don't use a for loop or other kind of loop). I don't know if those would be out of scope or not, so here they are:
Copy the array and reverse the copy, then use forEach on it
Use Object.keys to get the indexes, reverse that, then use forEach on it (which will loop through the indexes, not the values, but then we can look them up)
Here's #1:
slice copies the array (shallow copy, so not likely to be expensive), then we reverse it, then forEach:
var a = ['one', 'two', 'three'];
a.slice().reverse().forEach(function(entry) {
console.log(entry);
});
console.log("Proof that a is not, itself, reversed: " +
JSON.stringify(a));
Here's #2:
We use Object.keys to get the array indices (using filter if you store non-element properties in your arrays), reverse that, and then loop through the result:
var a = ['one', 'two', 'three'];
Object.keys(a).reverse().forEach(function(index) {
console.log(a[index]);
});
console.log("Proof that a is not, itself, reversed: " +
JSON.stringify(a));
Side note: Here's what I mean about using filter if you have non-element properties on your array:
var a = ['one', 'two', 'three'];
a.nonElementProperty = "foo";
Object.keys(a).filter(function(name) {
return String(+name) === name;
}).reverse().forEach(function(index) {
console.log(a[index]);
});
console.log("Proof that a is not, itself, reversed: " +
JSON.stringify(a));
As yet the browsers do not seem to have optimised the Array.forEach function. With not much effort you can write a simple polyfill that out performs the Array.forEach method by at least 10 to 1.
So you can create your own Array.revEach and have it outperform the native Array.forEach, thought I hope that the browsers address the very slow performance of Array.forEach soon and make the need to polyfill actual existing methods not necessary.
For Array.revEach out performs Array.forEach running 17 times faster on "Chrome 46.0.2490.22 beta-m"
if (Array.prototype.revEach === undefined) {
Object.defineProperty(Array.prototype, 'revEach', {
writable : false,
enumerable : false,
configurable : false,
value : function (func) {
var i;
var len = this.length-1;
for (i = len; i >= 0; i--) {
func(this[i], i, this);
}
}
});
}
Just to add the actual official polyfill modified to reverse. Comments show my changes.
// Production steps of ECMA-262, Edition 5, 15.4.4.18
// Reference: http://es5.github.io/#x15.4.4.18
// Modified by Blindman67 to revEach
if (!Array.prototype.revEach) { // name changed
Array.prototype.revEach = function(callback, thisArg) { // name changed
var T; // k defined where len was
if (this == null) {
throw new TypeError(' this is null or not defined');
}
var O = Object(this);
var k = (O.length >>> 0)-1; // set k (counter) ToUint32
// len var removed
if (typeof callback !== "function") {
throw new TypeError(callback + ' is not a function');
}
if (arguments.length > 1) {
T = thisArg;
}
while (k >= 0) { // reverse condition
var kValue;
if (k in O) {
kValue = O[k];
callback.call(T, kValue, k, O);
}
k--; // dec counter
}
};
}
array.forEach has 3 parameters. You can use these to effectively forEach backward.
var arr = [1, 2, 3];
arr.forEach(function(x, index, the_array) {
let x_prime = the_array[the_array.length-1-index]
console.log(x_prime);
})
will print
3
2
1
This can be accomplished relatively concisely using the reverse method, the forEach method, and (if using ES6) the arrow function
var someArray = ["a","b","c","d"];
someArray.reverse().forEach(arrayItem =>
console.log(arrayItem)
)
If you are not using ES6, the solution is about the same, just without the arrow function.
var someArray = ["a","b","c","d"];
someArray.reverse().forEach(function(arrayItem) {
console.log(arrayItem)
})
Both will print to the console:
d
c
b
a
There are many ways to achive this task.
Firsly we can use Array.reduceRight() method
[1,2,3,4,5].reduceRight((total,item) => {
console.log(item);
// Do somthing here and remember the return statement
return item;
},0)
Output: 5,4,3,2,1
the reduceRight method traverse an array in right to left manner and we can get advantage of it.
,but always keep in mind that you have to return something to keep going this loop until the length is reached.
As a second method we can use Array.reverse()
this method first format the array in reversed manner then returns it, and now you can iterate in reverse manner.
[1,2,3].reverse().map(n => console.log(n));
Output: 3,2,1
But the disadvantage of this method is that if you have thousands of entries in array then it may affect your performance.
Third and the most easiest way it to use classical for loop
let array = [1,2,3,4,5];
let start = array.length;
for(;start >= 0;start--){
// travese in right to left manner
console.log(array[start])
}
What about:
const array = [1, 2, 3];
const revArray = [...array].reverse() // won't affect the original array
revArray.forEach(x => console.log(x)) // 3, 2, 1
One liner:
[...array].reverse().forEach(x => console.log(x)) // 3, 2, 1
With index var:
[...array].reverse().forEach((val, index) => {
const revIndex = array.length - i - 1
console.log(revIndex) // 0, 1, 2
console.log(revIndex) // 2, 1, 0
console.log(val) // 3, 2, 1
})
You can also do it this way
let arr = [1, 3, 5, 7, 9];
arr.forEach(value => {
console.log(value);
})
let reversed = new Array(arr.reverse());
console.log("\n\nReversed: ");
reversed.forEach(value => {
value.forEach(val => {
console.log(val)
})
})

comparing ECMA6 sets for equality

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?
Try this:
const eqSet = (xs, ys) =>
xs.size === ys.size &&
[...xs].every((x) => ys.has(x));
const ws = new Set([1, 2, 3]);
const xs = new Set([1, 3, 2]);
const ys = new Set([1, 2, 4]);
const zs = new Set([1, 2, 3, 4]);
console.log(eqSet(ws, xs)); // true
console.log(eqSet(ws, ys)); // false
console.log(eqSet(ws, zs)); // false
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))
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
you can 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())
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>
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.
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);
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 🙃)
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.
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
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));
}
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);
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
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>
I believe this is the most performant version because it's not creating a new array, it's using the Set's own iterator:
function isEqualSets(a, b) {
if (a === b) return true;
if (a.size !== b.size) return false;
for (const value of a) if (!b.has(value)) return false;
return true;
}
None of the existing answers check the Set's insertion order, so here's one that does. It uses lodash _.isEqualWith to do shallow checking (since _.isEqual does deep checking and is slow for sets)
import isEqualWith from 'lodash/isEqualWith';
export function setsAreEqual (setA: Set<unknown>, setB: Set<unknown>): boolean {
return isEqualWith(setA, setB, (a: unknown, b: unknown) => {
if (a === setA) return undefined;
return a === b;
});
}
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);

Categories