Code Review Stack Exchange is a question and answer site for peer programmer code reviews. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

This is my challenge using JavaScript -> Compare two arrays and return a new array with any items only found in one of the two given arrays, but not both. In other words, return the symmetric difference of the two arrays.

My code works, but I think that it can be better.

function diffArray(arr1, arr2) {
 var newArr = [];

  arr1.map(function(val){
   arr2.indexOf(val) < 0 ? newArr.push(val) : '';
  });

  arr2.map(function(val){
   arr1.indexOf(val) < 0 ? newArr.push(val) : '';
  });

  return newArr;
}

diffArray([1, 2, 3, 5, 6, 7], [1, 2, 3, 4, 5]);

What do you think?

share|improve this question
    
Array.prototype.filter() is very appropriate for your usage case: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… – st88 Apr 6 at 8:23

map is specifically designed to map a value to another. You're merely looping through the array, not actually mapping. This is best expressed by using forEach instead.

function diffArray(arr1, arr2) {
 var newArr = [];

  arr1.forEach(function(val){
   if(arr2.indexOf(val) < 0) newArr.push(val);
  });

  arr2.forEach(function(val){
   if(arr1.indexOf(val) < 0) newArr.push(val);
  });

  return newArr;
}
share|improve this answer

As for performance: Array.indexOf in the worse case(element is not there), will have to check all values of the array to check if the value is in it. And doing this for all values of the second array, means O(n * m) where n is the arr1 size and m is the arr2 size. If n = m, we can consider that it takes O(n*n), which is quite expensive. You could improve this by sorting both arrays and doing a smarter check, but even that will have the cost of the sorting, which is O(n * log n) with a good implementation

Having said all that, what you really want is a structure to check if you have seen or not an element that can check it really quick. In javascript, we can use objets to do this. As its native code we don´t really know the complexity of access, but with testing a bit it is clear that it´s much better (in theory access should be O(1) or O(log n))

The pseudo code is like this:

var seen = {}
iterate all map1 values and mark is as seen
iterate all map2 values and unmark if already seen, mark if not
add all seen values to new array
return array

And here is a full fiddle:

function diffArray(arr1, arr2) {
  var newArr = [];

  arr1.map(function(val) {
    arr2.indexOf(val) < 0 ? newArr.push(val) : '';
  });

  arr2.map(function(val) {
    arr1.indexOf(val) < 0 ? newArr.push(val) : '';
  });

  return newArr;
}


function diffArray2(arr1, arr2) {
  seen = {}
  var newArr = []
  arr1.forEach(function(val) {
    seen[val] = 1 // we saw it on first array	
  })
  arr2.forEach(function(val) {
    if (seen[val] == 1) { // we already saw it on the first one, unmark
      seen[val] = false
    } else if (seen.hasOwnProperty(seen[val]) == false) { // if we hadnt seen it earlier
      seen[val] = 2 // mark with a 2
    }
  })

  for (var val in seen) {
    if (seen[val]) { // if its a 1 or a 2, it was unique
      newArr.push(val)
    }
  }
  return newArr
}

var arr1 = []
var arr2 = []
var mx = 10000;
for (var i = 0; i < mx; i++) {
  arr1.push(i)
  arr2.push(i + Math.round(mx / 2))
}

console.time("diffArray with arrays and .index")
diffArray(arr1, arr2);
console.timeEnd("diffArray with arrays and .index")


console.time("diffArray with object")
diffArray2(arr1, arr2);
console.timeEnd("diffArray with object")

share|improve this answer

This may not be more efficient than juvian's answer, but I think it's a lot cleaner to use Array.prototype.filter; it's usage, as the name suggests, it to filter elements out of an array, which is exactly what you are trying to do.

function diffArray(a, b) {
    return   a.filter(function(val) { // reveal the unique values in a
        return b.indexOf(val) < 0;
    }.concat(b.filter(function(val) { // concat those with the unique values in b
        return a.indexOf(val) < 0;
    }
}

And, if you want, you could make it even cleaner by extracting the filter parts to their own functions:

function getUniqueValues(src, other) {
    return src.filter(function(val) {
        return other.indexOf(val) < 0;
    }
}

Then, your main function becomes:

function diffArr(a, b) {
    return getUniqueValues(a, b).concat(getUniqueValues(b, a));
}

Again, this may not be more efficient, but it is more idiomatic in that is uses the right built-in JavaScript methods for the job.

Note: it may be more efficient to sort both arrays for faster computing overall:

function diffArr(a, b) {
    a.sort();
    b.sort();
    ...
}
share|improve this answer

Your best fit for this issue is to use the (relatively) new guy in town : the Set, that will do the job for you of ignoring duplicate.
Most browsers support it now, and i looked a few jsperf and it is already fast. The Set is especially interesting if you have many duplicates.

Edit : i changed my code for the more efficient code of @juvian, thks to him for his update.

function diffArray(arr1, arr2) {
   var set1 = new Set(arr1);
   var set2 = new Set(arr2);

   var arr = []   

   set1.forEach(function(val) {  
     if (!set2.has(val)) arr.push(val); 
   });
   set2.forEach(function(val) {  
     if (!set1.has(val)) arr.push(val); 
   });

   return arr;
}

( Notice that if performance matters you'll want to create only once those 4 Set and only clear() them before use ).

share|improve this answer
    
Always nice to see people taking advantage of new structures! Would recommend some few changes to make it a bit more performant though: jsfiddle.net/hL9hgynq – juvian Apr 7 at 15:04

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.