0

I'm trying to work out how to match arrays that share the same elements, but not necessarily in the same order.

For example, these two arrays share the same set of elements, even though they're in a different order.

Is there any way to determine whether two arrays contain the same elements?

var search1 = ["barry", "beth", "debbie"];
var search2 = ["beth", "barry", "debbie"];

if (search1 == search2) {
    document.write("We've found a match!");
} else {
    document.write("Nothing matches");
}

I've got a Codepen of this running at the moment over here: http://codepen.io/realph/pen/grblI

royhowie
  • 11,075
  • 14
  • 50
  • 67
realph
  • 4,481
  • 13
  • 49
  • 104
  • Sort them, then compare. – ahruss Jul 21 '14 at 20:14
  • @ahruss I've tried that. But I seem to be getting the boolean `false` returned. – realph Jul 21 '14 at 20:17
  • 1
    You can't compare by reference, which is what you're doing in your code. See http://stackoverflow.com/questions/7837456/comparing-two-arrays-in-javascript – ahruss Jul 21 '14 at 20:21
  • You'll wont be able to do this without some sort of looping / sorting happening whereby you check each value. When you simply do array1 === array2 it's just going to tell you if it's exactly the same instance (ie: if array1 and array2 are refs back to the same exact array). Clearly if both are different arrays with different orderings of items then they're not the same array and you must loop over the values and check them against each other to figure this out. – Mike Driver Jul 21 '14 at 20:27
  • @ahruss No need to sort them. I wrote a solution of linear complexity below. Sorting (depending on the algorithm) is slower. Comparing via a `for` loop inside of another `for` loop is even slower. – royhowie Jul 21 '14 at 20:34

5 Answers5

7

The problem with some of the other solutions is that they are of O(n²) complexity, if they're using a for loop inside of a for loop. That's slow! You don't need to sort either—also slow.

We can speed this up to O(2n) complexity1 by using a simple dictionary. This adds O(2n) storage, but that hardly matters.

JavaScript

var isEqual = function (arr1, arr2) {
    if (arr1.length !== arr2.length) {
        return false;   // no point in wasting time if they are of different lengths
    } else {
        var holder = {}, i = 0, l = arr2.length;
        // holder is our dictionary
        arr1.forEach(function (d) {
            holder[d] = true;    // put each item in arr1 into the dictionary
        })
        for (; i < l; i++) {     // run through the second array
            if (!(arr2[i] in holder)) return false;
            // if it's not in the dictionary, return false
        }
        return true;    // otherwise, return true
    }
}

Test Case

var arr1 = ["barry", "beth", "debbie"],
    arr2 = ["beth", "barry", "debbie"];

console.log(isEqual(arr1,arr2));
// returns true

fiddle

Improvement

As Ahruss pointed out, the above function will return true for two arrays that are seemingly equal. For example, [1,1,2,3] and [1,2,2,3] would return true. To overcome this, simply use a counter in the dictionary. This works because !undefined and !0 both return true.

var isReallyEqual = function (arr1, arr2) {
    if (arr1.length !== arr2.length) {
        return false;   // no point in wasting time if they are of different lengths
    } else {
        var holder = {}, i = 0, l = arr2.length;
        // holder is our dictionary
        arr1.forEach(function (d) {
            holder[d] = (holder[d] || 0) + 1;
            // checks whether holder[d] is in the dictionary: holder[d] || 0
            // this basically forces a cast to 0 if holder[d] === undefined
            // then increments the value
        })
        for (; i < l; i++) {     // run through the second array
            if (!holder[arr2[i]]) {    // if it's not "in" the dictionary
                return false;          // return false
                // this works because holder[arr2[i]] can be either
                // undefined or 0 (or a number > 0)
                // if it's not there at all, this will correctly return false
                // if it's 0 and there should be another one
                // (first array has the element twice, second array has it once)
                // it will also return false
            } else {
                holder[arr2[i]] -= 1;    // otherwise decrement the counter
            }
        }
        return true;
        // all good, so return true
    }
}

Test Case

var arr1 = [1, 1, 2],
    arr2 = [1, 2, 2];

isEqual(arr1, arr2);          // returns true
isReallyEqual(arr1, arr2);    // returns false;

1: It's really O(n+m) complexity, whereby n is the size of the first array and m of the second array. However, in theory, m === n, if the arrays are equal, or the difference is nominal as n -> ∞, so it can be said to be of O(2n) complexity. If you're feeling really pedantic, you can say it's of O(n), or linear, complexity.

Community
  • 1
  • 1
royhowie
  • 11,075
  • 14
  • 50
  • 67
  • Dude, that is a bada** solution! Nice thinking it through and using JSs 'dictionary'! – AmmarCSE Jul 21 '14 at 23:12
  • @Luxelin I'm very new to JS, so please forgive me, but from what I can understand of your answer, that's a really elegant solution! Do you mind if I expand on my original question? I want to use an `object`, and if the `arr2` matches all names in that `object`, I'd like it to return the object `name`. If, however it doesn't, I'd like it to jump to an `else if` statement that returns the object `name` that matches all the remaining `members` – if that makes sense. I've created another CodePen here based on your answer: http://codepen.io/realph/pen/HrdGy Please excuse the `for` loop. – realph Jul 22 '14 at 01:12
  • @realph Here's a [fiddle](http://jsfiddle.net/Luxelin/rs887/). (Personally, I like fiddles more than codepens because of the layout, but use whatever you prefer ofc.) Note that I changed `var groups = { "group" : […]}` to `var groups = [{}]`. If you need to use the old structure, just change `console.log(findMatchingArray(arr2, groups));` on `line 53` to `console.log(findMatchingArray(arr2, groups.group));` – royhowie Jul 22 '14 at 01:27
  • @Luxelin Dude, you're a rockstar! I'm going to try and read up on some of the code you're using now to try and understand it. Appreciate the comments too! Am I supposed to be seeing something on the console? – realph Jul 22 '14 at 01:35
  • 1
    @realph Don't worry! I knew absolutely no JavaScript but a year ago. Literally nothing. We all have to start at some point, though. The key to improvement is practice—write as many programs as you possibly can. JavaScript may seem quirky at first, but, imo, it is the best language. Hands down. So powerful, especially when it comes to [arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array), which are also [stacks and queues](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push). – royhowie Jul 22 '14 at 01:36
  • @realph Yeah. Open up your browser's console (for chrome: command-alt-J on mac, control-shift-J on windows). It should appear there. `console.log` is generally a better debugging tool than `document.write` (jsfiddle doesn't let you use `document.write` anyway). – royhowie Jul 22 '14 at 01:38
  • @Luxelin Thanks a lot duder! Appreciate all the help with this. I'm going to crack on with this now and see if I can pull something out of this object and see if I can work the rest out myself. Thanks a million. – realph Jul 22 '14 at 01:42
  • This is a great solution assuming you don't care about any duplicated elements in the array. This returns true for [1,2,3,4,4] and [1,1,2,3,4], for example. Also, if your values are objects, this doesn't work. You can make this work with a JSON.stringify before setting the dictionary value. Of course, for very complex objects that could become expensive. But as it is, as long as there are the same number of objects in both arrays, this will return true. – ahruss Jul 23 '14 at 15:30
  • @ahruss I actually didn't think of that. I suppose you could overcome the duplicate element problem by setting `holder[arr2[i]]` to `false`. This means if an element in `holder` is either `false` or `undefined`, it will return false. As for the objects, I suppose you could compare them, similar to how I merged an array of objects in [this answer](http://stackoverflow.com/a/24847271/2476755). – royhowie Jul 23 '14 at 15:34
  • That was my first thought, too, but then you run into a problem if you have duplicate elements and the arrays *do* match, right? [1,1,2] wouldn't match itself. My thought was instead of using a bool, keep track of the count and decrement the counter, and if it's 0 or undefined for an element (so anything falsy), return false. So something like `holder[d] = (holder[d] || 0) + 1` and then `if (!holder[arr2[i]]) { return false; } else { holder[arr2[i]]--; }` – ahruss Jul 23 '14 at 15:40
  • @ahruss That would work too b/c `0` is indeed false-y. I'll update my answer to include that part, for future visitors. – royhowie Jul 23 '14 at 15:44
0

Feed your arrays to the following function:

function isArrayEqual(firstArray, secondArray) {
  if (firstArray === secondArray) return true;
  if (firstArray == null || secondArray == null) return false;
  if (firstArray.length != secondArray.length) return false;

  // optional - sort the arrays
  // firstArray.sort();
  // secondArray.sort();

  for (var i = 0; i < firstArray.length; ++i) {
    if (firstArray[i] !== secondArray[i]) return false;
  }
  return true;
}

Now you may be thinking, can't I just say arrayOne.sort() and arrayTwo.sort() then compare if arrayOne == arrayTwo? The answer is no you can't in your case. While their contents may be the same, they're not the same object (comparison by reference).

m.casey
  • 2,579
  • 17
  • 10
0

Sort them firstly. Secondly, if their length is different, then they're not a match. After that, iterate one array and test a[i] with b[i], a being the first array, b the second.

var search1 = ["barry", "beth", "debbie"],
    search2 = ["beth", "barry", "debbie"];

// If length are different, than we have no match.
if ((search1.length != search2.length) || (search1 == null || search2 == null))
  document.write("Nothing matches");

var a = search1.sort(),
    b = search2.sort(),
    areEqual = true;

for (var i = 0; i < a.length; i++) {
  // if any two values from the two arrays are different, than we have no match.
  if (a[i] != b[i]) {
    areEqual = false;
    break; // no need to continue
  }
}

document.write(areEqual ? "We've found a match!" : "Nothing matches");
Nicolae Olariu
  • 2,487
  • 2
  • 18
  • 30
  • 1
    Perhaps you should check their lengths before sorting to prevent unnecessary sorting. – sushain97 Jul 21 '14 at 20:23
  • Or null. Also, it's not necessary to re-order the array. The order may be important. I addressed these in my answer. – m.casey Jul 21 '14 at 20:24
0

you can use this function to compare two arrays

function getMatch(a, b) {
    for ( var i = 0; i < a.length; i++ ) {
        for ( var e = 0; e < b.length; e++ ) {
            if ( a[i] === b[e] ){
                return true;
            }
        }
    }   
}
neno
  • 303
  • 1
  • 7
  • This incorrectly returns `true` for `[1,2,3]` and `[1,5]` (along with for any two arrays wherein the first elements are equal). It also incorrectly returns `false` for `[2,1]` and `[1,2]`, which, by the OP's specifications, are "equal." – royhowie Jul 21 '14 at 20:35
0

You need to simply sort them, then compare them

function compareArrayItems(array1, array2){
    array1 = array1.sort();
    array2 = array2.sort();

    return array1.equals(array2);
}

fiddle

You can use the equals function provided in How to compare arrays in JavaScript?

Community
  • 1
  • 1
AmmarCSE
  • 30,079
  • 5
  • 45
  • 53