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

16

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

Epistemic answered 25/6, 2010 at 6:24 Comment(4)
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.Beachlamar
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])Truculent
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.Ayer
The suggestion by @Ayer 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]Sylvestersylvia
L
427

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;
}
Leanneleanor answered 8/5, 2013 at 9:22 Comment(24)
(This is not originally my code, but the person writing it didn't feel like posting it)Leanneleanor
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 answerAntagonistic
Why should the code be sorted?Lundquist
@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.Leanneleanor
This does not work if the arrays contain an object.Magree
@Magree 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.Leanneleanor
See #7837956 for a solution that recursively compares multi-dimensional arrays. (Now, if only folks would kindly use Jasmine specs while writing their code...)Alginate
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.Recording
This does not deal correctly with nested arrays - see @ninjagecko 's answer or this one for correct version. (Yet another reason why we need to write tests/do TDD even for simple functions like this)Kauslick
Can you please explain in your answer why we shouldn't use stringify or < >?Aeneous
Please check @machineghost's answer.Merell
@KunalKapadia the question was for JavaScript or jQuery not a framework. I'm sure there are many frameworks besides underscore that offer this functionality.Leanneleanor
@Leanneleanor 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.Merell
It should be better to cache a.length to a local variable.Sen
@JonathanBenn: you can potentially get lots of false positives with the other options.Leanneleanor
@HunanRostomyan the only case where a===b that I can see is when you do this: a = [1,2,3]; b = a.Patent
@10basetom you're rightRecording
7 years later... could use some sexy fat arrow instead of that for loop... return a.every((val, idx) => val === b[idx])Bellflower
@Bellflower Unfortunately, no, if one array is emptied as it's change fat arrow will failHedron
My approach was this const a = ['A', 'B', 'C'].sort().toString() const b = ['A', 'C', 'B'].sort().toString() console.log(a === b);Mucilaginous
@JonathanBenn stringify shouldn't be used because it does unnecessary work. For example, consider the arrays [111, 222, 333] and [111, 220, 333] and their string representations. Comparing the arrays directly requires two operations (one for each number in each index position that must be checked before failing). However, comparing the strings [111,222,333] and [111,220,333] requires eight operations (one for each character that must be checked before the comparison fails). Also, stringify uses additional operations to convert an array and its contents into a string.Frost
@VarunMadiath Arrays shouldn't be sorted because an array is a sequence of items. If the array represents a set with repeated elements, it would need to be sorted to check for equality, but using arrays in this way is bad practice. For people doing this, a better alternative for storing sets with duplicate items is to use a hash table that maps each element to the number of times it occurs in the set.Frost
@Mucilaginous no: [1,"a,b","c"].sort().toString() === [1,"a","b,c"].sort().toString()Annunciator
Sorting the array won't help if there are multiple elements that are sorted equal but are different. It won't be an issue for string or int, but for more complex user defined types it may.Mertens
O
280

[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://mcmap.net/q/24497/-javascript-equality-transitivity-is-weird . 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);
}
Onaonager answered 25/4, 2012 at 13:22 Comment(7)
+1, a little comment: For allTrue, you can also use array.every with a function returning the array element value.Animated
JSON.stringify(null) === 'null' (the string "null"), not null.Joanejoanie
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.Onaonager
JSON.stringify does not guarantee you that it converts an object to the same string. each time.Gmur
yes, that's exactly what I already said, though I guess I'll bold it for clarity or somethingOnaonager
function areArraysEqual(a1, a2) { return (a1.length === a2.length && new Set(a1, a2).size === a1.length) ? true : false; }Petronia
TheDarkIn1978's suggestion is incorrect for cases like [1,1,2].Onaonager
E
101

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.

Everetteeverglade answered 11/1, 2013 at 23:29 Comment(10)
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.Xerosis
I don't understand why this is being downvoted: This is the only solution so far that actually gets the (under-specified) example correct...Beachlamar
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 objectsEveretteeverglade
It's not optimal. See https://mcmap.net/q/37262/-how-to-compare-arrays-in-javascript and https://mcmap.net/q/37262/-how-to-compare-arrays-in-javascriptMccary
One could consider it optimal in terms of readability and simplicity, and therefore in terms of maintainability.Mechanician
a.sort() does not only return sorted version. It changes the array itself, whih may affect one's application in unexpected ways.Ruthi
@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 equalWaers
ah you're right - however the problem remains: ['1,2',3].toString() === [1,'2,3'].toString() is a false positiveDoable
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.Flier
(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).Onaonager
A
96

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
Alodee answered 8/5, 2013 at 0:21 Comment(9)
Underscorejs is my daily used lib. It should be definitely rated much higher for its simplicity.Ignatia
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.Merell
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.Alodee
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.Alodee
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): blog.codinghorror.com/learn-to-read-the-source-lukeAlodee
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.Sibelle
this does not answer the question as @Sibelle say, it will print false|false which is not what OP wants.Crista
I fixed my example to sort the arrays before comparing them, resolving the problem.Alodee
best answer, should be pinned higherSatiety
Q
39

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.

Quicken answered 21/10, 2013 at 12:11 Comment(9)
It is generally advised not to modify/expand the existing global objects.Merell
better to use === rather than == right?Seize
@Seize Depends on what you want, like always :-) Do you want undefined to be equal to null, 0 etc. - or not.Quicken
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.Ayer
@Ayer [3,3,3].equals([1,2,3]) yields false as it should. Why does it give something else for you?Quicken
@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.Ayer
@Ayer 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.Quicken
Ok. I just raed this topic because I was searching for the same thing but without the index equality. But okay you're right.Ayer
you can add this == array || at the start of the return to exit early if the arrays are the same objectTrimaran
J
20

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.

Joletta answered 22/6, 2012 at 6:41 Comment(4)
An interesting answer!it's usefulEpistemic
what about [3, 2, 1].toString() == [1, 2, 3].toString()Gabrielgabriela
@Gabrielgabriela that would not work, those are different aways unless sortedSeise
@KevinDanikowski yes, that was exactly my point, I meant this answer doesn't work for the case I meantionedGabrielgabriela
H
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
Heth answered 23/12, 2011 at 16:31 Comment(3)
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?Gautier
As pointed out in this answer, arrays_equal([1, [2, 3]],[[1, 2], 3]) would return true.Adelina
Also, arrays_equal(["1,2"], ["1,2"]) are treated as equal and so are arrays_equal([], [""]).Mockingbird
H
5

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;
    }
}
Heavyset answered 3/8, 2016 at 0:37 Comment(1)
@Doable I think this one defintely does not have false positiveSelfsatisfied
M
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.');
Meador answered 21/12, 2012 at 1:40 Comment(6)
How your code is dealing with objects which are referring themselves?Mesdames
@Mesdames 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. – uKolkaMeador
@Mesdames 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.Meador
please don't edit prototypes of objects you don't ownIrreformable
@Umur Kontacı Guilty. I'll update it sometime.Meador
Edited the answer. Changes: - Accounting for self-referencing objects. - Moved from Object prototype. - Added tests.Meador
H
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
Heliopolis answered 23/6, 2016 at 23:2 Comment(0)
S
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)
    );
}
Sectarianize answered 23/9, 2015 at 10:27 Comment(3)
This is not relevant answer for the question asked. i tried this in jsbin and it does not work with input mentioned in question.Spearwort
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.Gentle
It won't work if one of the element is 0 in the array.Lingwood
P
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.

Puma answered 3/2, 2016 at 16:34 Comment(0)
C
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.

Cysto answered 1/10, 2012 at 15:25 Comment(3)
Question, what will happen if array contains an object, which has property which refers to the same object?Mesdames
TypeError: Converting circular structure to JSONCysto
Thanks @Mesdames for pointing this out, I've made it explicit in the answer.Cysto
U
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);
Utu answered 6/11, 2013 at 8:24 Comment(0)
W
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);
*/
Woodbury answered 22/3, 2014 at 10:26 Comment(0)
T
-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);
Tempe answered 23/1, 2015 at 12:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.