JavaScript Sets comparison [duplicate] - javascript

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);

Related

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));

Check if array has element(s) from another array

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))

Array intersection (set-theoretic) with Array.prototype.reduce

I have two arrays [a,b,c,d] and [b,d,f,h].
I want to get an array back with the common elements [b,d].
I can achieve that with a combination of filter and indexOf:
[a,b,c,d].filter(el => [b,d,f,h].indexOf(el) !== -1)
but I was wondering if and how I can do the same with reduce.
I admit that, despite looking at many examples, reduce still is to me one of the most obscure JS methods, so I'd really appreciate some advice.
ES6, a propoosal with Array#includes
The includes() method determines whether an array includes a certain element, returning true or false as appropriate.
On every loop of aa, reduce adds the element to the result array if the value is found in the test array bb. If not found, the former result is returned.
var aa = ['a','b','c','d'],
bb = ['b','d','f','h'],
cc = aa.reduce((r, a) => bb.includes(a) && r.concat(a) || r, []);
console.log(cc);
Just a smarter approach with using a single array which contains all arrays.
var aa = ['a','b','c','d'],
bb = ['b','d','f','h'],
result = [aa, bb].reduce((a, b) => a.filter(c => b.includes(c)));
console.log(result);
Reduce is designed to return a single value from a list of items. So filter makes much more sense here.
A good use for reduce would be to return the total number of common elements. Check it out here: https://jsfiddle.net/c69vgzL4/
var a = ['a','b','c','d']
var b = ['b','d','f','h']
var number_of_common = b.reduce(function(prev, next) {
return prev + (a.indexOf(next) + 1 ? 1 : 0)
}, 0)
$('body').html(number_of_common)
Not only two arrays but for an intersection of n arrays... Let's invent Array.prototype.intersect()
Array.prototype.intersect = function(...a) {
return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
}
var arrs = [[0,2,4,6,8],[4,5,6,7],[4,6]],
arr = [0,1,2,3,4,5,6,7,8,9];
console.log(JSON.stringify(arr.intersect(...arrs)));
// or just do
console.log(JSON.stringify(["a","b","c","d"].intersect(["b","d","f","h"])));

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);

How to implement a map or sorted-set in javascript

Javascript has arrays which use numeric indexes ["john", "Bob", "Joe"] and objects which can be used like associative arrays or "maps" that allow string keys for the object values {"john" : 28, "bob": 34, "joe" : 4}.
In PHP it is easy to both A) sort by values (while maintaining the key) and B) test for the existence of a value in an associative array.
$array = ["john" => 28, "bob" => 34, "joe" => 4];
asort($array); // ["joe" => 4, "john" => 28, "bob" => 34];
if(isset($array["will"])) { }
How would you acheive this functionality in Javascript?
This is a common need for things like weighted lists or sorted sets where you need to keep a single copy of a value in data structure (like a tag name) and also keep a weighted value.
This is the best I've come up with so far:
function getSortedKeys(obj) {
var keys = Object.keys(obj);
keys = keys.sort(function(a,b){return obj[a]-obj[b]});
var map = {};
for (var i = keys.length - 1; i >= 0; i--) {
map[keys[i]] = obj[keys[i]];
};
return map;
}
var list = {"john" : 28, "bob": 34, "joe" : 4};
list = getSortedKeys(list);
if(list["will"]) { }
Looking at this answer by Luke Schafer I think I might have found a better way to handle this by extending the Object.prototype:
// Sort by value while keeping index
Object.prototype.iterateSorted = function(worker, limit)
{
var keys = Object.keys(this), self = this;
keys.sort(function(a,b){return self[b] - self[a]});
if(limit) {
limit = Math.min(keys.length, limit);
}
limit = limit || keys.length;
for (var i = 0; i < limit; i++) {
worker(keys[i], this[keys[i]]);
}
};
var myObj = { e:5, c:3, a:1, b:2, d:4, z:1};
myObj.iterateSorted(function(key, value) {
console.log("key", key, "value", value)
}, 3);
http://jsfiddle.net/Xeoncross/kq3gbwgh/
With ES6 you could choose to extend the Map constructor/class with a sort method that takes an optional compare function (just like arrays have). That sort method would take two arguments, each of which are key/value pairs so that the sorting can happen on either the keys or the values (or both).
The sort method will rely on the documented behaviour of Maps that entries are iterated in insertion order. So this new method will visit the entries according to the sorted order, and then delete and immediately re-insert them.
Here is how that could look:
class SortableMap extends Map {
sort(cmp = (a, b) => a[0].localeCompare(b[0])) {
for (const [key, value] of [...this.entries()].sort(cmp)) {
this.delete(key);
this.set(key, value); // New keys are added at the end of the order
}
}
}
// Demo
const mp = new SortableMap([[3, "three"],[1, "one"],[2, "two"]]);
console.log("Before: ", JSON.stringify([...mp])); // Before
mp.sort( (a, b) => a[0] - b[0] ); // Custom compare function: sort numerical keys
console.log(" After: ", JSON.stringify([...mp])); // After
I'm not sure why none of these answers mentions the existence of a built-in JS class, Set. Seems to be an ES6 addition, perhaps that's why.
Ideally override either add or keys below... NB overriding keys doesn't even need access to the Set object's prototype. Of course you could override these methods for the entire Set class. Or make a subclass, SortedSet.
const mySet = new Set();
const mySetProto = Object.getPrototypeOf(mySet);
const addOverride = function(newObj){
const arr = Array.from(this);
arr.add(newObj);
arr.sort(); // or arr.sort(function(a, b)...)
this.clear();
for(let item of arr){
mySetProto.add.call(this, item);
}
}
mySet.add = addOverride;
const keysOverride = function(){
const arr = Array.from(this);
arr.sort(); // or arr.sort(function(a, b)...)
return arr[Symbol.iterator]();
}
mySet.keys = keysOverride;
Usage:
mySet.add(3); mySet.add(2); mySet.add(1); mySet.add(2);
for(let item of mySet.keys()){console.log(item)};
Prints out:
1 ... 2 ... 3
NB Set.keys() returns not the items in the Set, but an iterator. You could choose to return the sorted array instead, but you'd obviously be breaking the class's "contract".
Which one to override? Depends on your usage and the size of your Set. If you override both you will be duplicating the sort activity, but in most cases it probably won't matter.
NB The add function I suggest is of course naive, a "first draft": rebuilding the entire set each time you add could be pretty costly. There are clearly much cleverer ways of doing this based on examining the existing elements in the Set and using a compare function, a binary tree structure*, or some other method to determine where in it to add the candidate for adding (I say "candidate" because it would be rejected if an "identical" element, namely itself, were already found to be present).
The question also asks about similar arrangements for a sorted map... in fact it turns out that ES6 has a new Map class which lends itself to similar treatment ... and also that Set is just a specialised Map, as you might expect.
* e.g. https://github.com/Crizstian/data-structure-and-algorithms-with-ES6/tree/master/10-chapter-Binary-Tree
You usually don't sort an object. But if you do: Sorting JavaScript Object by property value
If you want to sort an array, let's say the following
var arraylist = [{"john" : 28},{ "bob": 34},{ "joe" : 4}];
You can always use Array.prototype.sort function.
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
Maybe this code look like what you want:
Object.prototype.asort = function(){
var retVal = {};
var self = this;
var keys = Object.keys(this);
keys = keys.sort(function(a,b){return self[a] - self[b]});
for (var i = 0; i < keys.length; i++) {
retVal[keys[i]] = this[keys[i]];
}
return retVal;
}
var map = {"john" : 28, "bob": 34, "joe" : 4}
var sortedMap = map.asort();//sortedMap["will"]: undefined
If you use the open source project jinqJs its easy.
See Fiddler
var result = jinqJs()
.from([{"john" : 28},{ "bob": 34},{ "joe" : 4}])
.orderBy([{field: 0}])
.select();
Here's an implementation of OrderedMap.
Use the functions get() and set() to extract or push key value pairs to the OrderedMap.
It is internally using an array to maintain the order.
class OrderedMap {
constructor() {
this.arr = [];
return this;
}
get(key) {
for(let i=0;i<this.arr.length;i++) {
if(this.arr[i].key === key) {
return this.arr[i].value;
}
}
return undefined;
}
set(key, value) {
for(let i=0;i<this.arr.length;i++) {
if(this.arr[i].key === key) {
this.arr[i].value = value;
return;
}
}
this.arr.push({key, value})
}
values() {
return this.arr;
}
}
let m = new OrderedMap();
m.set('b', 60)
m.set('a', 10)
m.set('c', 20)
m.set('d', 89)
console.log(m.get('a'));
console.log(m.values());
https://github.com/js-sdsl/js-sdsl
The OrderedMap in Js-sdsl maybe helpful.
This is a sorted-map which implement refer to C++ STL Map.
/*
* key value
* 1 1
* 2 2
* 3 3
* Sorted by key.
*/
const mp = new OrderedMap(
[1, 2, 3].map((element, index) => [index, element])
);
mp.setElement(1, 2); // O(logn)
mp.eraseElementByKey(1) // O(logn)
// custom comparison function
mp = new OrderedMap(
[1, 2, 3].map((element, index) => [index, element]),
(x, y) => x - y
);
// enable tree iterator index (enableIndex = true)
console.log(new OrderedMap([[0, 1], [1, 1]], undefined, true).begin(),next().index); // 1

Categories