1

Primordial topic, I know, but can anyone please comment on the following algorithm for comparing two ES5 arrrays:

function equal_arrays(a, b) {
    "use strict";

    var c = 0;

    return a.every(function (e) {
        return e === b[c++];
    });
}

I think this is very clean and obvious use of [].every(), to quickly compare two arrays? I have that nagging "surely it can not be that simple ?" feeling about it ?

NOTE: two arrays are equal when they contain all elements in the same position strictly equal to each other. So both the element index and element value have to be exactly equal. Values of different types are considered not equal. Sparse arrays are also compared.

TEST CASES:

      equal_arrays([],[]) ; // => true
      equal_arrays([1,2],[1,2]) ; // => true
      equal_arrays([1,,2],[1,,2]) ; // => true
      equal_arrays([,],[,]); // => true
      equal_arrays([1,,3,,,],[1,,3,,,]); // => true

Uses cases yielding => false, one can imagine herself. Comparing non-arrays is a syntax error.

Many thanks to the well meaning and helpful contributors. It appears that the "best" (there is never such a thing) implementation is this:

function has(element, index)
{
    return this[index] === element;
}

function equal_arrays(a, b)
{
    return (a.length === b.length) && a.every(has, b) && b.every(has, a);
}

@tom's implementation of the two-way every() which is necessary sp that test cases like this one work:

equal_arrays([1,,3],[1,2,3]); //=> false 

Once again thanks ...

7
  • 2
    You don't need the incrementing c. Just define a second parameter to the every() callback, and you'll get your index. Other than that, if you want a strict equality evaluation, then yes, it's valid. Commented Jul 11, 2013 at 22:59
  • 1
    ...though one thing to keep in mind is that .every() will skip indices that are not defined in sparse arrays. So if a has a hole in it, and b does not, it won't bother checking the index where the hole is. So if b has something defined, you'll get a false positive. Also a good idea to test the lengths first. Commented Jul 11, 2013 at 23:02
  • It'll work well for arrays of number and string values. For arrays of objects, it'll expect object references to be exactly the same in order to be equal (which may be OK). Commented Jul 11, 2013 at 23:03
  • I guess it depends on your arrays and how deep they go. You haven't supplied any test criteria. Commented Jul 11, 2013 at 23:05
  • If the array a is shorter than b, and the items in a are the same the first items in b, the method will give a false positive. Commented Jul 11, 2013 at 23:06

4 Answers 4

4

No, it is not valid. From the MDN docs:

callback is invoked only for indexes of the array which have assigned values; it is not invoked for indexes which have been deleted or which have never been assigned values.

So if the first array has 'gaps' they will be skipped over.

equal_arrays([1, 2, , 4], [1, 2, 4]); // true
equal_arrays([1, 2, 4], [1, 2, , 4]); // false

Here is a better implementation:

function equal_arrays(a, b) {
    if (a.length != b.length) return false;
    var i;
    for (i = 0; i < a.length; i++) {
        if (a[i] !== b[i]) return false;
    }
    return true;
}

Here is an elegant implementation of @RobG's two-way every() idea:

function has(element, index)
{
    return this[index] === element;
}

function equal_arrays(a, b)
{
    return (a.length === b.length) && a.every(has, b) && b.every(has, a);
}
Sign up to request clarification or add additional context in comments.

Comments

3

I have that nagging "surely it can not be that simple ?" feeling about it ?

No, it isn't. Using every on one array will test it for every member of that array but not the other. You have to test both ways.

Also, it depends on your criteria for "equal". You might want every member of the same index to have the same value, but you might not be concerned about order so is [1,2] equal to [2,1]? and is [,,,2] equal to [2]?

Also, should [1,,2] equal [1,undefined,2], i.e. should a member that doesn't exist be "equal" to one that does but has the value undefined?

An array comparison function

The following may do the job. It compares both ways, checking own properties and values. I think it covers all cases but am willing to be convinced otherwise. Might be better as a separate function, but adding to Array.prototype is convenient.

// Test that own properties of two arrays have the same values
Array.prototype.isEqualTo = function(a) {
  var visited = {};
  if (this.length != a.length) return false;

  // Test every own property of this, remember tested properties
  for (var p in this) {

    if (this.hasOwnProperty(p)) {
      visited[p] = p;

      if (!(a.hasOwnProperty(p)) || this[p] !== a[p]) {
        return false; 
      }
    }
  }

  // Reverse test that comparison array only has tested properties
  for (var q in a) {
    if (a.hasOwnProperty(q) && !(q in this)) {
      return false;
    }
  }
  return true;
}

console.log([1,,2].isEqualTo([1,undefined,2])); // false, not defined != undefined
console.log([1,,2].isEqualTo([1,2]));           // false, different length
console.log([1,2].isEqualTo([1,2]));            // true

Note that inherited properties should be ignored as if arrays from different windows are compared (e.g. one is from a frame) then inherited properties won't be equal.

Comments

2

As an alternative, if you just want to check if both arrays are exactly the same you could just do this:

var a = [1,2,3,4];
var b = [1,2,3,4];

JSON.stringify(a) == JSON.stringify(b); //= true

That should work with arrays of numbers and strings.

6 Comments

a = ["hello,world"]; b = ["hello","world"];
@Pointy: How about JSON.stringify?
Using JSON for this is safer, but there are edge cases where it can fail unexpectedly.
@CrazyTrain: Probably yes. Just offering an alternative to the loop. I'd still use every.
@elclanrs—"edge cases" is new–speak for "things I didn't think of", e.g. the above will return true for a = [1,,2], b = [1,undefined,2], which may (or may not) be what the OP wants.
|
2

As CrazyTrain said, the increment value is useless and you need to check for length first:

function equalArrays( a, b ) {
  "use strict";
  if (a.length !== b.length) return false;
  return a.filter(function(el){ return el;}).every(function(e, i) {
    return e === b[i];
  });
}

Is a valid algorithm to compare arrays by value.

2 Comments

Still not quite right for arrays with unassigned indices: equalArrays([1, , 3], [1, 2, 3]) returns true (should be false).
Yeah right... The filter function takes care of sparse arrays.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.