2

I am learning Javascript, and I saw a function on SO for comparing arrays to check if they are same. However, my current function returns false if the two arrays are [string1,string2] & [string2,string1]. Basically, the two positions are interchanged. The code is as follows:

function _compareArrays(arr1,arr2){
    var result = arr1 != null && arr2 != null && arr1.length == arr2.length && arr1.every(function(element) {
            return arr2.indexOf(element);
        });
    return result ;
}

However, I want these two arrays to be returned as same. So, I changed .every to .indexOf() and it seemed to work. But, I have a doubt, how exactly is the counter getting incremented here to make sure the comparison is being done for every element? I mean, like, in C++, we do,

for (int i = 0; i < 10; i++)
    if (arr1[i] == arr2[i]
      cout<<"Elements are same\n";

Here, I have an explicit i++ which increments the counter. How does it happen in the above function?

Thanks!

sarah
  • 247
  • 1
  • 9
  • 19
  • 1
    your first code block is total rubbish. `indexOf` does not take a function as an argument in any documentation I've ever read - `So, I changed .every to .indexOf()` when you have a flat tyre, do you replace the wheel with a banana? – Jaromanda X Dec 06 '17 at 03:56
  • I'd just [create shallow copies](https://stackoverflow.com/questions/3978492/javascript-fastest-way-to-duplicate-an-array-slice-vs-for-loop) of the arrays, sort those and then use the comparison mechanism you mentioned – Phil Dec 06 '17 at 03:58
  • The `_compareArrays` method you've defined always returns -1. As for how the `every` method iterates without explicitly incrementing a counter: It's a function on the Array prototype that does the itteration automatically. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every – nipuna-g Dec 06 '17 at 04:02
  • See my edited function, will the above function also not work? – sarah Dec 06 '17 at 04:07
  • @sarah See my answer again. – doubleOrt Dec 06 '17 at 10:19
  • for a lowdash version - https://stackoverflow.com/questions/29951293/using-lodash-to-compare-arrays-items-existence-without-order – nycynik Jan 04 '19 at 17:10

3 Answers3

2

Convert the array into an object instead. Convert array values into keys and their respective count as their value. This is more performant as you iterate over the arrays only once.

function compare(a, b) {
  if (a.length !== b.length) {
    return false;
  }
  let set = {};
  a.forEach((i) => {
    if (set[i] !== undefined) {
      set[i]++;
    } else {
      set[i] = 1;
    }
  });
  let difference = b.every((i) => {
    if (set[i] === undefined) {
      return false;
    } else {
      set[i]--;
      if (set[i] === 0) {
        delete set[i];
      }
      return true;
    }
  });
  return Object.keys(set) == 0 && difference;
}

The first loop on the first array initialises the the set (object), the second loop on the second array subtracts the count and removes the keys when the count hits 0. If a key is not found or if the set is not empty at the end of the procedure, then the arrays are not similar.

vader
  • 889
  • 8
  • 22
1

Your current has these issues:

  1. It will return true for these 2 arrays (I hope that you understand that this is not specific to these 2): [1, 2, 2], [1,1,2]. This problem is why I set indices to undefined after a match in my solution below.

  2. indexOf returns -1 for element's it cannot find, and any number >= 0 if it can find the element, -1 is truthy, so if an element cannot be found, your comparison will return true, and 0 is falsy, so if an element is located in the first index of the second array, your method will return false (which means the whole thing will become false, because of every...). So, you should add a ~ before the result of indexOf call: ~arr2.indexOf(element).

    See this MDN page on the Bitwise Not operator (~) and how it solves the indexOf problem I mentioned.

    I also recommend that you take a look at this answer of mine on truthy/falsy values and how they interact with && and ||.


So Try this out (it's mostly your example, except there is no indexOf used and I have fixed problem #2):

function _compareArrays(arr1,arr2){
  if(!(arr1 != null && arr2 != null && arr1.length == arr2.length)) {
    return false;
  }

  /* copy the arrays so that the original arrays are not affected when we set the indices to "undefined" */
  arr1 = [].concat(arr1);
  arr2 = [].concat(arr2);

  return arr1.every(function(element, index) {
    return arr2.some(function(e, i) {
      return e === element && (arr2[i] = undefined, true);
    });
  });
}
    
var x = ["str", "boo", "str"];
var y = ["boo", "str", "str"];
var z = ["abc", "def", "ghi"]    

console.log(_compareArrays(x, y));
console.log(_compareArrays(x, z));
console.log(_compareArrays(z, z));

It won't work if the array has any undefined elements, though.

doubleOrt
  • 2,407
  • 1
  • 14
  • 34
0

So the fastest way would be to sort both arrays, then compare each one element by element. This requires 2n * log(n) + n time rather than n2 time.

function compareArrays(arr1, arr2){
  if(arr1.length !== arr2.length) return false;

  // implement custom sort if necessary
  arr1.sort();
  arr2.sort();

  // use normal for loop so we can return immediately if not equal
  for(let i=0; i<arr1.length; i++){
    if(arr1[i] !== arr2[i]) return false;
  }

  return true;
}

Luke
  • 186
  • 1
  • 7