455
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?

4
  • 19
    The example seems to suggest that the order of the numbers should not be taken into account ([1, 2, 3] == [3, 2, 1]). However, an answer that doesn't address this is marked as correct and the only answer that actually addresses this (using sort()) is donwvoted.... If the ordering is not meant to be relevant, this should be fixed in the example. Otherwise, the answer marked as correct can't be correct. Commented May 8, 2013 at 14:03
  • Isn't the simplest way to do a shallow compare of two Numbers like this to just do this? a.every((v, i) => v === b[i]) b.every((v, i) => v === c[i]) Commented Jul 30, 2020 at 18:19
  • 17
    In ES6, just do a.every(item => b.includes(item)) && b.every(item => a.includes(item)). Don't forget to check on both sides, because if you only do one every, you can handle this case : a=[3,3,3], b=[1,2,3] while it's false. Commented Dec 18, 2020 at 11:23
  • 2
    The suggestion by @NoxFly is neat but only works if you don't care about order and either check the length or don't care about dupes. Otherwise [1,2,3] will be equal to ``` [1,3,2]``` and equal to [1,2,3,3] Commented Aug 30, 2023 at 8:32

16 Answers 16

444

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;
}
Sign up to request clarification or add additional context in comments.

27 Comments

(This is not originally my code, but the person writing it didn't feel like posting it)
This does not work if the arrays contain an object.
@GijsjanB it does work if the array contains the same objects. Of course, it doesn't work if the objects are not identical, and you want to compare the objects themselves. But that was not the question.
Can you please explain in your answer why we shouldn't use stringify or < >?
7 years later... could use some sexy fat arrow instead of that for loop... return a.every((val, idx) => val === b[idx])
|
287

[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).]

This answer provides two methods: a simple implementation with JSON.stringify with a few caveats, and another more general solution that does the right thing even for Sets and Maps and can be extended.


Option 1

Easiest option, works in almost all cases (including arbitrarily nested arrays-in-objects-in-arrays etc.), except for some notable caveats and the objects must be clones or have been created with properties in the same order. This uses the JSON notion of equality, not the == notion of equality. (In javascript, the string '2' is == to 2, "2"==2 is true! This conveniently avoids that...)

function areProbablyJsonIsomorphic(obj1,obj2) {
    /* 
       Returns whether obj1 is isomorphic to obj2; that is
        the tree of lists [..] and objects {..} have,
        branch-by-branch, recursively identical values,
        after the processing done by the JSON.stringify spec,
        https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description
                  
       WARNINGS:
         1) any propName:propVal must have been created with
            their fields defined in the same order
         2) - does not support Sets or Maps, 
            - is confused by Functions and Symbols, 
            - is confused by undefined and non-own and 
               non-enumerable properties, 
            - collapses sparse arrays, 
            - considers null and +/-Infinity and undefined 
               to be the same when in an Array, 
            - can be tricked with .toJson() methods, 
            and other and future things noted in the above link
            dependent on the ECMAScript version running
    */
    return JSON.stringify(a1)==JSON.stringify(a2);
}

(Non-literal objects might not have keys defined in the same order if, e.g., an adversary or other circumstance gives you data to deserialize in a different order, or you are using async or Promises to asynchronously build an object post-initialization, etc.)

This must work because the ES6 specification guarantees object keys will be iterated in order of

  1. integer properties, then
  2. properties in the order they were defined, then
  3. symbol properties in the order they were defined

Thus since the JSON.stringify implementation (in your browser or in your node.js environment) presumably is not outdated and follows the spec correctly, reasonably "simple objects" that are isomorphic will stringify to equal strings.

e.g.

x = {a:1, b:2}

xClone = x;

y = {a:1}
y.b = 2;

evilY = {b:2};
evilY.a = 1;


console.log(`DEFINITIONS:    x is ${JSON.stringify(x)};  y is ${JSON.stringify(y)}`);

console.log(`== EQUALITY treats obj by reference:    x is ${x==xClone ? '==':'!='} to xClone`);
console.log(`== EQUALITY treats obj by reference:    x is ${x==y ? '==':'!='} to y`);
console.log(`== EQUALITY treats obj by reference:    [x] is ${x==y ? '==':'!='} to [x]`);

console.log(`JSON.stringify:    [x,x] is ${JSON.stringify([x,x])==JSON.stringify([x,xClone]) ? '':'NOT '}isomorphic to [x,xClone]`);
console.log(`JSON.stringify:    [x,x] is ${JSON.stringify([x,x])==JSON.stringify([x,y]) ? '':'NOT '}isomorphic to [x,y]`);
console.log(`JSON.stringify:    [x,x] is ${JSON.stringify([x,x])==JSON.stringify([x,evilY]) ? '':'NOT(?!) '}isomorphic to [x,evilY]`);

This method is slightly inefficient because of the overhead of creating these strings and garbage-collecting them.

Variant of this method: If your objects have some global unique ID like a database ID .myGuid, you can use the replacer parameter of JSON.stringify, to replace those objects with e.g. {type:'stub', myGuid:...} and throw an error if encountering other objects.


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

7 Comments

+1, a little comment: For allTrue, you can also use array.every with a function returning the array element value.
JSON.stringify(null) === 'null' (the string "null"), not null.
Oops, it seems back in the day I was in a rush and tricked myself into misreading someone's comment, and so said an irrelevant falsehood (that JSON.stringify(null)=='"null"'). I still do not see a problem in relation to OP's question with respect to any undefineds or nulls that I have not mentioned in my answer. Therefore I stand by my answer and its demo tests.
JSON.stringify does not guarantee you that it converts an object to the same string. each time.
yes, that's exactly what I already said, though I guess I'll bold it for clarity or something
|
108

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.

9 Comments

Be careful if your array contains anything other than simple values. Array.prototype.sort() is shallow and Array.prototype.toString() converts objects to [object Object] and flattens any embedded arrays, which could cause a false positive.
It would be nice to see an explanation of why this is down voted. True it might not work correctly for complex objects but it's a simple solution that solves the problem presented in the question! and could probably be extended to complex objects using the correct string representations of the objects
a.sort() does not only return sorted version. It changes the array itself, whih may affect one's application in unexpected ways.
@feihcsim you are wrong. [12, 34, 56].toString() //results: "12,34,56" while [1, 23, 456].toString() // results: "1,23,456" thus, they are not equal
ah you're right - however the problem remains: ['1,2',3].toString() === [1,'2,3'].toString() is a false positive
|
97

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

9 Comments

Underscorejs is my daily used lib. It should be definitely rated much higher for its simplicity.
Underscore has been replaced by a more superior library lodash. Since it a superset of underscore it also supports _.isEqual(a, b). check this for more details.
I like lodash, and it does contain a super-set of Underscore's features ... but it is not "superior" nor has it "replaced" Underscore. For one thing, Lodash has been so metaprogrammed and micro-optimized that its source code is basically unreadable. This matters when (say) you accidentally pass the wrong arguments to a Lodash/Underscore function and have to debug what's going on. In that case Underscore is vastly superior because (unlike Lodash) you can actually read the source. Ultimately, neither library is superior to the other, they just have different strengths and weaknesses.
As a side note, the authors of those two libraries (John-David Dalton and Jeremy Ashkenas) have recently discussed the prospect of merging them because of their commonalities. But (as you'll find if you read the GitHub thread: github.com/jashkenas/underscore/issues/2182) it was not an obvious decision. In fact, AFAIK no decision has been made in the 3+ months since, precisely because Underscore has advantages like readable source code that Underscore users don't want to lose.
Back to the topic at hand, this alerts false|false. _.isEqual(a,b) compares the elements of an array according to their order so if an order-insensitive comparison is desired then the arrays must be sorted before comparing.
|
41

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.

9 Comments

It is generally advised not to modify/expand the existing global objects.
better to use === rather than == right?
@ChetPrickles Depends on what you want, like always :-) Do you want undefined to be equal to null, 0 etc. - or not.
Ok. I just raed this topic because I was searching for the same thing but without the index equality. But okay you're right.
you can add this == array || at the start of the return to exit early if the arrays are the same object
|
21

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.

3 Comments

what about [3, 2, 1].toString() == [1, 2, 3].toString()
@Reza that would not work, those are different aways unless sorted
@KevinDanikowski yes, that was exactly my point, I meant this answer doesn't work for the case I meantioned
10

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

3 Comments

Since null==null in Javascript (even more, null===null), wouldn't it be more appropriate to treat two nulls as equal in array equivalence check too?
As pointed out in this answer, arrays_equal([1, [2, 3]],[[1, 2], 3]) would return true.
Also, arrays_equal(["1,2"], ["1,2"]) are treated as equal and so are arrays_equal([], [""]).
7

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

1 Comment

@feihcsim I think this one defintely does not have false positive
4

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

6 Comments

How your code is dealing with objects which are referring themselves?
@jusio Hi, sorry for the delay, didn't have much spare time. This particular one doesn't deal with self references, but take a look at its improved version. After a bit more testing and feedback I'll edit the one in the answer. – uKolka
@jusio Here is newer version. I've updated it for new test cases. It was failing when var a = {}, b = {}; a.self = a; b.self = {}; and in some other cases.
please don't edit prototypes of objects you don't own
@Umur Kontacı Guilty. I'll update it sometime.
|
4

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

Comments

2

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

3 Comments

This is not relevant answer for the question asked. i tried this in jsbin and it does not work with input mentioned in question.
Since the question is not 100% clear about the order this algorithm is kind of OK. But it performs badly, since it fully iterates twice over the array. For larger arrays this might be an issue.
It won't work if one of the element is 0 in the array.
2

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.

Comments

0

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.

3 Comments

Question, what will happen if array contains an object, which has property which refers to the same object?
TypeError: Converting circular structure to JSON
Thanks @jusio for pointing this out, I've made it explicit in the answer.
0

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

Comments

0

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

Comments

-2
var a= [1, 2, 3, '3'];
var b = [1, 2, 3];

var c = a.filter(function (i) { return ! ~b.indexOf(i); });

alert(c.length);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.