Deep comparing objects inside an array and finding duplicates [duplicate] - javascript

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How do you determine equality for two JavaScript objects?
Object comparison in JavaScript
If I have two arrays or objects and want to compare them, such as
object1 = [
{ shoes:
[ 'loafer', 'penny' ]
},
{ beers:
[ 'budweiser', 'busch' ]
}
]
object2 = [
{ shoes:
[ 'loafer', 'penny' ]
},
{ beers:
[ 'budweiser', 'busch' ]
}
]
object1 == object2 // false
this can be annoying if you're getting a response from a server and trying to see if it's changed

Update:
In response to the comments and worries surrounding the original suggestion (comparing 2 JSON strings), you could use this function:
function compareObjects(o, p)
{
var i,
keysO = Object.keys(o).sort(),
keysP = Object.keys(p).sort();
if (keysO.length !== keysP.length)
return false;//not the same nr of keys
if (keysO.join('') !== keysP.join(''))
return false;//different keys
for (i=0;i<keysO.length;++i)
{
if (o[keysO[i]] instanceof Array)
{
if (!(p[keysO[i]] instanceof Array))
return false;
//if (compareObjects(o[keysO[i]], p[keysO[i]] === false) return false
//would work, too, and perhaps is a better fit, still, this is easy, too
if (p[keysO[i]].sort().join('') !== o[keysO[i]].sort().join(''))
return false;
}
else if (o[keysO[i]] instanceof Date)
{
if (!(p[keysO[i]] instanceof Date))
return false;
if ((''+o[keysO[i]]) !== (''+p[keysO[i]]))
return false;
}
else if (o[keysO[i]] instanceof Function)
{
if (!(p[keysO[i]] instanceof Function))
return false;
//ignore functions, or check them regardless?
}
else if (o[keysO[i]] instanceof Object)
{
if (!(p[keysO[i]] instanceof Object))
return false;
if (o[keysO[i]] === o)
{//self reference?
if (p[keysO[i]] !== p)
return false;
}
else if (compareObjects(o[keysO[i]], p[keysO[i]]) === false)
return false;//WARNING: does not deal with circular refs other than ^^
}
if (o[keysO[i]] !== p[keysO[i]])//change !== to != for loose comparison
return false;//not the same value
}
return true;
}
But in many cases, it needn't be that difficult IMO:
JSON.stringify(object1) === JSON.stringify(object2);
If the stringified objects are the same, their values are alike.
For completeness' sake: JSON simply ignores functions (well, removes them all together). It's meant to represent Data, not functionality.
Attempting to compare 2 objects that contain only functions will result in true:
JSON.stringify({foo: function(){return 1;}}) === JSON.stringify({foo: function(){ return -1;}});
//evaulutes to:
'{}' === '{}'
//is true, of course
For deep-comparison of objects/functions, you'll have to turn to libs or write your own function, and overcome the fact that JS objects are all references, so when comparing o1 === ob2 it'll only return true if both variables point to the same object...
As #a-j pointed out in the comment:
JSON.stringify({a: 1, b: 2}) === JSON.stringify({b: 2, a: 1});
is false, as both stringify calls yield "{"a":1,"b":2}" and "{"b":2,"a":1}" respectively. As to why this is, you need to understand the internals of chrome's V8 engine. I'm not an expert, and without going into too much detail, here's what it boils down to:
Each object that is created, and each time it is modified, V8 creates a new hidden C++ class (sort of). If object X has a property a, and another object has the same property, both these JS objects will reference a hidden class that inherits from a shared hidden class that defines this property a. If two objects all share the same basic properties, then they will all reference the same hidden classes, and JSON.stringify will work exactly the same on both objects. That's a given (More details on V8's internals here, if you're interested).
However, in the example pointed out by a-j, both objects are stringified differently. How come? Well, put simply, these objects never exist at the same time:
JSON.stringify({a: 1, b: 2})
This is a function call, an expression that needs to be resolved to the resulting value before it can be compared to the right-hand operand. The second object literal isn't on the table yet.
The object is stringified, and the exoression is resolved to a string constant. The object literal isn't being referenced anywhere and is flagged for garbage collection.
After this, the right hand operand (the JSON.stringify({b: 2, a: 1}) expression) gets the same treatment.
All fine and dandy, but what also needs to be taken into consideration is that JS engines now are far more sophisticated than they used to be. Again, I'm no V8 expert, but I think its plausible that a-j's snippet is being heavily optimized, in that the code is optimized to:
"{"b":2,"a":1}" === "{"a":1,"b":2}"
Essentially omitting the JSON.stringify calls all together, and just adding quotes in the right places. That is, after all, a lot more efficient.

As an underscore mixin:
in coffee-script:
_.mixin deepEquals: (ar1, ar2) ->
# typeofs should match
return false unless (_.isArray(ar1) and _.isArray(ar2)) or (_.isObject(ar1) and _.isObject(ar2))
#lengths should match
return false if ar1.length != ar2.length
still_matches = true
_fail = -> still_matches = false
_.each ar1, (prop1, n) =>
prop2 = ar2[n]
return if prop1 == prop2
_fail() unless _.deepEquals prop1, prop2
return still_matches
And in javascript:
_.mixin({
deepEquals: function(ar1, ar2) {
var still_matches, _fail,
_this = this;
if (!((_.isArray(ar1) && _.isArray(ar2)) || (_.isObject(ar1) && _.isObject(ar2)))) {
return false;
}
if (ar1.length !== ar2.length) {
return false;
}
still_matches = true;
_fail = function() {
still_matches = false;
};
_.each(ar1, function(prop1, n) {
var prop2;
prop2 = ar2[n];
if (prop1 !== prop2 && !_.deepEquals(prop1, prop2)) {
_fail();
}
});
return still_matches;
}
});

Related

Tic Tac Toe winning condition in JavaScript [duplicate]

This question already has answers here:
How to compare arrays in JavaScript?
(55 answers)
Closed 6 years ago.
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
alert(a == b + "|" + b == c);
demo
How can I check these array for equality and get a method which returns true if they are equal?
Does jQuery offer any method for this?
This is what you should do. Please do not use stringify nor < >.
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
// Please note that calling sort on an array will modify that array.
// you might want to clone your array first.
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
[2021 changelog: bugfix for option4: no total ordering on js objects (even excluding NaN!=NaN and '5'==5 ('5'===5, '2'<3, etc.)), so cannot use .sort(cmpFunc) on Map.keys() (though you can on Object.keys(obj), since even 'numerical' keys are strings).]
Option 1
Easiest option, works in almost all cases, except that null!==undefined but they both are converted to JSON representation null and considered equal:
function arraysEqual(a1,a2) {
/* WARNING: arrays must not contain {objects} or behavior may be undefined */
return JSON.stringify(a1)==JSON.stringify(a2);
}
(This might not work if your array contains objects. Whether this still works with objects depends on whether the JSON implementation sorts keys. For example, the JSON of {1:2,3:4} may or may not be equal to {3:4,1:2}; this depends on the implementation, and the spec makes no guarantee whatsoever. [2017 update: Actually the ES6 specification now guarantees object keys will be iterated in order of 1) integer properties, 2) properties in the order they were defined, then 3) symbol properties in the order they were defined. Thus IF the JSON.stringify implementation follows this, equal objects (in the === sense but NOT NECESSARILY in the == sense) will stringify to equal values. More research needed. So I guess you could make an evil clone of an object with properties in the reverse order, but I cannot imagine it ever happening by accident...] At least on Chrome, the JSON.stringify function tends to return keys in the order they were defined (at least that I've noticed), but this behavior is very much subject to change at any point and should not be relied upon. If you choose not to use objects in your lists, this should work fine. If you do have objects in your list that all have a unique id, you can do a1.map(function(x)}{return {id:x.uniqueId}}). If you have arbitrary objects in your list, you can read on for option #2.)
This works for nested arrays as well.
It is, however, slightly inefficient because of the overhead of creating these strings and garbage-collecting them.
Option 2
Historical, version 1 solution:
// generally useful functions
function type(x) { // does not work in general, but works on JSONable objects we care about... modify as you see fit
// e.g. type(/asdf/g) --> "[object RegExp]"
return Object.prototype.toString.call(x);
}
function zip(arrays) {
// e.g. zip([[1,2,3],[4,5,6]]) --> [[1,4],[2,5],[3,6]]
return arrays[0].map(function(_,i){
return arrays.map(function(array){return array[i]})
});
}
// helper functions
function allCompareEqual(array) {
// e.g. allCompareEqual([2,2,2,2]) --> true
// does not work with nested arrays or objects
return array.every(function(x){return x==array[0]});
}
function isArray(x){ return type(x)==type([]) }
function getLength(x){ return x.length }
function allTrue(array){ return array.reduce(function(a,b){return a&&b},true) }
// e.g. allTrue([true,true,true,true]) --> true
// or just array.every(function(x){return x});
function allDeepEqual(things) {
// works with nested arrays
if( things.every(isArray) )
return allCompareEqual(things.map(getLength)) // all arrays of same length
&& allTrue(zip(things).map(allDeepEqual)); // elements recursively equal
//else if( this.every(isObject) )
// return {all have exactly same keys, and for
// each key k, allDeepEqual([o1[k],o2[k],...])}
// e.g. ... && allTrue(objectZip(objects).map(allDeepEqual))
//else if( ... )
// extend some more
else
return allCompareEqual(things);
}
// Demo:
allDeepEqual([ [], [], [] ])
true
allDeepEqual([ [1], [1], [1] ])
true
allDeepEqual([ [1,2], [1,2] ])
true
allDeepEqual([ [[1,2],[3]], [[1,2],[3]] ])
true
allDeepEqual([ [1,2,3], [1,2,3,4] ])
false
allDeepEqual([ [[1,2],[3]], [[1,2],[],3] ])
false
allDeepEqual([ [[1,2],[3]], [[1],[2,3]] ])
false
allDeepEqual([ [[1,2],3], [1,[2,3]] ])
false
<!--
More "proper" option, which you can override to deal with special cases (like regular objects and null/undefined and custom objects, if you so desire):
To use this like a regular function, do:
function allDeepEqual2() {
return allDeepEqual([].slice.call(arguments));
}
Demo:
allDeepEqual2([[1,2],3], [[1,2],3])
true
-->
Option 3
function arraysEqual(a,b) {
/*
Array-aware equality checker:
Returns whether arguments a and b are == to each other;
however if they are equal-lengthed arrays, returns whether their
elements are pairwise == to each other recursively under this
definition.
*/
if (a instanceof Array && b instanceof Array) {
if (a.length!=b.length) // assert same length
return false;
for(var i=0; i<a.length; i++) // assert each element equal
if (!arraysEqual(a[i],b[i]))
return false;
return true;
} else {
return a==b; // if not both arrays, should be the same
}
}
//Examples:
arraysEqual([[1,2],3], [[1,2],3])
true
arraysEqual([1,2,3], [1,2,3,4])
false
arraysEqual([[1,2],[3]], [[1,2],[],3])
false
arraysEqual([[1,2],[3]], [[1],[2,3]])
false
arraysEqual([[1,2],3], undefined)
false
arraysEqual(undefined, undefined)
true
arraysEqual(1, 2)
false
arraysEqual(null, null)
true
arraysEqual(1, 1)
true
arraysEqual([], 1)
false
arraysEqual([], undefined)
false
arraysEqual([], [])
true
/*
If you wanted to apply this to JSON-like data structures with js Objects, you could do so. Fortunately we're guaranteed that all objects keys are unique, so iterate over the objects OwnProperties and sort them by key, then assert that both the sorted key-array is equal and the value-array are equal, and just recurse. We CANNOT extend the sort-then-compare method with Maps as well; even though Map keys are unique, there is no total ordering in ecmascript, so you can't sort them... but you CAN query them individually (see the next section Option 4). (Also if we extend this to Sets, we run into the tree isomorphism problem http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf - fortunately it's not as hard as general graph isomorphism; there is in fact an O(#vertices) algorithm to solve it, but it can get very complicated to do it efficiently. The pathological case is if you have a set made up of lots of seemingly-indistinguishable objects, but upon further inspection some of those objects may differ as you delve deeper into them. You can also work around this by using hashing to reject almost all cases.)
*/
<!--
**edit**: It's 2016 and my previous overcomplicated answer was bugging me. This recursive, imperative "recursive programming 101" implementation keeps the code really simple, and furthermore fails at the earliest possible point (giving us efficiency). It also doesn't generate superfluous ephemeral datastructures (not that there's anything wrong with functional programming in general, but just keeping it clean here).
If we wanted to apply this to a non-empty arrays of arrays, we could do seriesOfArrays.reduce(arraysEqual).
This is its own function, as opposed to using Object.defineProperties to attach to Array.prototype, since that would fail with a key error if we passed in an undefined value (that is however a fine design decision if you want to do so).
This only answers OPs original question.
-->
Option 4:
(continuation of 2016 edit)
This should work with most objects:
const STRICT_EQUALITY_BROKEN = (a,b)=> a===b;
const STRICT_EQUALITY_NO_NAN = (a,b)=> {
if (typeof a=='number' && typeof b=='number' && ''+a=='NaN' && ''+b=='NaN')
// isNaN does not do what you think; see +/-Infinity
return true;
else
return a===b;
};
function deepEquals(a,b, areEqual=STRICT_EQUALITY_NO_NAN, setElementsAreEqual=STRICT_EQUALITY_NO_NAN) {
/* compares objects hierarchically using the provided
notion of equality (defaulting to ===);
supports Arrays, Objects, Maps, ArrayBuffers */
if (a instanceof Array && b instanceof Array)
return arraysEqual(a,b, areEqual);
if (Object.getPrototypeOf(a)===Object.prototype && Object.getPrototypeOf(b)===Object.prototype)
return objectsEqual(a,b, areEqual);
if (a instanceof Map && b instanceof Map)
return mapsEqual(a,b, areEqual);
if (a instanceof Set && b instanceof Set) {
if (setElementsAreEqual===STRICT_EQUALITY_NO_NAN)
return setsEqual(a,b);
else
throw "Error: set equality by hashing not implemented because cannot guarantee custom notion of equality is transitive without programmer intervention."
}
if ((a instanceof ArrayBuffer || ArrayBuffer.isView(a)) && (b instanceof ArrayBuffer || ArrayBuffer.isView(b)))
return typedArraysEqual(a,b);
return areEqual(a,b); // see note[1] -- IMPORTANT
}
function arraysEqual(a,b, areEqual) {
if (a.length!=b.length)
return false;
for(var i=0; i<a.length; i++)
if (!deepEquals(a[i],b[i], areEqual))
return false;
return true;
}
function objectsEqual(a,b, areEqual) {
var aKeys = Object.getOwnPropertyNames(a);
var bKeys = Object.getOwnPropertyNames(b);
if (aKeys.length!=bKeys.length)
return false;
aKeys.sort();
bKeys.sort();
for(var i=0; i<aKeys.length; i++)
if (!areEqual(aKeys[i],bKeys[i])) // keys must be strings
return false;
return deepEquals(aKeys.map(k=>a[k]), aKeys.map(k=>b[k]), areEqual);
}
function mapsEqual(a,b, areEqual) { // assumes Map's keys use the '===' notion of equality, which is also the assumption of .has and .get methods in the spec; however, Map's values use our notion of the areEqual parameter
if (a.size!=b.size)
return false;
return [...a.keys()].every(k=>
b.has(k) && deepEquals(a.get(k), b.get(k), areEqual)
);
}
function setsEqual(a,b) {
// see discussion in below rest of StackOverflow answer
return a.size==b.size && [...a.keys()].every(k=>
b.has(k)
);
}
function typedArraysEqual(a,b) {
// we use the obvious notion of equality for binary data
a = new Uint8Array(a);
b = new Uint8Array(b);
if (a.length != b.length)
return false;
for(var i=0; i<a.length; i++)
if (a[i]!=b[i])
return false;
return true;
}
Demo (not extensively tested):
var nineTen = new Float32Array(2);
nineTen[0]=9; nineTen[1]=10;
> deepEquals(
[[1,[2,3]], 4, {a:5,'111':6}, new Map([['c',7],['d',8]]), nineTen],
[[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen]
)
true
> deepEquals(
[[1,[2,3]], 4, {a:'5','111':6}, new Map([['c',7],['d',8]]), nineTen],
[[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen],
(a,b)=>a==b
)
true
Note that if one is using the == notion of equality, then know that falsey values and coercion means that == equality is NOT TRANSITIVE. For example ''==0 and 0=='0' but ''!='0'. This is relevant for Sets: I do not think one can override the notion of Set equality in a meaningful way. If one is using the built-in notion of Set equality (that is, ===), then the above should work. However if one uses a non-transitive notion of equality like ==, you open a can of worms: Even if you forced the user to define a hash function on the domain (hash(a)!=hash(b) implies a!=b) I'm not sure that would help... Certainly one could do the O(N^2) performance thing and remove pairs of == items one by one like a bubble sort, and then do a second O(N^2) pass to confirm things in equivalence classes are actually == to each other, and also != to everything not thus paired, but you'd STILL have to throw a runtime error if you have some coercion going on... You'd also maybe get weird (but potentially not that weird) edge cases with https://developer.mozilla.org/en-US/docs/Glossary/Falsy and Truthy values (with the exception that NaN==NaN... but just for Sets!). This is not an issue usually with most Sets of homogenous datatype.
To summarize the complexity of recursive equality on Sets:
Set equality is the tree isomorphism problem http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf but a bit simpler
set A =? set B being synonymous with B.has(k) for every k in A implicitly uses ===-equality ([1,2,3]!==[1,2,3]), not recursive equality (deepEquals([1,2,3],[1,2,3]) == true), so two new Set([[1,2,3]]) would not be equal because we don't recurse
trying to get recursive equality to work is kind of meaningless if the recursive notion of equality you use is not 1) reflexive (a=b implies b=a) and 2) symmetric (a=a) and 3) transitive (a=b and b=c implies a=c); this is the definition of an equivalence class
the equality == operator obviously does not obey many of these properties
even the strict equality === operator in ecmascript
does not obey these properties, because the strict equality comparison algorithm of ecmascript has NaN!=NaN; this is why many native datatypes like Set and Map 'equate' NaNs to consider them the same values when they appear as keys
As long as we force and ensure recursive set equality is indeed transitive and reflexive and symmetric, we can make sure nothing horribly wrong happens.
Then, we can do O(N^2) comparisons by recursively comparing everything randomly, which is incredibly inefficient. There is no magical algorithm that lets us do setKeys.sort((a,b)=> /*some comparison function*/) because there is no total ordering in ecmascript (''==0 and 0=='0', but ''!='0'... though I believe you might be able to define one yourself which would certainly be a lofty goal).
We can however .toStringify or JSON.stringify all elements to assist us. We will then sort them, which gives us equivalence classes (two same things won't not have the same string JSON representation) of potentially-false-positives (two different things may have the same string or JSON representation).
However, this introduces its own performance issues because serializing the same thing, then serializing subsets of that thing, over and over, is incredibly inefficient. Imagine a tree of nested Sets; every node would belong to O(depth) different serializations!
Even if that was not an issue, the worst-case performance would still be O(N!) if all the serializations 'hints' were the same
Thus, the above implementation declares that Sets are equal if the items are just plain === (not recursively ===). This will mean that it will return false for new Set([1,2,3]) and new Set([1,2,3]). With a bit of effort, you may rewrite that part of the code if you know what you're doing.
(sidenote: Maps are es6 dictionaries. I can't tell if they have O(1) or O(log(N)) lookup performance, but in any case they are 'ordered' in the sense that they keep track of the order in which key-value pairs were inserted into them. However, the semantic of whether two Maps should be equal if elements were inserted in a different order into them is ambiguous. I give a sample implementation below of a deepEquals that considers two maps equal even if elements were inserted into them in a different order.)
(note [1]: IMPORTANT: NOTION OF EQUALITY: You may want to override the noted line with a custom notion of equality, which you'll also have to change in the other functions anywhere it appears. For example, do you or don't you want NaN==NaN? By default this is not the case. There are even more weird things like 0=='0'. Do you consider two objects to be the same if and only if they are the same object in memory? See https://stackoverflow.com/a/5447170/711085 . You should document the notion of equality you use.) Also note that other answers which naively use .toString and .sort may sometimes fall pray to the fact that 0!=-0 but are considered equal and canonicalizable to 0 for almost all datatypes and JSON serialization; whether -0==0 should also be documented in your notion of equality, as well as most other things in that table like NaN, etc.
You should be able to extend the above to WeakMaps, WeakSets. Not sure if it makes sense to extend to DataViews. Should also be able to extend to RegExps probably, etc.
As you extend it, you realize you do lots of unnecessary comparisons. This is where the type function that I defined way earlier (solution #2) can come in handy; then you can dispatch instantly. Whether that is worth the overhead of (possibly? not sure how it works under the hood) string representing the type is up to you. You can just then rewrite the dispatcher, i.e. the function deepEquals, to be something like:
var dispatchTypeEquals = {
number: function(a,b) {...a==b...},
array: function(a,b) {...deepEquals(x,y)...},
...
}
function deepEquals(a,b) {
var typeA = extractType(a);
var typeB = extractType(a);
return typeA==typeB && dispatchTypeEquals[typeA](a,b);
}
jQuery does not have a method for comparing arrays. However the Underscore library (or the comparable Lodash library) does have such a method: isEqual, and it can handle a variety of other cases (like object literals) as well. To stick to the provided example:
var a=[1,2,3];
var b=[3,2,1];
var c=new Array(1,2,3);
alert(_.isEqual(a, b) + "|" + _.isEqual(b, c));
By the way: Underscore has lots of other methods that jQuery is missing as well, so it's a great complement to jQuery.
EDIT: As has been pointed out in the comments, the above now only works if both arrays have their elements in the same order, ie.:
_.isEqual([1,2,3], [1,2,3]); // true
_.isEqual([1,2,3], [3,2,1]); // false
Fortunately Javascript has a built in method for for solving this exact problem, sort:
_.isEqual([1,2,3].sort(), [3,2,1].sort()); // true
For primitive values like numbers and strings this is an easy solution:
a = [1,2,3]
b = [3,2,1]
a.sort().toString() == b.sort().toString()
The call to sort() will ensure that the order of the elements does not matter. The toString() call will create a string with the values comma separated so both strings can be tested for equality.
With JavaScript version 1.6 it's as easy as this:
Array.prototype.equals = function( array ) {
return this.length == array.length &&
this.every( function(this_i,i) { return this_i == array[i] } )
}
For example, [].equals([]) gives true, while [1,2,3].equals( [1,3,2] ) yields false.
Even if this would seem super simple, sometimes it's really useful. If all you need is to see if two arrays have the same items and they are in the same order, try this:
[1, 2, 3].toString() == [1, 2, 3].toString()
true
[1, 2, 3,].toString() == [1, 2, 3].toString()
true
[1,2,3].toString() == [1, 2, 3].toString()
true
However, this doesn't work for mode advanced cases such as:
[[1,2],[3]].toString() == [[1],[2,3]].toString()
true
It depends what you need.
Based on Tim James answer and Fox32's comment, the following should check for nulls, with the assumption that two nulls are not equal.
function arrays_equal(a,b) { return !!a && !!b && !(a<b || b<a); }
> arrays_equal([1,2,3], [1,3,4])
false
> arrays_equal([1,2,3], [1,2,3])
true
> arrays_equal([1,3,4], [1,2,3])
false
> arrays_equal(null, [1,2,3])
false
> arrays_equal(null, null)
false
jQuery has such method for deep recursive comparison.
A homegrown general purpose strict equality check could look as follows:
function deepEquals(obj1, obj2, parents1, parents2) {
"use strict";
var i;
// compare null and undefined
if (obj1 === undefined || obj2 === undefined ||
obj1 === null || obj2 === null) {
return obj1 === obj2;
}
// compare primitives
if (typeof (obj1) !== 'object' || typeof (obj2) !== 'object') {
return obj1.valueOf() === obj2.valueOf();
}
// if objects are of different types or lengths they can't be equal
if (obj1.constructor !== obj2.constructor || (obj1.length !== undefined && obj1.length !== obj2.length)) {
return false;
}
// iterate the objects
for (i in obj1) {
// build the parents list for object on the left (obj1)
if (parents1 === undefined) parents1 = [];
if (obj1.constructor === Object) parents1.push(obj1);
// build the parents list for object on the right (obj2)
if (parents2 === undefined) parents2 = [];
if (obj2.constructor === Object) parents2.push(obj2);
// walk through object properties
if (obj1.propertyIsEnumerable(i)) {
if (obj2.propertyIsEnumerable(i)) {
// if object at i was met while going down here
// it's a self reference
if ((obj1[i].constructor === Object && parents1.indexOf(obj1[i]) >= 0) || (obj2[i].constructor === Object && parents2.indexOf(obj2[i]) >= 0)) {
if (obj1[i] !== obj2[i]) {
return false;
}
continue;
}
// it's not a self reference so we are here
if (!deepEquals(obj1[i], obj2[i], parents1, parents2)) {
return false;
}
} else {
// obj2[i] does not exist
return false;
}
}
}
return true;
};
Tests:
// message is displayed on failure
// clean console === all tests passed
function assertTrue(cond, msg) {
if (!cond) {
console.log(msg);
}
}
var a = 'sdf',
b = 'sdf';
assertTrue(deepEquals(b, a), 'Strings are equal.');
b = 'dfs';
assertTrue(!deepEquals(b, a), 'Strings are not equal.');
a = 9;
b = 9;
assertTrue(deepEquals(b, a), 'Numbers are equal.');
b = 3;
assertTrue(!deepEquals(b, a), 'Numbers are not equal.');
a = false;
b = false;
assertTrue(deepEquals(b, a), 'Booleans are equal.');
b = true;
assertTrue(!deepEquals(b, a), 'Booleans are not equal.');
a = null;
assertTrue(!deepEquals(b, a), 'Boolean is not equal to null.');
a = function () {
return true;
};
assertTrue(deepEquals(
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': 1.0
},
true]
],
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': 1.0
},
true]
]), 'Arrays are equal.');
assertTrue(!deepEquals(
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': 1.0
},
true]
],
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': '1'
},
true]
]), 'Arrays are not equal.');
a = {
prop: 'val'
};
a.self = a;
b = {
prop: 'val'
};
b.self = a;
assertTrue(deepEquals(b, a), 'Immediate self referencing objects are equal.');
a.prop = 'shmal';
assertTrue(!deepEquals(b, a), 'Immediate self referencing objects are not equal.');
a = {
prop: 'val',
inside: {}
};
a.inside.self = a;
b = {
prop: 'val',
inside: {}
};
b.inside.self = a;
assertTrue(deepEquals(b, a), 'Deep self referencing objects are equal.');
b.inside.self = b;
assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equeal. Not the same instance.');
b.inside.self = {foo: 'bar'};
assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equal. Completely different object.');
a = {};
b = {};
a.self = a;
b.self = {};
assertTrue(!deepEquals(b, a), 'Empty object and self reference of an empty object.');
If you are using lodash and don't want to modify either array, you can use the function _.xor(). It compares the two arrays as sets and returns the set that contains their difference. If the length of this difference is zero, the two arrays are essentially equal:
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
_.xor(a, b).length === 0
true
_.xor(b, c).length === 0
true
Check every each value by a for loop once you checked the size of the array.
function equalArray(a, b) {
if (a.length === b.length) {
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
} else {
return false;
}
}
Using map() and reduce():
function arraysEqual (a1, a2) {
return a1 === a2 || (
a1 !== null && a2 !== null &&
a1.length === a2.length &&
a1
.map(function (val, idx) { return val === a2[idx]; })
.reduce(function (prev, cur) { return prev && cur; }, true)
);
}
If you wish to check arrays of objects for equality and order does NOT matter, i.e.
areEqual([{id: "0"}, {id: "1"}], [{id: "1"}, {id: "0"}]) // true
you'll want to sort the arrays first. lodash has all the tools you'll need, by combining sortBy and isEqual:
// arr1 & arr2: Arrays of objects
// sortProperty: the property of the object with which you want to sort
// Note: ensure every object in both arrays has your chosen sortProperty
// For example, arr1 = [{id: "v-test_id0"}, {id: "v-test_id1"}]
// and arr2 = [{id: "v-test_id1"}, {id: "v-test_id0"}]
// sortProperty should be 'id'
function areEqual (arr1, arr2, sortProperty) {
return _.areEqual(_.sortBy(arr1, sortProperty), _.sortBy(arr2, sortProperty))
}
EDIT: Since sortBy returns a new array, there is no need to clone your arrays before sorting. The original arrays will not be mutated.
Note that for lodash's isEqual, order does matter. The above example will return false if sortBy is not applied to each array first.
This method sucks, but I've left it here for reference so others avoid this path:
Using Option 1 from #ninjagecko worked best for me:
Array.prototype.equals = function(array) {
return array instanceof Array && JSON.stringify(this) === JSON.stringify(array) ;
}
a = [1, [2, 3]]
a.equals([[1, 2], 3]) // false
a.equals([1, [2, 3]]) // true
It will also handle the null and undefined case, since we're adding this to the prototype of array and checking that the other argument is also an array.
There is no easy way to do this. I needed this as well, but wanted a function that can take any two variables and test for equality. That includes non-object values, objects, arrays and any level of nesting.
In your question, you mention wanting to ignore the order of the values in an array. My solution doesn't inherently do that, but you can achieve it by sorting the arrays before comparing for equality
I also wanted the option of casting non-objects to strings so that [1,2]===["1",2]
Since my project uses UnderscoreJs, I decided to make it a mixin rather than a standalone function.
You can test it out on http://jsfiddle.net/nemesarial/T44W4/
Here is my mxin:
_.mixin({
/**
Tests for the equality of two variables
valA: first variable
valB: second variable
stringifyStatics: cast non-objects to string so that "1"===1
**/
equal:function(valA,valB,stringifyStatics){
stringifyStatics=!!stringifyStatics;
//check for same type
if(typeof(valA)!==typeof(valB)){
if((_.isObject(valA) || _.isObject(valB))){
return false;
}
}
//test non-objects for equality
if(!_.isObject(valA)){
if(stringifyStatics){
var valAs=''+valA;
var valBs=''+valB;
ret=(''+valA)===(''+valB);
}else{
ret=valA===valB;
}
return ret;
}
//test for length
if(_.size(valA)!=_.size(valB)){
return false;
}
//test for arrays first
var isArr=_.isArray(valA);
//test whether both are array or both object
if(isArr!==_.isArray(valB)){
return false;
}
var ret=true;
if(isArr){
//do test for arrays
_.each(valA,function(val,idx,lst){
if(!ret){return;}
ret=ret && _.equal(val,valB[idx],stringifyStatics);
});
}else{
//do test for objects
_.each(valA,function(val,idx,lst){
if(!ret){return;}
//test for object member exists
if(!_.has(valB,idx)){
ret=false;
return;
}
// test for member equality
ret=ret && _.equal(val,valB[idx],stringifyStatics);
});
}
return ret;
}
});
This is how you use it:
_.equal([1,2,3],[1,2,"3"],true)
To demonstrate nesting, you can do this:
_.equal(
['a',{b:'b',c:[{'someId':1},2]},[1,2,3]],
['a',{b:'b',c:[{'someId':"1"},2]},["1",'2',3]]
,true);
It handle all possible stuff and even reference itself in structure of object. You can see the example at the end of code.
var deepCompare = (function() {
function internalDeepCompare (obj1, obj2, objects) {
var i, objPair;
if (obj1 === obj2) {
return true;
}
i = objects.length;
while (i--) {
objPair = objects[i];
if ( (objPair.obj1 === obj1 && objPair.obj2 === obj2) ||
(objPair.obj1 === obj2 && objPair.obj2 === obj1) ) {
return true;
}
}
objects.push({obj1: obj1, obj2: obj2});
if (obj1 instanceof Array) {
if (!(obj2 instanceof Array)) {
return false;
}
i = obj1.length;
if (i !== obj2.length) {
return false;
}
while (i--) {
if (!internalDeepCompare(obj1[i], obj2[i], objects)) {
return false;
}
}
}
else {
switch (typeof obj1) {
case "object":
// deal with null
if (!(obj2 && obj1.constructor === obj2.constructor)) {
return false;
}
if (obj1 instanceof RegExp) {
if (!(obj2 instanceof RegExp && obj1.source === obj2.source)) {
return false;
}
}
else if (obj1 instanceof Date) {
if (!(obj2 instanceof Date && obj1.getTime() === obj2.getTime())) {
return false;
}
}
else {
for (i in obj1) {
if (obj1.hasOwnProperty(i)) {
if (!(obj2.hasOwnProperty(i) && internalDeepCompare(obj1[i], obj2[i], objects))) {
return false;
}
}
}
}
break;
case "function":
if (!(typeof obj2 === "function" && obj1+"" === obj2+"")) {
return false;
}
break;
default: //deal with NaN
if (obj1 !== obj2 && obj1 === obj1 && obj2 === obj2) {
return false;
}
}
}
return true;
}
return function (obj1, obj2) {
return internalDeepCompare(obj1, obj2, []);
};
}());
/*
var a = [a, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null],
b = [b, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null];
deepCompare(a, b);
*/
var a= [1, 2, 3, '3'];
var b = [1, 2, 3];
var c = a.filter(function (i) { return ! ~b.indexOf(i); });
alert(c.length);

Comparing 2 JSON objects structure in JavaScript

I am using angular-translate for a big application. Having several people committing code + translations, many times the translation objects are not in sync.
I am building a Grunt plugin to look at both files' structure and compare it (just the keys and overall structure, not values).
The main goals are:
Look into each file, and check if the structure of the whole object
(or file, in this case) is the exact same as the translated ones;
On error, return the key that doesn't match.
It turns out it was a bit more complicated than I anticipated. So i figured I could do something like:
Sort the object;
Check the type of data the value contains (since they are translations, it will only have strings, or objects for the nestings) and store it in another object, making the key equal to the original key and the value would be a string 'String', or an object in case it's an object. That object contains the children elements;
Recursively repeat steps 1-2 until the whole object is mapped and sorted;
Do the same for all the files
Stringify and compare everything.
A tiny example would be the following object:
{
key1: 'cool',
key2: 'cooler',
keyWhatever: {
anotherObject: {
key1: 'better',
keyX: 'awesome'
},
aObject: 'actually, it\'s a string'
},
aKey: 'more awesomeness'
}
would map to:
{
aKey: 'String',
key1: 'String',
key2: 'String',
keyWhatever: {
aObject: 'String',
anotherObject: {
key1: 'String',
keyX: 'String'
}
}
}
After this, I would stringify all the objects and proceed with a strict comparison.
My question is, is there a better way to perform this? Both in terms of simplicity and performance, since there are many translation files and they are fairly big.
I tried to look for libraries that would already do this, but I couldn't find any.
Thank you
EDIT: Thank you Jared for pointing out objects can't be sorted. I am ashamed for saying something like that :D Another solution could be iterating each of the properties on the main translation file, and in case they are strings, compare the key with the other files. In case they are objects, "enter" them, and do the same. Maybe it is even simpler than my first guess. What should be done?
Lets say you have two JSON objects, jsonA and jsonB.
function compareValues(a, b) {
//if a and b aren't the same type, they can't be equal
if (typeof a !== typeof b) {
return false;
}
// Need the truthy guard because
// typeof null === 'object'
if (a && typeof a === 'object') {
var keysA = Object.keys(a).sort(),
keysB = Object.keys(b).sort();
//if a and b are objects with different no of keys, unequal
if (keysA.length !== keysB.length) {
return false;
}
//if keys aren't all the same, unequal
if (!keysA.every(function(k, i) { return k === keysB[i];})) {
return false;
}
//recurse on the values for each key
return keysA.every(function(key) {
//if we made it here, they have identical keys
return compareValues(a[key], b[key]);
});
//for primitives just use a straight up check
} else {
return a === b;
}
}
//true if their structure, values, and keys are identical
var passed = compareValues(jsonA, jsonB);
Note that this can overflow the stack for deeply nested JSON objects. Note also that this will work for JSON but not necessarily regular JS objects as special handling is needed for Date Objects, Regexes, etc.
Actually you do need to sort the keys, as they are not required to be spit out in any particular order. Write a function,
function getComparableForObject(obj) {
var keys = Object.keys(obj);
keys.sort(a, b => a > b ? 1 : -1);
var comparable = keys.map(
key => key + ":" + getValueRepresentation(obj[key])
).join(",");
return "{" + comparable + "}";
}
Where getValueRepresentation is a function that either returns "String" or calls getComparableForObject. If you are worried about circular references, add a Symbol to the outer scope, repr, assign obj[repr] = comparable in the function above, and in getValueRepresentation check every object for a defined obj[repr] and return it instead of processing it recursively.
Sorting an array of the keys from the object works. However, sorting has an average time complexity of O(nâ‹…log(n)). We can do better. A fast general algorithm for ensuring two sets A and B are equivalent is as follows:
for item in B
if item in A
remove item from A
else
sets are not equivalent
sets are equivalent iff A is empty
To address #Katana31, we can detect circular references as we go by maintaining a set of visited objects and ensuring that all descendents of that object are not already in the list:
# poorly written pseudo-code
fn detectCycles(A, found = {})
if A in found
there is a cycle
else
found = clone(found)
add A to found
for child in A
detectCycles(child, found)
Here's a complete implementation (you can find a simplified version that assumes JSON/non-circular input here):
var hasOwn = Object.prototype.hasOwnProperty;
var indexOf = Array.prototype.indexOf;
function isObjectEmpty(obj) {
for (var key in obj) {
return false;
}
return true;
}
function copyKeys(obj) {
var newObj = {};
for (var key in obj) {
newObj[key] = undefined;
}
return newObj;
}
// compares the structure of arbitrary values
function compareObjectStructure(a, b) {
return function innerCompare(a, b, pathA, pathB) {
if (typeof a !== typeof b) {
return false;
}
if (typeof a === 'object') {
// both or neither, but not mismatched
if (Array.isArray(a) !== Array.isArray(b)) {
return false;
}
if (indexOf.call(pathA, a) !== -1 || indexOf.call(pathB, b) !== -1) {
return false;
}
pathA = pathA.slice();
pathA.push(a);
pathB = pathB.slice();
pathB.push(b);
if (Array.isArray(a)) {
// can't compare structure in array if we don't have items in both
if (!a.length || !b.length) {
return true;
}
for (var i = 1; i < a.length; i++) {
if (!innerCompare(a[0], a[i], pathA, pathA)) {
return false;
}
}
for (var i = 0; i < b.length; i++) {
if (!innerCompare(a[0], b[i], pathA, pathB)) {
return false;
}
}
return true;
}
var map = copyKeys(a), keys = Object.keys(b);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
if (!hasOwn.call(map, key) || !innerCompare(a[key], b[key], pathA,
pathB)) {
return false;
}
delete map[key];
}
// we should've found all the keys in the map
return isObjectEmpty(map);
}
return true;
}(a, b, [], []);
}
Note that this implementation directly compares two objects for structural equivalency, but doesn't reduce the objects to a directly comparable value (like a string). I haven't done any performance testing, but I suspect that it won't add significant value, though it will remove the need to repeatedly ensure objects are non-cyclic. For that reason, you could easily split compareObjectStructure into two functions - one to compare the structure and one to check for cycles.

Javascript - Determine if any scoped references in object

I execute some function and get a return value. This value could be anything (string, array, object, reference, function). I then pass this result along using JSON.stringify.
Now, the functions and references don't do me much good in the scope they're being delivered to, so the whole "to string, eval" method isn't much use. I'm going to store them in a local array and just pass along an ID to reference them by later. But, I do go ahead and send string data, arrays, and objects (in the "associated array" sense of javascript objects) as those all play very nicely with JSON.stringify.
I'm already using try... JSON.stringify() catch to do this with recursive objects (which JSON.stringify errors on.) But that doesn't account for anything else mentioned above.
What is the most efficient way to check if an value contains a function?
And not
typeof foo === "function"
Because the return might be
["foo", "bar", ["foo", "bar"], function(){...something}]
I don't want to pick apart each individual piece's type either, just return on the whole whether there's ANY functions/objects that cannot be safely stringified. I could probably work out how to loop and check each individual value, but if there's a shortcut or more efficient method someone can think of, I'd like to hear it.
Thanks!
Refining welcome and appreciated!
//your favorite object length checking function could go here
$.objectLength = (function(){
//ie<9
if (typeof Object.keys === "undefined" ){
return function(o){
var count = 0, i;
for (i in o) {
if (o.hasOwnProperty(i)) {
count++;
}
}
return count;
};
//everyone else
} else {
return function(o){
return Object.keys(o).length;
}
}
})();
//comparing our two objects
$.checkMatch = function(a, b){
//if they're not the same length, we're done here. (functions exist, etc)
if (typeof a !== typeof b || typeof a === "object" && typeof b === "object" && a && b && $.objectLength(a) !== $.objectLength(b)){
return false;
//if they are the same length, they may contain deeper objects we need to check.
} else {
var key;
for (key in a){
//make sure it's not prototyped key
if (a.hasOwnProperty(key)){
//if it doesn't exist on the other object
if (!b.hasOwnProperty(key)){
return false;
//if this an object as well
} else if (typeof a[key] === "object"){
//check for a match
if (!$.checkMatch(a[key], b[key])){
return false;
}
//then check if they're not equal (simple values)
} else if (a[key] !== b[key]){
return false
}
}
}
return true;
}
};
//...stuff
//catch recursive objects in parameters
var good = true, sendObject = {"ourobject", "values"}, finalSendObject;
//try to stringify, which rejects infinitely recursive objects
try {
finalSendObject = JSON.stringify(sendObject);
} catch(e){
good = false;
}
//if that passes, try matching the original against the parsed JSON string
if (good && sendObject !== JSON.parse(finalSendObject)){
good = $.checkMatch(sendObject, JSON.parse(finalSendObject));
}
This will not work
But I will leave it up for anyone who thinks of trying it. Working solution coming shortly.
30 seconds later, I figure it out myself.
String it, parse it. If anything changed, it wont be equal to itself.
var checkParse = function(obj){
return obj === JSON.parse(JSON.strigify(obj));
}

JavaScript: Testing primitives for equality

Let's say I have two objects that only have primitives as properties for members (e.g. the object has no functions or object members):
var foo = {
start: 9,
end: 11
};
var bar = {
start: 9,
end: 11
};
Given two objects like this, I want to know if all their members have the same values.
Right now I'm simpling doing:
if (foo.start === bar.start && foo.end == bar.end) {
// same member values
}
But I'm going to have to work with objects that may have dozens of these primitive members.
Is there anything built into JavaScript to easily allow me to compare them? What's the easiest way to compare all their values?
If both objects are Objects (e.g., created via literal notation [{}] or new Object, not by [say] new Date), you can do it like this:
function primativelyEqual(a, b) {
var name;
for (name in a) {
if (!b.hasOwnProperty(name) || b[name] !== a[name]) {
// `b` doesn't have it or it's not the same
return false;
}
}
for (name in b) {
if (!a.hasOwnProperty(name)) {
// `a` doesn't have it
return false;
}
}
// All properties in both objects are present in the other,
// and have the same value down to the type
return true;
}
for..in iterates over the names of the properties of an object. hasOwnProperty tells you whether the instance itself (as opposed to a member of its prototype chain) has that property. !== checks for any inequality between two values without doing any type coercion. By looping through the names of the properties of both objects, you know that they have the same number of entries.
You can shortcut this a bit if the implementation has the new Object.keys feature from ECMAScript5:
function primativelyEqual(a, b) {
var name, checkedKeys;
checkedKeys = typeof Object.keys === "function";
if (checkedKeys && Object.keys(a).length !== Object.keys(b).length) {
// They don't have the same number of properties
return false;
}
for (name in a) {
if (!b.hasOwnProperty(name) || b[name] !== a[name]) {
// `b` doesn't have it or it's not the same
return false;
}
}
if (!checkedKeys) {
// Couldn't check for equal numbers of keys before
for (name in b) {
if (!a.hasOwnProperty(name)) {
// `a` doesn't have it
return false;
}
}
}
// All properties in both objects are present in the other,
// and have the same value down to the type
return true;
}
Live example
But both versions of the above assume that the objects don't inherit any enumerable properties from their prototypes (hence my opening statement about their being Objects). (I'm also assuming no one's added anything to Object.prototype, which is an insane thing people learned very quickly not to do.)
Definitely possible to refine that more to generalize it, and even make it descend into object properties by the same definition, but within the bounds of what you described (and within the bounds of most reasonable deployments), that should be fine.
You can use a for in loop to loop through every property in an object.
For example:
function areEqual(a, b) {
for (var prop in a)
if (a.hasOwnProperty(prop) && a[prop] !== b[prop])
return false;
return true;
}
Any properties in b but not a will be ignored.
Y'know, something's just occurred to me. Basic object literals are called JSON. The easiest way to compare those two objects is
function equalObjects(obj1, obj2) {
return JSON.stringify(obj1) === JSON.stringify(obj2);
}
If you need to use this in a browser that doesn't have native JSON support, you can use the open source JSON scripts

How to check if two arrays are equal with JavaScript? [duplicate]

This question already has answers here:
How to compare arrays in JavaScript?
(55 answers)
Closed 6 years ago.
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
alert(a == b + "|" + b == c);
demo
How can I check these array for equality and get a method which returns true if they are equal?
Does jQuery offer any method for this?
This is what you should do. Please do not use stringify nor < >.
function arraysEqual(a, b) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
// If you don't care about the order of the elements inside
// the array, you should sort both arrays here.
// Please note that calling sort on an array will modify that array.
// you might want to clone your array first.
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
[2021 changelog: bugfix for option4: no total ordering on js objects (even excluding NaN!=NaN and '5'==5 ('5'===5, '2'<3, etc.)), so cannot use .sort(cmpFunc) on Map.keys() (though you can on Object.keys(obj), since even 'numerical' keys are strings).]
Option 1
Easiest option, works in almost all cases, except that null!==undefined but they both are converted to JSON representation null and considered equal:
function arraysEqual(a1,a2) {
/* WARNING: arrays must not contain {objects} or behavior may be undefined */
return JSON.stringify(a1)==JSON.stringify(a2);
}
(This might not work if your array contains objects. Whether this still works with objects depends on whether the JSON implementation sorts keys. For example, the JSON of {1:2,3:4} may or may not be equal to {3:4,1:2}; this depends on the implementation, and the spec makes no guarantee whatsoever. [2017 update: Actually the ES6 specification now guarantees object keys will be iterated in order of 1) integer properties, 2) properties in the order they were defined, then 3) symbol properties in the order they were defined. Thus IF the JSON.stringify implementation follows this, equal objects (in the === sense but NOT NECESSARILY in the == sense) will stringify to equal values. More research needed. So I guess you could make an evil clone of an object with properties in the reverse order, but I cannot imagine it ever happening by accident...] At least on Chrome, the JSON.stringify function tends to return keys in the order they were defined (at least that I've noticed), but this behavior is very much subject to change at any point and should not be relied upon. If you choose not to use objects in your lists, this should work fine. If you do have objects in your list that all have a unique id, you can do a1.map(function(x)}{return {id:x.uniqueId}}). If you have arbitrary objects in your list, you can read on for option #2.)
This works for nested arrays as well.
It is, however, slightly inefficient because of the overhead of creating these strings and garbage-collecting them.
Option 2
Historical, version 1 solution:
// generally useful functions
function type(x) { // does not work in general, but works on JSONable objects we care about... modify as you see fit
// e.g. type(/asdf/g) --> "[object RegExp]"
return Object.prototype.toString.call(x);
}
function zip(arrays) {
// e.g. zip([[1,2,3],[4,5,6]]) --> [[1,4],[2,5],[3,6]]
return arrays[0].map(function(_,i){
return arrays.map(function(array){return array[i]})
});
}
// helper functions
function allCompareEqual(array) {
// e.g. allCompareEqual([2,2,2,2]) --> true
// does not work with nested arrays or objects
return array.every(function(x){return x==array[0]});
}
function isArray(x){ return type(x)==type([]) }
function getLength(x){ return x.length }
function allTrue(array){ return array.reduce(function(a,b){return a&&b},true) }
// e.g. allTrue([true,true,true,true]) --> true
// or just array.every(function(x){return x});
function allDeepEqual(things) {
// works with nested arrays
if( things.every(isArray) )
return allCompareEqual(things.map(getLength)) // all arrays of same length
&& allTrue(zip(things).map(allDeepEqual)); // elements recursively equal
//else if( this.every(isObject) )
// return {all have exactly same keys, and for
// each key k, allDeepEqual([o1[k],o2[k],...])}
// e.g. ... && allTrue(objectZip(objects).map(allDeepEqual))
//else if( ... )
// extend some more
else
return allCompareEqual(things);
}
// Demo:
allDeepEqual([ [], [], [] ])
true
allDeepEqual([ [1], [1], [1] ])
true
allDeepEqual([ [1,2], [1,2] ])
true
allDeepEqual([ [[1,2],[3]], [[1,2],[3]] ])
true
allDeepEqual([ [1,2,3], [1,2,3,4] ])
false
allDeepEqual([ [[1,2],[3]], [[1,2],[],3] ])
false
allDeepEqual([ [[1,2],[3]], [[1],[2,3]] ])
false
allDeepEqual([ [[1,2],3], [1,[2,3]] ])
false
<!--
More "proper" option, which you can override to deal with special cases (like regular objects and null/undefined and custom objects, if you so desire):
To use this like a regular function, do:
function allDeepEqual2() {
return allDeepEqual([].slice.call(arguments));
}
Demo:
allDeepEqual2([[1,2],3], [[1,2],3])
true
-->
Option 3
function arraysEqual(a,b) {
/*
Array-aware equality checker:
Returns whether arguments a and b are == to each other;
however if they are equal-lengthed arrays, returns whether their
elements are pairwise == to each other recursively under this
definition.
*/
if (a instanceof Array && b instanceof Array) {
if (a.length!=b.length) // assert same length
return false;
for(var i=0; i<a.length; i++) // assert each element equal
if (!arraysEqual(a[i],b[i]))
return false;
return true;
} else {
return a==b; // if not both arrays, should be the same
}
}
//Examples:
arraysEqual([[1,2],3], [[1,2],3])
true
arraysEqual([1,2,3], [1,2,3,4])
false
arraysEqual([[1,2],[3]], [[1,2],[],3])
false
arraysEqual([[1,2],[3]], [[1],[2,3]])
false
arraysEqual([[1,2],3], undefined)
false
arraysEqual(undefined, undefined)
true
arraysEqual(1, 2)
false
arraysEqual(null, null)
true
arraysEqual(1, 1)
true
arraysEqual([], 1)
false
arraysEqual([], undefined)
false
arraysEqual([], [])
true
/*
If you wanted to apply this to JSON-like data structures with js Objects, you could do so. Fortunately we're guaranteed that all objects keys are unique, so iterate over the objects OwnProperties and sort them by key, then assert that both the sorted key-array is equal and the value-array are equal, and just recurse. We CANNOT extend the sort-then-compare method with Maps as well; even though Map keys are unique, there is no total ordering in ecmascript, so you can't sort them... but you CAN query them individually (see the next section Option 4). (Also if we extend this to Sets, we run into the tree isomorphism problem http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf - fortunately it's not as hard as general graph isomorphism; there is in fact an O(#vertices) algorithm to solve it, but it can get very complicated to do it efficiently. The pathological case is if you have a set made up of lots of seemingly-indistinguishable objects, but upon further inspection some of those objects may differ as you delve deeper into them. You can also work around this by using hashing to reject almost all cases.)
*/
<!--
**edit**: It's 2016 and my previous overcomplicated answer was bugging me. This recursive, imperative "recursive programming 101" implementation keeps the code really simple, and furthermore fails at the earliest possible point (giving us efficiency). It also doesn't generate superfluous ephemeral datastructures (not that there's anything wrong with functional programming in general, but just keeping it clean here).
If we wanted to apply this to a non-empty arrays of arrays, we could do seriesOfArrays.reduce(arraysEqual).
This is its own function, as opposed to using Object.defineProperties to attach to Array.prototype, since that would fail with a key error if we passed in an undefined value (that is however a fine design decision if you want to do so).
This only answers OPs original question.
-->
Option 4:
(continuation of 2016 edit)
This should work with most objects:
const STRICT_EQUALITY_BROKEN = (a,b)=> a===b;
const STRICT_EQUALITY_NO_NAN = (a,b)=> {
if (typeof a=='number' && typeof b=='number' && ''+a=='NaN' && ''+b=='NaN')
// isNaN does not do what you think; see +/-Infinity
return true;
else
return a===b;
};
function deepEquals(a,b, areEqual=STRICT_EQUALITY_NO_NAN, setElementsAreEqual=STRICT_EQUALITY_NO_NAN) {
/* compares objects hierarchically using the provided
notion of equality (defaulting to ===);
supports Arrays, Objects, Maps, ArrayBuffers */
if (a instanceof Array && b instanceof Array)
return arraysEqual(a,b, areEqual);
if (Object.getPrototypeOf(a)===Object.prototype && Object.getPrototypeOf(b)===Object.prototype)
return objectsEqual(a,b, areEqual);
if (a instanceof Map && b instanceof Map)
return mapsEqual(a,b, areEqual);
if (a instanceof Set && b instanceof Set) {
if (setElementsAreEqual===STRICT_EQUALITY_NO_NAN)
return setsEqual(a,b);
else
throw "Error: set equality by hashing not implemented because cannot guarantee custom notion of equality is transitive without programmer intervention."
}
if ((a instanceof ArrayBuffer || ArrayBuffer.isView(a)) && (b instanceof ArrayBuffer || ArrayBuffer.isView(b)))
return typedArraysEqual(a,b);
return areEqual(a,b); // see note[1] -- IMPORTANT
}
function arraysEqual(a,b, areEqual) {
if (a.length!=b.length)
return false;
for(var i=0; i<a.length; i++)
if (!deepEquals(a[i],b[i], areEqual))
return false;
return true;
}
function objectsEqual(a,b, areEqual) {
var aKeys = Object.getOwnPropertyNames(a);
var bKeys = Object.getOwnPropertyNames(b);
if (aKeys.length!=bKeys.length)
return false;
aKeys.sort();
bKeys.sort();
for(var i=0; i<aKeys.length; i++)
if (!areEqual(aKeys[i],bKeys[i])) // keys must be strings
return false;
return deepEquals(aKeys.map(k=>a[k]), aKeys.map(k=>b[k]), areEqual);
}
function mapsEqual(a,b, areEqual) { // assumes Map's keys use the '===' notion of equality, which is also the assumption of .has and .get methods in the spec; however, Map's values use our notion of the areEqual parameter
if (a.size!=b.size)
return false;
return [...a.keys()].every(k=>
b.has(k) && deepEquals(a.get(k), b.get(k), areEqual)
);
}
function setsEqual(a,b) {
// see discussion in below rest of StackOverflow answer
return a.size==b.size && [...a.keys()].every(k=>
b.has(k)
);
}
function typedArraysEqual(a,b) {
// we use the obvious notion of equality for binary data
a = new Uint8Array(a);
b = new Uint8Array(b);
if (a.length != b.length)
return false;
for(var i=0; i<a.length; i++)
if (a[i]!=b[i])
return false;
return true;
}
Demo (not extensively tested):
var nineTen = new Float32Array(2);
nineTen[0]=9; nineTen[1]=10;
> deepEquals(
[[1,[2,3]], 4, {a:5,'111':6}, new Map([['c',7],['d',8]]), nineTen],
[[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen]
)
true
> deepEquals(
[[1,[2,3]], 4, {a:'5','111':6}, new Map([['c',7],['d',8]]), nineTen],
[[1,[2,3]], 4, {111:6,a:5}, new Map([['d',8],['c',7]]), nineTen],
(a,b)=>a==b
)
true
Note that if one is using the == notion of equality, then know that falsey values and coercion means that == equality is NOT TRANSITIVE. For example ''==0 and 0=='0' but ''!='0'. This is relevant for Sets: I do not think one can override the notion of Set equality in a meaningful way. If one is using the built-in notion of Set equality (that is, ===), then the above should work. However if one uses a non-transitive notion of equality like ==, you open a can of worms: Even if you forced the user to define a hash function on the domain (hash(a)!=hash(b) implies a!=b) I'm not sure that would help... Certainly one could do the O(N^2) performance thing and remove pairs of == items one by one like a bubble sort, and then do a second O(N^2) pass to confirm things in equivalence classes are actually == to each other, and also != to everything not thus paired, but you'd STILL have to throw a runtime error if you have some coercion going on... You'd also maybe get weird (but potentially not that weird) edge cases with https://developer.mozilla.org/en-US/docs/Glossary/Falsy and Truthy values (with the exception that NaN==NaN... but just for Sets!). This is not an issue usually with most Sets of homogenous datatype.
To summarize the complexity of recursive equality on Sets:
Set equality is the tree isomorphism problem http://logic.pdmi.ras.ru/~smal/files/smal_jass08_slides.pdf but a bit simpler
set A =? set B being synonymous with B.has(k) for every k in A implicitly uses ===-equality ([1,2,3]!==[1,2,3]), not recursive equality (deepEquals([1,2,3],[1,2,3]) == true), so two new Set([[1,2,3]]) would not be equal because we don't recurse
trying to get recursive equality to work is kind of meaningless if the recursive notion of equality you use is not 1) reflexive (a=b implies b=a) and 2) symmetric (a=a) and 3) transitive (a=b and b=c implies a=c); this is the definition of an equivalence class
the equality == operator obviously does not obey many of these properties
even the strict equality === operator in ecmascript
does not obey these properties, because the strict equality comparison algorithm of ecmascript has NaN!=NaN; this is why many native datatypes like Set and Map 'equate' NaNs to consider them the same values when they appear as keys
As long as we force and ensure recursive set equality is indeed transitive and reflexive and symmetric, we can make sure nothing horribly wrong happens.
Then, we can do O(N^2) comparisons by recursively comparing everything randomly, which is incredibly inefficient. There is no magical algorithm that lets us do setKeys.sort((a,b)=> /*some comparison function*/) because there is no total ordering in ecmascript (''==0 and 0=='0', but ''!='0'... though I believe you might be able to define one yourself which would certainly be a lofty goal).
We can however .toStringify or JSON.stringify all elements to assist us. We will then sort them, which gives us equivalence classes (two same things won't not have the same string JSON representation) of potentially-false-positives (two different things may have the same string or JSON representation).
However, this introduces its own performance issues because serializing the same thing, then serializing subsets of that thing, over and over, is incredibly inefficient. Imagine a tree of nested Sets; every node would belong to O(depth) different serializations!
Even if that was not an issue, the worst-case performance would still be O(N!) if all the serializations 'hints' were the same
Thus, the above implementation declares that Sets are equal if the items are just plain === (not recursively ===). This will mean that it will return false for new Set([1,2,3]) and new Set([1,2,3]). With a bit of effort, you may rewrite that part of the code if you know what you're doing.
(sidenote: Maps are es6 dictionaries. I can't tell if they have O(1) or O(log(N)) lookup performance, but in any case they are 'ordered' in the sense that they keep track of the order in which key-value pairs were inserted into them. However, the semantic of whether two Maps should be equal if elements were inserted in a different order into them is ambiguous. I give a sample implementation below of a deepEquals that considers two maps equal even if elements were inserted into them in a different order.)
(note [1]: IMPORTANT: NOTION OF EQUALITY: You may want to override the noted line with a custom notion of equality, which you'll also have to change in the other functions anywhere it appears. For example, do you or don't you want NaN==NaN? By default this is not the case. There are even more weird things like 0=='0'. Do you consider two objects to be the same if and only if they are the same object in memory? See https://stackoverflow.com/a/5447170/711085 . You should document the notion of equality you use.) Also note that other answers which naively use .toString and .sort may sometimes fall pray to the fact that 0!=-0 but are considered equal and canonicalizable to 0 for almost all datatypes and JSON serialization; whether -0==0 should also be documented in your notion of equality, as well as most other things in that table like NaN, etc.
You should be able to extend the above to WeakMaps, WeakSets. Not sure if it makes sense to extend to DataViews. Should also be able to extend to RegExps probably, etc.
As you extend it, you realize you do lots of unnecessary comparisons. This is where the type function that I defined way earlier (solution #2) can come in handy; then you can dispatch instantly. Whether that is worth the overhead of (possibly? not sure how it works under the hood) string representing the type is up to you. You can just then rewrite the dispatcher, i.e. the function deepEquals, to be something like:
var dispatchTypeEquals = {
number: function(a,b) {...a==b...},
array: function(a,b) {...deepEquals(x,y)...},
...
}
function deepEquals(a,b) {
var typeA = extractType(a);
var typeB = extractType(a);
return typeA==typeB && dispatchTypeEquals[typeA](a,b);
}
jQuery does not have a method for comparing arrays. However the Underscore library (or the comparable Lodash library) does have such a method: isEqual, and it can handle a variety of other cases (like object literals) as well. To stick to the provided example:
var a=[1,2,3];
var b=[3,2,1];
var c=new Array(1,2,3);
alert(_.isEqual(a, b) + "|" + _.isEqual(b, c));
By the way: Underscore has lots of other methods that jQuery is missing as well, so it's a great complement to jQuery.
EDIT: As has been pointed out in the comments, the above now only works if both arrays have their elements in the same order, ie.:
_.isEqual([1,2,3], [1,2,3]); // true
_.isEqual([1,2,3], [3,2,1]); // false
Fortunately Javascript has a built in method for for solving this exact problem, sort:
_.isEqual([1,2,3].sort(), [3,2,1].sort()); // true
For primitive values like numbers and strings this is an easy solution:
a = [1,2,3]
b = [3,2,1]
a.sort().toString() == b.sort().toString()
The call to sort() will ensure that the order of the elements does not matter. The toString() call will create a string with the values comma separated so both strings can be tested for equality.
With JavaScript version 1.6 it's as easy as this:
Array.prototype.equals = function( array ) {
return this.length == array.length &&
this.every( function(this_i,i) { return this_i == array[i] } )
}
For example, [].equals([]) gives true, while [1,2,3].equals( [1,3,2] ) yields false.
Even if this would seem super simple, sometimes it's really useful. If all you need is to see if two arrays have the same items and they are in the same order, try this:
[1, 2, 3].toString() == [1, 2, 3].toString()
true
[1, 2, 3,].toString() == [1, 2, 3].toString()
true
[1,2,3].toString() == [1, 2, 3].toString()
true
However, this doesn't work for mode advanced cases such as:
[[1,2],[3]].toString() == [[1],[2,3]].toString()
true
It depends what you need.
Based on Tim James answer and Fox32's comment, the following should check for nulls, with the assumption that two nulls are not equal.
function arrays_equal(a,b) { return !!a && !!b && !(a<b || b<a); }
> arrays_equal([1,2,3], [1,3,4])
false
> arrays_equal([1,2,3], [1,2,3])
true
> arrays_equal([1,3,4], [1,2,3])
false
> arrays_equal(null, [1,2,3])
false
> arrays_equal(null, null)
false
jQuery has such method for deep recursive comparison.
A homegrown general purpose strict equality check could look as follows:
function deepEquals(obj1, obj2, parents1, parents2) {
"use strict";
var i;
// compare null and undefined
if (obj1 === undefined || obj2 === undefined ||
obj1 === null || obj2 === null) {
return obj1 === obj2;
}
// compare primitives
if (typeof (obj1) !== 'object' || typeof (obj2) !== 'object') {
return obj1.valueOf() === obj2.valueOf();
}
// if objects are of different types or lengths they can't be equal
if (obj1.constructor !== obj2.constructor || (obj1.length !== undefined && obj1.length !== obj2.length)) {
return false;
}
// iterate the objects
for (i in obj1) {
// build the parents list for object on the left (obj1)
if (parents1 === undefined) parents1 = [];
if (obj1.constructor === Object) parents1.push(obj1);
// build the parents list for object on the right (obj2)
if (parents2 === undefined) parents2 = [];
if (obj2.constructor === Object) parents2.push(obj2);
// walk through object properties
if (obj1.propertyIsEnumerable(i)) {
if (obj2.propertyIsEnumerable(i)) {
// if object at i was met while going down here
// it's a self reference
if ((obj1[i].constructor === Object && parents1.indexOf(obj1[i]) >= 0) || (obj2[i].constructor === Object && parents2.indexOf(obj2[i]) >= 0)) {
if (obj1[i] !== obj2[i]) {
return false;
}
continue;
}
// it's not a self reference so we are here
if (!deepEquals(obj1[i], obj2[i], parents1, parents2)) {
return false;
}
} else {
// obj2[i] does not exist
return false;
}
}
}
return true;
};
Tests:
// message is displayed on failure
// clean console === all tests passed
function assertTrue(cond, msg) {
if (!cond) {
console.log(msg);
}
}
var a = 'sdf',
b = 'sdf';
assertTrue(deepEquals(b, a), 'Strings are equal.');
b = 'dfs';
assertTrue(!deepEquals(b, a), 'Strings are not equal.');
a = 9;
b = 9;
assertTrue(deepEquals(b, a), 'Numbers are equal.');
b = 3;
assertTrue(!deepEquals(b, a), 'Numbers are not equal.');
a = false;
b = false;
assertTrue(deepEquals(b, a), 'Booleans are equal.');
b = true;
assertTrue(!deepEquals(b, a), 'Booleans are not equal.');
a = null;
assertTrue(!deepEquals(b, a), 'Boolean is not equal to null.');
a = function () {
return true;
};
assertTrue(deepEquals(
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': 1.0
},
true]
],
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': 1.0
},
true]
]), 'Arrays are equal.');
assertTrue(!deepEquals(
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': 1.0
},
true]
],
[
[1, 1, 1],
[2, 'asdf', [1, a]],
[3, {
'a': '1'
},
true]
]), 'Arrays are not equal.');
a = {
prop: 'val'
};
a.self = a;
b = {
prop: 'val'
};
b.self = a;
assertTrue(deepEquals(b, a), 'Immediate self referencing objects are equal.');
a.prop = 'shmal';
assertTrue(!deepEquals(b, a), 'Immediate self referencing objects are not equal.');
a = {
prop: 'val',
inside: {}
};
a.inside.self = a;
b = {
prop: 'val',
inside: {}
};
b.inside.self = a;
assertTrue(deepEquals(b, a), 'Deep self referencing objects are equal.');
b.inside.self = b;
assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equeal. Not the same instance.');
b.inside.self = {foo: 'bar'};
assertTrue(!deepEquals(b, a), 'Deep self referencing objects are not equal. Completely different object.');
a = {};
b = {};
a.self = a;
b.self = {};
assertTrue(!deepEquals(b, a), 'Empty object and self reference of an empty object.');
If you are using lodash and don't want to modify either array, you can use the function _.xor(). It compares the two arrays as sets and returns the set that contains their difference. If the length of this difference is zero, the two arrays are essentially equal:
var a = [1, 2, 3];
var b = [3, 2, 1];
var c = new Array(1, 2, 3);
_.xor(a, b).length === 0
true
_.xor(b, c).length === 0
true
Check every each value by a for loop once you checked the size of the array.
function equalArray(a, b) {
if (a.length === b.length) {
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
} else {
return false;
}
}
Using map() and reduce():
function arraysEqual (a1, a2) {
return a1 === a2 || (
a1 !== null && a2 !== null &&
a1.length === a2.length &&
a1
.map(function (val, idx) { return val === a2[idx]; })
.reduce(function (prev, cur) { return prev && cur; }, true)
);
}
If you wish to check arrays of objects for equality and order does NOT matter, i.e.
areEqual([{id: "0"}, {id: "1"}], [{id: "1"}, {id: "0"}]) // true
you'll want to sort the arrays first. lodash has all the tools you'll need, by combining sortBy and isEqual:
// arr1 & arr2: Arrays of objects
// sortProperty: the property of the object with which you want to sort
// Note: ensure every object in both arrays has your chosen sortProperty
// For example, arr1 = [{id: "v-test_id0"}, {id: "v-test_id1"}]
// and arr2 = [{id: "v-test_id1"}, {id: "v-test_id0"}]
// sortProperty should be 'id'
function areEqual (arr1, arr2, sortProperty) {
return _.areEqual(_.sortBy(arr1, sortProperty), _.sortBy(arr2, sortProperty))
}
EDIT: Since sortBy returns a new array, there is no need to clone your arrays before sorting. The original arrays will not be mutated.
Note that for lodash's isEqual, order does matter. The above example will return false if sortBy is not applied to each array first.
This method sucks, but I've left it here for reference so others avoid this path:
Using Option 1 from #ninjagecko worked best for me:
Array.prototype.equals = function(array) {
return array instanceof Array && JSON.stringify(this) === JSON.stringify(array) ;
}
a = [1, [2, 3]]
a.equals([[1, 2], 3]) // false
a.equals([1, [2, 3]]) // true
It will also handle the null and undefined case, since we're adding this to the prototype of array and checking that the other argument is also an array.
There is no easy way to do this. I needed this as well, but wanted a function that can take any two variables and test for equality. That includes non-object values, objects, arrays and any level of nesting.
In your question, you mention wanting to ignore the order of the values in an array. My solution doesn't inherently do that, but you can achieve it by sorting the arrays before comparing for equality
I also wanted the option of casting non-objects to strings so that [1,2]===["1",2]
Since my project uses UnderscoreJs, I decided to make it a mixin rather than a standalone function.
You can test it out on http://jsfiddle.net/nemesarial/T44W4/
Here is my mxin:
_.mixin({
/**
Tests for the equality of two variables
valA: first variable
valB: second variable
stringifyStatics: cast non-objects to string so that "1"===1
**/
equal:function(valA,valB,stringifyStatics){
stringifyStatics=!!stringifyStatics;
//check for same type
if(typeof(valA)!==typeof(valB)){
if((_.isObject(valA) || _.isObject(valB))){
return false;
}
}
//test non-objects for equality
if(!_.isObject(valA)){
if(stringifyStatics){
var valAs=''+valA;
var valBs=''+valB;
ret=(''+valA)===(''+valB);
}else{
ret=valA===valB;
}
return ret;
}
//test for length
if(_.size(valA)!=_.size(valB)){
return false;
}
//test for arrays first
var isArr=_.isArray(valA);
//test whether both are array or both object
if(isArr!==_.isArray(valB)){
return false;
}
var ret=true;
if(isArr){
//do test for arrays
_.each(valA,function(val,idx,lst){
if(!ret){return;}
ret=ret && _.equal(val,valB[idx],stringifyStatics);
});
}else{
//do test for objects
_.each(valA,function(val,idx,lst){
if(!ret){return;}
//test for object member exists
if(!_.has(valB,idx)){
ret=false;
return;
}
// test for member equality
ret=ret && _.equal(val,valB[idx],stringifyStatics);
});
}
return ret;
}
});
This is how you use it:
_.equal([1,2,3],[1,2,"3"],true)
To demonstrate nesting, you can do this:
_.equal(
['a',{b:'b',c:[{'someId':1},2]},[1,2,3]],
['a',{b:'b',c:[{'someId':"1"},2]},["1",'2',3]]
,true);
It handle all possible stuff and even reference itself in structure of object. You can see the example at the end of code.
var deepCompare = (function() {
function internalDeepCompare (obj1, obj2, objects) {
var i, objPair;
if (obj1 === obj2) {
return true;
}
i = objects.length;
while (i--) {
objPair = objects[i];
if ( (objPair.obj1 === obj1 && objPair.obj2 === obj2) ||
(objPair.obj1 === obj2 && objPair.obj2 === obj1) ) {
return true;
}
}
objects.push({obj1: obj1, obj2: obj2});
if (obj1 instanceof Array) {
if (!(obj2 instanceof Array)) {
return false;
}
i = obj1.length;
if (i !== obj2.length) {
return false;
}
while (i--) {
if (!internalDeepCompare(obj1[i], obj2[i], objects)) {
return false;
}
}
}
else {
switch (typeof obj1) {
case "object":
// deal with null
if (!(obj2 && obj1.constructor === obj2.constructor)) {
return false;
}
if (obj1 instanceof RegExp) {
if (!(obj2 instanceof RegExp && obj1.source === obj2.source)) {
return false;
}
}
else if (obj1 instanceof Date) {
if (!(obj2 instanceof Date && obj1.getTime() === obj2.getTime())) {
return false;
}
}
else {
for (i in obj1) {
if (obj1.hasOwnProperty(i)) {
if (!(obj2.hasOwnProperty(i) && internalDeepCompare(obj1[i], obj2[i], objects))) {
return false;
}
}
}
}
break;
case "function":
if (!(typeof obj2 === "function" && obj1+"" === obj2+"")) {
return false;
}
break;
default: //deal with NaN
if (obj1 !== obj2 && obj1 === obj1 && obj2 === obj2) {
return false;
}
}
}
return true;
}
return function (obj1, obj2) {
return internalDeepCompare(obj1, obj2, []);
};
}());
/*
var a = [a, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null],
b = [b, undefined, new Date(10), /.+/, {a:2}, function(){}, Infinity, -Infinity, NaN, 0, -0, 1, [4,5], "1", "-1", "a", null];
deepCompare(a, b);
*/
var a= [1, 2, 3, '3'];
var b = [1, 2, 3];
var c = a.filter(function (i) { return ! ~b.indexOf(i); });
alert(c.length);

Categories