I'm looking for an efficient way to find out whether two arrays contain same amounts of equal elements (in the ==
sense), in any order:
foo = {/*some object*/}
bar = {/*some other object*/}
a = [1,2,foo,2,bar,2]
b = [bar,2,2,2,foo,1]
sameElements(a, b) --> true
PS. Note that pretty much every solution in the thread uses ===
and not ==
for comparison. This is fine for my needs though.
Update 5 I posted a new answer with a different approach.
Update
I extended the code to have the possibility of either checking by
reference
orequality
just pass
true
as second parameter to do a reference check.Also I added the example to Brunos JSPerf
I will comment the code as soon(!) as I get some spare time to explain it a bit more, but at the moment don't have the time for that, sry. DoneUpdate 2.
Like Bruno pointed out in the comments
sameElements([NaN],[NaN])
yieldsfalse
In my opinion this is the correct behaviour as
NaN
is ambigious and should always lead to afalse
result,at least when comparingNaN.equals(NaN)
. But he had quite a good point.Whether
[1,2,foo,bar,NaN,3]
should be equal to[1,3,foo,NaN,bar,2]
or not.Ok.. honestly I'm a bit torn whether it should or not, so i added two flags.
NaN.equals(NaN) //true
[NaN].equals([NaN],true) //true
Number.prototype.equals
anywayUpdate 3:
Dang i totally missed 2 lines in the sort function.
Added
Line 105 in the Fiddle
Which is kind of important as it determines the consistent order of the Elements.
Update 4
I tried to optimize the sort function a bit, and managed to get it up to about 20 ops/s.
Below is the updated code, as well as the updated fiddle =)
Also i chose to mark the objects outside the sort function, it doesn't seem to make a performance difference anymore, and its more readable
Here is an approach using
Object.defineProperty
to addequals
functions toArray,Object,Number,String,Boolean's
prototype
to avoid typechecking in one function for performance reasons. As we can recursively call.equals
on any element.But of course checking Objects for equality may cause performance issues in big Objects.
So if anyone feels unpleasant manipulating native prototypes, just do a type check and put it into one function
With this we can reduce the
sameElements
function toNote. of course you could put all equal functions into the
sameElements
function, for the cost of the typechecking.Now here are 3 examples: 1 with deep checking, 2 with reference checking.
So these are the Arrays we compare. And the output is
Check
a
andb
with references only.console.log (sameElements ( a,b,true)) //true As they contain the same elements
Check
b
andc
with references onlyconsole.log (sameElements (b,c,true)) //false as c contains bar twice.
Check
b
andc
deeplyconsole.log (sameElements (b,c,false)) //true as bar and foo are equal but not the same
Check for 2 Arrays containing NaN
Array.prototype.equals.NaN = true;
console.log(sameElements([NaN],[NaN],true)); //true.
Array.prototype.equals.NaN = false;
Demo on JSFiddle
Using efficient lookup tables for the counts of the elements:
It still uses
indexOf
search amongst all objects, so if you have multisets with many different objects you might want to optimize that part as well. Have a look at Unique ID or object signature (and it's duplicate questions) for how to get lookup table keys for them. And if you don't have many primitive values in the multisets, you might just store them in arrays and sort those before comparing each item-by-item (like @Bruno did).Disclaimer: This solution doesn't try to get the
[[PrimitiveValue]]
of objects, they will never be counted as equal to primitives (while==
would do).Here is the update on @Bruno's jsperf test of the answers, yet I guess only two objects (each of them present 500 times in the 10k array) and no duplicate primitive values are not representative.
Edit 2
1) Thanks to user2357112 for pointing out the
Object.prototype.toString.call
issue this also showed, the reason it was that fast, that it didn't consider Arrays ...I fixed the code,it should be working now :), unfortunately its now at about 59ops/s on chrome and 45ops/s on ff.
Fiddle and JSPerf is updated.
Edit
1) I fixed the code, it supports mutliple variables referencing the same Object now. A little bit slower than before, but still over 100ops/s on chrome.
2) I tried using a bitmask instead of an array to keep multiple positions of the objects, but its nearly 15ops/s slow
3) As pointed ot in the comments i forgot to reset
tmp
after[[get]]
is called fixed the code, the fiddle, and the perf.So thanks to user2357112 with his Answer heres another approach using counting
Here is a JSFiddle and a JSPerf (it uses the same Arrays a and b as in the previous answers perf) with this code vs the Closure compiled
Heres the output. note: it doesn't support a deep comparison anymore, as is
i wasn't sure if "===" is ok, the question is a bit vauge... if so, this is quite a bit faster and simpler than some other possible ways of doing it:
Thanks everyone for sharing ideas! I've came up with the following
This isn't the fastest solution, but IMO, most readable one so far.
UPDATE
As @Bergi and @thg435 point out my previous implementation was flawed so here is another implementation:
Then just call the function
The above implement actually defines a new property called "__count" that is used to keep track of non-primitive elements in both arrays. These are deleted before the function returns so as to leave the array elements as before.
Fiddle here
jsperf here.
The reason I changed the jsperf test case was that as @Bergi states the test arrays, especially the fact there were only 2 unique objects in the whole array is not representative of what we are testing for.
One other advantage of this implementation is that if you need to make it compatible with pre IE9 browsers instead of using the defineProperty to create a non-enumerable property you could just use a normal property.