views:

108

answers:

6

I have an array that contains an array of arrays if that makes any sense. so for example:

[[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]]

I want to see whether an array exists withing the array, so if [1, 2, 3] is duplicated at all. I have tried to use the .indexOf method but it does find the duplicate. I have also tried Extjs to loop through the array manually and to evaluate each inner array, this is how I did it:

var arrayToSearch = [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]];        
var newArray = [1, 2, 3];
Ext.each(arrayToSearch, function(entry, index){
                    console.log(newArray, entry);
                    if(newArray == entry){
                        console.log(index);
                    };
                });

This also does not detect the duplicate. the console.log will output [1, 2, 3] and [1, 2, 3] but will not recognize them as equal. I have also tried the === evaluator but obviously since == doesn't work the === wont work. I am at wits end, any suggestions.

+1  A: 

You need to write a helper method that compares 2 arrays element by element and use that instead of ===.

z5h
+2  A: 

Unfortunately, in the general case, the only way you can tell if arrays are equal in this sense is to compare their elements. There's no shortcut or built-in way of doing that.

In some special cases you can make your life a bit easier by using Array#join and comparing the resulting strings, e.g.:

var a = [1, 2, 3];
var b = [1, 2, 3];
alert(a.join(",") == b.join(",")); // Alerts "true"

...because a.join(",") results in the string "1,2,3", as does b.join(","). But obviously you can only do that when you know that you can concatenate and compare the values meaningfully like that. Doing this may (may) be faster because you can leverage the JavaScript interpreter's internal join and string comparison methods, but again, you can't do this in the general case, only when you know the data in the array is going to be okay with being turned into a string and concatenated like that.

T.J. Crowder
+3  A: 

I've often found that the best way to compare two arrays is to compare their join values.

if(newArray.join('/') == entry.join('/')) ...

Additionally, you may want to throw in one more check:

if(newArray.length == entry.length && newArray.join('/') == entry.join('/'))

If the length check is invalidated, that's a really quick way of invalidating the comparison and not bother with doing the joins, but the check also improves the reliability of the comparison. For instance, without the length check, the following arrays would yield the same result when joined:

var a = [1, '2/3'];
var b = [1, 2, 3];

With the length check, you're sure something like this can't happen, and don't have to worry about escaping delimiters or anything...

David Hedlund
Not only does this hide your intent, it's also inefficient in terms of memory and CPU use (in the general case). Why do this when you could write a proper compare in a few minutes?
z5h
@z5h: Have you actually *tried* both methods and compared the performance of the results? `Array#join` and string compare can happen within the interpreter's guts, rather than being in interpreted JavaScript code (interpreted on almost all major browsers, Chrome being the counter-example -- it compiles!). You might well be surprised. There are several of these counter-intuitive-on-the-face-of-it things in browser-based JavaScript.
T.J. Crowder
@z5h: i don't agree that this obscures the programmer's intent at all. i think this is a very straightforward approach - a simple comparison between a lefthand and a righthand statement could hardly be surpassed in readability. of course the code should be optimized to only join `newArray` once; i don't know if js optimizes that natively... whether or not cpu cycles is a priority i'll leave for OP to decide.
David Hedlund
no, but I suspected as much. That's why I wrote general case. You're also creating a new string for every entry that will need to be garbage collected.
z5h
@David: You have 13.4k reputation and the person who asked the question was having trouble with something fairly simple. I just thought that the .join solution was not appropriate for the person who asked the question. Also, this person might not understand that it will fail for something like `['//','/']` compared against `['/','//']`. I haven't downvoted you, as I think there is still good information in your response.
z5h
@z5h: of course, i don't mind your objections. i don't mind a downvote either, if it's deserved. i simply don't share your view on what's a plain and simple solution to the problem at hand. 'tis all =)
David Hedlund
+6  A: 

Comparing the two arrays using == or === will not work because they are not the same object. If you want to determine the element-wise equality of two arrays you need to compare the arrays element-wise.

I doubt you'll gain anything from using tricks like join(',') and then using string operations. The following should work though:

function arraysAreEqual (a, 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;
}

function containsArray (arrays, target) {
  for (var i=0; i<arrays.length; i++) {
    if (arraysAreEqual(arrays[i], target)) {
      return true;
    }
  }

  return false;
}

var arraysToSearch = [[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 6]];
var newArray = [1, 2, 3];
containsArray(arraysToSearch, newArray);
ntownsend
+1 for providing code. Of course, this assumes that the arrays don't contain anything that can't be compared via `==` (such as other arrays). But the OP's sample data doesn't have any of those, so...
T.J. Crowder
A: 

In the spirit of an interesting challenge, I wrote the following function. It works, and handles simple cases of (arrays, objects, numbers, dates, functions, & strings)

Note: If you pass it things like {document} you are in for a world of hurt, but the simple stuff works.

function compare(a,b){
  if(a instanceof Array && b instanceof Array){
    if(a.length != b.length){
      return false;
    }
    for(var i=0,l=a.length;i<l;i++){
      if(!compare(a[i], b[i])){
      return false;
      }
    }
    return true;
  } else if(typeof(a) == 'object' && typeof(b) == 'object'){
    var keys = {};
    for(var i in a){
      keys[i] = true;
      if(!compare(a[i], b[i])){
      return false;
      }
    }
    //what if b contains a key not in a?
    for(var i in b){
      if(!keys[i]){
      return false;
      }
    }
    return true;
  } else {
    return (a == b);
  }
}

var someDate = new Date();
var someFunc = function(){alert('cheese');};

var foo = {};
foo['a'] = 'asdf';
foo['b'] = 'qwer';
foo['c'] = 'zxcv';
foo['d'] = ['a','b','c','d','e'];
foo['e'] = someDate;
foo['f'] = 34;
foo['g'] = someFunc

var bar = {};
bar['a'] = 'asdf';
bar['b'] = 'qwer';
bar['c'] = 'zx' + 'cv';
bar['d'] = ['a','b','c','d','e'];
bar['e'] = someDate;
bar['f'] = 34;
bar['g'] = someFunc

if(compare(foo, bar)){
  alert('same!');
} else {
  alert('diff!');
}
scunliffe
When and where does typeof return "array"? This is one of the most well-known "bad parts" of JavaScript...
ken
Ah, thanks @ken, I copied the wrong code from my test page.
scunliffe
You might want to do `hasOwnProperty` checks in your `for ... in` loops so that you're not going through all the properties in the prototype chain.
ntownsend
A: 

One possible alternative to explore would be using Array.toSource():

>>> [3, 2, 1].toSource() == [3, 2, 1].toSource()
true
ken
scunliffe
Ah, I knew there was a reason that I never used it :)
ken