16

As the title says I want to compare two js arrays in which I only care about the contents being the same, but I don't care about the order of them being the same. So what I would expect is this:

[1, 2, 3, 3] == [1, 2, 3, 3]     // True
[1, 2, 3, 3] == [1, 3, 2, 3]     // True
[1, 2, 3, 3] == [1, 2]           // False
[1, 2, 3, 3] == [1, 2, 3]        // False
[1, 2, 3, 3] == [1, 2, 3, 3, 3]  // False
[1, 2, 3, 3] == [1, "2, 3, 3"]      // False

Obviously the comparison operator doesn't work. From this SO answer I got the Array.prototype method below, but that unfortunately also checks if the order is the same.

So does anybody know how I can check if two js arrays contain the same elements, not taking into account the order of the elements? All tips are welcome!

Array.prototype.equals = function (array) {
    // if the other array is a falsy value, return
    if (!array)
        return false;

    // compare lengths - can save a lot of time 
    if (this.length != array.length)
        return false;

    for (var i = 0, l=this.length; i < l; i++) {
        // Check if we have nested arrays
        if (this[i] instanceof Array && array[i] instanceof Array) {
            // recurse into the nested arrays
            if (!this[i].equals(array[i]))
                return false;       
        }           
        else if (this[i] != array[i]) { 
            // Warning - two different object instances will never be equal: {x:20} != {x:20}
            return false;   
        }           
    }       
    return true;
}   
Community
  • 1
  • 1
kramer65
  • 50,427
  • 120
  • 308
  • 488

2 Answers2

12

sort them before comparing:

from the comment below, if you want the 2 arrs to contain different primitive type.add this sort function.

function s(x,y){
    var pre = ['string' , 'number' , 'bool']
    if(typeof x!== typeof y )return pre.indexOf(typeof y) - pre.indexOf(typeof x);

    if(x === y)return 0;
    else return (x > y)?1:-1;

}
var arr1 = [1, 2, 3, 3].sort(s);
var arr2 = [1, 3, 2, 3].sort(s);

arr1.equals(arr2);// true
Omar Elawady
  • 3,300
  • 1
  • 13
  • 17
  • 2
    Sorting does not always work. As I explained in the other answer, `[1, '1'].sort()` and `['1', 1].sort()` will both leave the arrays in the original order. This particular example is not an issue in your example, because the `equals` implementation uses loose comparison, but that itself implies other problems (see my comment on the question). Fact is that sorting only truly works if you are dealing with values of the same primitive data type only. (Which may be the case, given the OP's presentation of the problem) – Felix Kling Apr 16 '15 at 11:34
  • Seeing that my arrays will only contain strings of letters, and nothing else, the comment above from Felix Kling is not a problem for me. This solution is both simple and effective and I'm already using it right now. Thanks @Omar – kramer65 Apr 16 '15 at 11:50
  • @FelixKling thanks for your notice.solution in edit. – Omar Elawady Apr 16 '15 at 11:52
  • Your sort callback is not correct however... it should always return a negative or positive value, or 0. Not a Boolean. – Felix Kling Apr 16 '15 at 11:59
  • @kramer65: an example that more closely reflects your use case would have been better then. – Felix Kling Apr 16 '15 at 12:01
  • @FelixKling simple just replace `>` with `-` – Omar Elawady Apr 16 '15 at 12:03
  • Oh yeah? What's the result of `"foo" - "bar"`? – Felix Kling Apr 16 '15 at 12:04
  • simpler xD. compare and if true return 1,if false return -1,if they are equal return 0. – Omar Elawady Apr 16 '15 at 12:10
7

Here is a solution that works for primitive values. It uses an object as a primitive map and counts the number of occurrences of each value. However, it also considers the data type of each value.

It checks the length of the arrays first, as shortcut:

function equals(a, b) {
    if (a.length !== b.length) {
        return false;
    }

    var seen = {};
    a.forEach(function(v) {
        var key = (typeof v) + v;
        if (!seen[key]) {
            seen[key] = 0;
        }
        seen[key] += 1;
    });

    return b.every(function(v) {
        var key = (typeof v) + v;
        if (seen[key]) {
            seen[key] -= 1;
            return true;
        }
        // not (anymore) in the map? Wrong count, we can stop here
    });
}
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • 3
    Good thing that ES 2015 has introduced Maps! Now type checking and stringification is obsolete and something like this can be used instead: `const compareOutOfOrder = (arr1, arr2) => { if(arr1.length !== arr2.length){ return false; } const counts = new Map(); arr1.forEach((value) => counts.set(value, (counts.get(value) ?? 0) + 1)); arr2.forEach((value) => counts.set(value, (counts.get(value) ?? 0) - 1)); return Array.from(counts.values()).every((count) => count === 0); };`. I doubt it’s possible to utilize a `return`…`.every(`…`);` that directly counts down here. – Sebastian Simon Jun 04 '21 at 08:24