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

K-Gun
  • 10,121
  • 2
  • 50
  • 56
Koerr
  • 13,337
  • 25
  • 70
  • 107
  • 17
    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. – Stefan Haustein May 08 '13 at 14:03
  • Isn't the simplest way to do a shallow compare of two `Number`s like this to just do this? a.every((v, i) => v === b[i]) b.every((v, i) => v === c[i]) – RavenHursT Jul 30 '20 at 18:19
  • 3
    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. – NoxFly Dec 18 '20 at 11:23

16 Answers16

332

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;
}
C. Tewalt
  • 2,093
  • 2
  • 23
  • 42
enyo
  • 14,875
  • 7
  • 48
  • 67
  • 14
    (This is not originally my code, but the person writing it didn't feel like posting it) – enyo May 08 '13 at 09:22
  • 1
    This should be the accepted answer if you want to write it yourself. Period. If you want deep comparison or have UnderscoreJS already included, check out [machineghost's](http://stackoverflow.com/users/5921/machineghost) [answer](http://stackoverflow.com/a/16430730/60518) – Tim Büthe Jul 01 '13 at 12:14
  • 3
    Why should the code be sorted? – Varun Madiath Sep 17 '13 at 19:17
  • 3
    @VarunMadiath the last loop goes through every element and compares them. If the arrays are not sorted then it will fail if the order of the items is not exactly the same. – enyo Sep 23 '13 at 15:17
  • 5
    This does not work if the arrays contain an object. – GijsjanB Jun 18 '14 at 11:39
  • 16
    @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. – enyo Jul 04 '14 at 21:51
  • 1
    See http://stackoverflow.com/questions/7837456/comparing-two-arrays-in-javascript for a solution that recursively compares multi-dimensional arrays. (Now, if only folks would kindly use Jasmine specs while writing their code...) – Rob Myers Sep 29 '14 at 00:50
  • Is the guard of the first conditional (viz. `a === b`) ever met for some arrays `a` and `b`? I can't think of any two arrays for which that condition would be met. – Hunan Rostomyan Apr 06 '15 at 02:31
  • This **does not** deal correctly with nested arrays - see @ninjagecko 's answer or [this one](http://stackoverflow.com/a/14853974/785470) for correct version. (Yet another reason why we need to write tests/do TDD even for simple functions like this) – Richard Aug 02 '15 at 04:55
  • 4
    Can you please explain in your answer _why_ we shouldn't use `stringify` or `< >`? – Jonathan Benn Aug 11 '15 at 12:37
  • Please check @machineghost's [answer](http://stackoverflow.com/a/16430730/825980). – Kunal Kapadia Sep 30 '15 at 12:54
  • 2
    @KunalKapadia the question was for JavaScript or jQuery not a framework. I'm sure there are *many* frameworks besides underscore that offer this functionality. – enyo Oct 05 '15 at 15:45
  • @enyo lodash is just a Javascript library and not a framework. Its always safe to use well tested library instead of reinventing the wheel which might lead to bugs. – Kunal Kapadia Oct 06 '15 at 13:24
  • It should be better to cache `a.length` to a local variable. – tsh Jun 03 '17 at 07:55
  • 1
    @JonathanBenn: you can potentially get lots of false positives with the other options. – enyo Jun 26 '17 at 14:52
  • @HunanRostomyan the only case where a===b that I can see is when you do this: `a = [1,2,3]; b = a`. – thdoan Apr 02 '18 at 07:02
  • @10basetom you're right – Hunan Rostomyan Apr 02 '18 at 16:33
  • 3
    7 years later... could use some sexy fat arrow instead of that for loop... `return a.every((val, idx) => val === b[idx])` – Mr5o1 Jun 26 '20 at 19:42
  • @Mr5o1 Unfortunately, no, if one array is emptied as it's change fat arrow will fail – JohnC Feb 01 '21 at 17:42
213

[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);
}
ninjagecko
  • 77,349
  • 22
  • 129
  • 137
  • 2
    +1, a little comment: For `allTrue`, you can also use `array.every` with a function returning the array element value. – pimvdb Apr 25 '12 at 14:08
  • 2
    `JSON.stringify(null) === 'null'` (the string "null"), not `null`. – djechlin Aug 23 '13 at 18:22
  • 1
    `undefined` isn't converted to the string `'null'`... can you please take a minute to understand how JSON.stringify works? – djechlin Aug 25 '13 at 16:00
  • 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. – ninjagecko Mar 30 '14 at 04:50
  • JSON.stringify does not guarantee you that it converts an object to the same string. each time. – runspired Aug 19 '15 at 15:21
  • yes, that's exactly what I already said, though I guess I'll bold it for clarity or something – ninjagecko Sep 04 '15 at 18:54
  • `function areArraysEqual(a1, a2) { return (a1.length === a2.length && new Set(a1, a2).size === a1.length) ? true : false; }` – Chunky Chunk May 07 '19 at 03:33
  • TheDarkIn1978's suggestion is incorrect for cases like [1,1,2]. – ninjagecko May 20 '19 at 14:54
  • deep equal !!! – datdinhquoc May 02 '21 at 13:10
  • That JSON.stringify was one of the smartest things I've seen on Stack – shaswat.dharaiya May 26 '21 at 19:34
82

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
machineghost
  • 28,573
  • 26
  • 128
  • 197
  • 3
    Underscorejs is my daily used lib. It should be definitely rated much higher for its simplicity. – Jan Stanicek Apr 21 '15 at 09:46
  • Underscore has been replaced by a more superior library [lodash](https://lodash.com). Since it a superset of underscore it also supports _.isEqual(a, b). check [this](http://stackoverflow.com/a/13898916/825980) for more details. – Kunal Kapadia Sep 30 '15 at 13:01
  • 2
    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. – machineghost Sep 30 '15 at 16:15
  • 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: https://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. – machineghost Sep 30 '15 at 16:22
  • One last thing: to anyone who doubts the significance of being able to read a library's source code, I strongly recommend this blog post from Jeff Atwood (one of the co-founders of Stack Overflow): http://blog.codinghorror.com/learn-to-read-the-source-luke/ – machineghost Sep 30 '15 at 16:31
  • 2
    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. – Paul Feb 25 '16 at 14:55
  • this does not answer the question as @Paul say, it will print ```false|false``` which is not what OP wants. – Rocel Oct 22 '16 at 03:14
  • I fixed my example to sort the arrays before comparing them, resolving the problem. – machineghost Dec 05 '16 at 19:24
61

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.

try-catch-finally
  • 6,720
  • 6
  • 37
  • 64
soheildb
  • 1,116
  • 7
  • 9
  • 7
    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. – Nate Jan 28 '13 at 02:59
  • 7
    I don't understand why this is being downvoted: This is the only solution so far that actually gets the (under-specified) example correct... – Stefan Haustein May 08 '13 at 04:16
  • 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 – soheildb May 10 '13 at 22:30
  • It's not optimal. See https://stackoverflow.com/a/14853974/2404492 and https://stackoverflow.com/a/34256998/2404492 – Alexandr Zarubkin Oct 16 '17 at 14:14
  • One could consider it optimal in terms of readability and simplicity, and therefore in terms of maintainability. – twm Jan 23 '18 at 19:51
  • 9
    `a.sort()` does not only return sorted version. It changes the array itself, whih may affect one's application in unexpected ways. – pilat May 05 '18 at 20:11
  • 3
    @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 – Must Ckr Apr 16 '19 at 14:19
  • 3
    ah you're right - however the problem remains: `['1,2',3].toString() === [1,'2,3'].toString()` is a false positive – feihcsim Apr 16 '19 at 17:37
  • This is terrible. Not only is `a.sort()` slow because it converts the elements to strings before sorting, but you then convert the entire arrays to strings! It's also *probably* not what OP was asking. – Timmmm Sep 20 '20 at 19:28
  • (This answer assumes OP meant an orderless comparison. If we assume that...) At least two minor issues: 1) (this won't come up unless the end user is being malicious:) if one considers -0 to be equal to 0, then this solution will 'successfully' distinguish [0,1,2] and [-0,1,2] as being "not equal". 2) This solution will fail with [1,2,'2',3] =? [1,'2',2,3] since es6 mandates sorting implementations are stable, so would only work for homogeneous arrays of numbers or arrays of strings, not arrays of numbers-or-strings (sorry if this was already implied). – ninjagecko May 27 '21 at 18:25
30

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.

rplantiko
  • 2,476
  • 19
  • 21
  • 10
    It is generally advised not to modify/expand the existing global objects. – Kunal Kapadia Sep 30 '15 at 12:48
  • 2
    better to use === rather than == right? – ChetPrickles Feb 19 '16 at 15:41
  • 1
    @ChetPrickles Depends on what you want, like always :-) Do you want `undefined` to be equal to `null`, 0 etc. - or not. – rplantiko Feb 22 '16 at 20:40
  • It's not working if you have 2 arrays like those ones : `[3,3,3] and [1,2,3]`, because your code will return true while it's not. So you have to do the `every` on both sides. – NoxFly Dec 18 '20 at 11:20
  • @NoxFly `[3,3,3].equals([1,2,3])` yields `false` as it should. Why does it give something else for you? – rplantiko Dec 19 '20 at 13:52
  • @rplantiko, depending on what you wrote, `[1,2,3].equals([1,3,2])` should return true because there're equals but just the order matter. Maybe I'm wrong about your purposes, but for mines I wanted to check if two arrays were equal independently of their item's order. – NoxFly Dec 19 '20 at 13:56
  • @NoxFly The natural definition of equality for arrays is that, index by index, the array elements are equal. Since the question was unclear in this regard, I explicitly added the example that `[1,2,3]` is unequal to `[1,3,2]` according to this natural equality definition of arrays. – rplantiko Dec 19 '20 at 14:11
  • 1
    Ok. I just raed this topic because I was searching for the same thing but without the index equality. But okay you're right. – NoxFly Dec 19 '20 at 14:32
11

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.

claudiut
  • 1,438
  • 2
  • 14
  • 19
6

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
Community
  • 1
  • 1
stantont
  • 167
  • 1
  • 3
  • 4
    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? – Halil Özgür Feb 06 '12 at 11:49
  • 5
    As pointed out in [this answer](http://stackoverflow.com/a/9334251/950430), `arrays_equal([1, [2, 3]],[[1, 2], 3])` would return `true`. – Dennis Feb 17 '12 at 19:42
  • 2
    Also, `arrays_equal(["1,2"], ["1,2"])` are treated as equal and so are `arrays_equal([], [""])`. – Mike Samuel Feb 17 '12 at 19:44
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.');
uKolka
  • 27,488
  • 4
  • 29
  • 43
  • How your code is dealing with objects which are referring themselves? – jusio May 07 '13 at 20:46
  • @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](http://jsfiddle.net/kolkabes/WQ58V/1/). After a bit more testing and feedback I'll edit the one in the answer. – uKolka – uKolka May 21 '13 at 03:44
  • @jusio Here is [newer version](http://jsfiddle.net/kolkabes/WQ58V/10/). 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. – uKolka May 21 '13 at 14:23
  • please don't edit prototypes of objects you don't own – Umur Kontacı Jul 01 '13 at 12:16
  • @Umur Kontacı Guilty. I'll update it sometime. – uKolka Jul 01 '13 at 21:03
  • Edited the answer. Changes: - Accounting for self-referencing objects. - Moved from Object prototype. - Added tests. – uKolka Aug 18 '13 at 22:21
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
Chase Sandmann
  • 3,935
  • 3
  • 28
  • 37
4

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;
    }
}
try-catch-finally
  • 6,720
  • 6
  • 37
  • 64
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)
    );
}
Chitharanjan Das
  • 1,094
  • 8
  • 15
  • This is not relevant answer for the question asked. i tried this in jsbin and it does not work with input mentioned in question. – Jaimin Aug 02 '16 at 19:52
  • 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. – try-catch-finally Jul 15 '17 at 08:04
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.

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.

Aram Kocharyan
  • 19,179
  • 11
  • 69
  • 93
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);
Nemesarial
  • 367
  • 3
  • 10
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);
*/
Community
  • 1
  • 1
-1
var a= [1, 2, 3, '3'];
var b = [1, 2, 3];

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

alert(c.length);