freeCodeCamp Challenge Guide: Symmetric difference

Symmetric difference


Solutions

Solution 1 (Click to Show/Hide)
function symmetricDifference(A, B) {
  function relative_complement(A, B) {
    return A.filter(function(elem) {
      return B.indexOf(elem) == -1
    });
  }

  function unique(ary) {
    var u = ary.concat().sort();
    for (var i = 1; i < u.length;) {
      if (u[i - 1] === u[i])
        u.splice(i, 1);
      else
        i++;
    }
    return u;
  }

  return unique(relative_complement(A, B).concat(relative_complement(B, A))).sort();
}
4 Likes

Hi @Rafase282,

I’m not sure how to contribute to this, so I hope this is the right place. Symmetric difference turned out to be the hardest problem for me so far (went from very bruteforce to a respectable degree of cleverness).
Anyway, I think my solution is perhaps more readable as an Intermediate solution (I’m not confident enough to call it better, it may be less performant)… would you be so kind to have a look?

      function sym() {
      // get the arguments in a proper Array
      var args = Array.prototype.slice.call(arguments);
      // call reduce on the arguments using the diff function, and return the end-result
      return args.reduce(diff);
    }

    // returns the unique elements in the union of the arrays
    function diff(arrX, arrY){
      // clean up both arrays, so we don't have duplicates in a set/array
      arrX = sortAndClean(arrX);
      arrY = sortAndClean(arrY);
      // concatenate both arrays and sort
      // then cleverly filter out elements that show up more than once (common elements)
      // leaving us with the unique elements in the union
      var result = arrX.concat(arrY).sort(function(a,b){
        return a - b;
      }).filter(function(x, i, arr){
        return arr.indexOf(x) === arr.lastIndexOf(x);
      });

      return result;
    }
    // sortAndClean simply ends up giving us the unique elements in the Array
    // i.e. it sorts and de-duplicates
    function sortAndClean(arr){
      arr = arr.sort(function(a,b){
        return a - b;
      }).filter(function(x, i, arr){
        return x !== arr[i+1];
      });
      return arr;
    }
    //Test it
    sym([1, 2, 5], [2, 3, 5], [3, 4, 5]);

Cheers,
-Balach

1 Like

It was a difficult problem for me as well. I came up with this:


function sym() {
  var inputs = Array.from(arguments);
  
  return inputs.reduce(function(prev, current) {
    prev = Array.from(new Set(prev));
    current = Array.from(new Set(current));
    
    return prev.concat(current).filter(function(el, i, arr) {
      return (arr.indexOf(el) === (arr.length - 1) || arr.indexOf(el, i+1) === -1) &&
             ((arr.lastIndexOf(el) === 0) || arr.lastIndexOf(el, i-1) === -1);
    });
  });
  
}

sym([1, 2, 3], [5, 2, 1, 4]);
function sym(args) {
  var arr = Array.from(arguments);
  arr = arr.reduce(function(a, b) {
    var aNotB = a.filter(function(val) { return !b.includes(val); });
    var bNotA = b.filter(function(val) { return !a.includes(val); });
    return aNotB.concat(bNotA);
  });
  return arr.filter(function(value, index, self) {
    return self.indexOf(value) === index;
  });
}
12 Likes

I am confused when there are multiple values in a single set. Based on all the descriptions of Symmetric
Difference, the operation is a SET LEVEL comparison, not a comparison of ELEMENTS within a SET.

Thus I think the test is wrong…

For example is says this test – "sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]) should return [1, 4, 5]."
I think it should actually return [1,1,2,4,5]

Specifically they first set contains four elements 1, 1, 2 & 5. Although the number 1 is duplicated, for Symmetric Difference you are comparing ALL ELEMENTS in the first set with all elements in sets two and three. Since neither sets two or three contain a number one, all ones are valid. Furthermore if set two had only a single 1, then it would eliminate 1 of the number ones but not both.

Now if you said, “UNIQUE SET ELEMENTS” then I’d agree that the answer would be [1,4,5]

2 Likes

For what it’s worth, from the Wikipedia entry on Sets:

…two definitions of sets which differ only in that one of the definitions lists set members multiple times, define, in fact, the same set. Hence, the set {11, 6, 6} is exactly identical to the set {11, 6}. The second important point is that the order in which the elements of a set are listed is irrelevant (unlike for a sequence or tuple). We can illustrate these two important points with an example:

{6, 11} = {11, 6} = {11, 6, 6, 11} .

Javascript is consistent with this, e.g., new Set([11, 6, 6, 11]) results in creation of a set object {11, 6}

My take on a solution to this one:

function sym() {
 // Convert arguments into an array and eliminate duplicate elements 
 // within each argument.
  return [...arguments].map(arg => [...new Set(arg)])
     // concatenate successive arguments a and b     
     .reduce((a,b) => a.concat(b)
     // elements that are in both a and b will appear twice, filter 
     // these out to get the Symmetric Difference
     .filter((el, i, arr) => 
         !(arr.indexOf(el) < i || arr.indexOf(el, i + 1) > -1)))
    .sort((a,b) => a - b );
}

Thanks. I still recall from SAT and past problems describing a SET or (BAG) of “12 BLUE and 6 RED MARBLES” and a SET of “12 RED MARBLES and 6 BLUE MARBLES” with the Intersection being “6 RED and 6 BLUE”

It seems there is a distinction (that is not clear in the problem description) that they are talking about a pure mathematical description of a set…for example mathematicians would describe the set of WHOLE NUMBERS, and as such there are no duplicates WHOLE NUMBERS., the number ONE is the number ONE no matter how many times is listed. But if I’m talking about the SET of THINGS (like COLORED MARBLES) if I have FIVE BLUE MARBLES, I do in fact have FIVE different things.

Thanks Philip

Hi, this problem wasn’t the most dificult to me, the “Smallest Common Multiple Complete” problem freaked me out, so I’ll put here my solution, but I don’t think that is better, just a little diferent from the others soluctions shown here:

function sym(args) {
  
  var auxArguments = Array.from(arguments);
  var auxArr = [];
  
  for(var i = 1 ; i < auxArguments.length; i ++){
    auxArr = auxArguments[i-1].filter(function (value, index){
      return auxArguments[i].indexOf(value) === -1 && auxArguments[i - 1].indexOf(value, index + 1) === -1;
  }).concat(auxArguments[i].filter(function(value1, index1){
      return auxArguments[i - 1].indexOf(value1) === -1 && auxArguments[i].indexOf(value1, index1 + 1) === -1;
   }));
        
    auxArguments[i] = auxArr;
  }
  
  return auxArr;
  
}

@drguildo Thought I come up with a neat concise version, but you already have pretty much the same: :smiley:

/*jshint esversion: 6 */
function sym(params) {
  var args = Array.prototype.slice.call(arguments);
  return args.reduce((a, b) => {
    return a.filter(el => !b.includes(el)).concat(b.filter(el => !a.includes(el)));
  }, []).filter((el, i, arr) => arr.indexOf(el) === i).sort((a, b) => a - b);
}

I also sorted the array at the end, which is not necessary to pass the test but to get the same sorted result as proposed.

By the way, I like most of your solutions from the other exercises as well! It proves that you really put in some thought on refactoring like I do. :slight_smile:

How about this one-liner? Can anyone do it shorter? :stuck_out_tongue:

function sym(args) {
  return Array.from(arguments) \
         .reduce((a,b)=>a.concat(b).filter(el=>(a.indexOf(el)==-1||b.indexOf(el)==-1),[])) \
         .filter((el,index,array)=> array.indexOf(el)==index);
}
6 Likes

Here’s what I came up with. it was hard, ,but not the hardest for me to solve. I agree with others, it threw me for a loop that repeats of the same value were not allowed.


function diffArray(arr1,arr2) {
  console.log("starting to diff", arr1,arr2);
  var unique1=[];
  var unique2=[];
  var kindOfUnique;
  unique1=arr2.filter(function(val) {
    return arr1.indexOf(val) === -1;
  });
  console.log("unique1=", unique1);
  unique2=arr1.filter(function(val) {
    return arr2.indexOf(val) === -1;
  });
  console.log("unique2=", unique2);
  kindOfUnique=unique1.concat(unique2);
  var noDup = kindOfUnique.filter (function (value, index, array) {
    //console.log("value=", value, "index=", index);
    //  console.log(array);
      return array.indexOf (value) == index;
  });
  console.log(noDup);
  return noDup;

}
function sym(arg) {
  var combined= Array.prototype.slice.call(arguments);
  //console.log(combined);
  //console.log(combined.reduce(diffArray,[]));
  return combined.reduce(diffArray,[]);
}
sym([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]);


obtw, the ‘estimate’ of the number of hours to complete a section is laughable in my case…
(as in grossly underestimated)

3 Likes

Mine :

function sym(args) {
let arg = […arguments];
let newArr = [];
let redArr = arg.reduce((arrayOne,arrayTwo)=>{
newArr = arrayOne.filter((item)=>{return arrayTwo.indexOf(item)===-1;}).concat(arrayTwo.filter((item)=>{return arrayOne.indexOf(item) === -1;}));
return newArr.filter((item,pos)=>{return newArr.indexOf(item)===pos;});});
return redArr;
}

sym([3, 3, 3, 2, 5], [2, 1, 5, 7], [3, 4, 6, 6], [1, 2, 3], [5, 3, 9, 8], [1]);

Very nice, understandable solution. I played with it a bit and it appears you don’t actually need reduce.

function diffArray(arr1, arr2) {
  var newArr = arr1.concat(arr2)
    .filter( (el) => arr1.indexOf(el) == -1 || arr2.indexOf(el) == -1)
    .filter( (el, index, array) => array.indexOf(el) == index);

  return newArr;
}

Notes:

  • This problem is limited to diffing two arrays. It appears an earlier incarnation of the test allowed an arbitrary number of argument arrays.
  • For the given test input (both in the main challenge set and the new beta, which appear identical for this problem), the second filter is not necessary. But try diffArray([1, 1, 2], [2, 3 ]) to see why it’s needed.

@pennyJack
@cambsCoder
Hey there! I’ve made almost the same solution, it’s kinda fun to know someone else wrote the same code:

const sym = (...args) =>
  args.reduce((acc, val) =>
    acc.concat(val).filter(x => !val.includes(x) || !acc.includes(x))
  ).filter((x, i, self) => self.indexOf(x) == i);

How about using a spread operator in your solution like […arguments] instead of Array.prototype.slice.call(arguments)? I know it’s poorly supported, but you’ve already used includes() which is not so good as well :slight_smile:
Here is the link: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator

1 Like

I would like to offer this solution:

function unique (value, index, self){
  return self.indexOf(value)===index
  }

function sym(...args) {
  var arr = args.reduce(function(accum, item){
    item=item.filter(unique)
    var newAccum = accum.filter(element => !item.includes(element))
    var newAccum2 = item.filter(element => !accum.includes(element))
    newAccum = newAccum.concat(newAccum2)
    return newAccum
  }, [])
  return arr;
}

Here my solution too. Slightly different than the other solutions here.

function sym(args) {
var difference = [];
var unique = [];
for (var i=0; i<arguments.length; i++) {
/* Make each element in the individual array unique */
unique = arguments[i].filter(function(item, j, ar){ return ar.indexOf(item) === j; });

/* Compare each element for being in the difference-aray */
for (var y=0; y<unique.length; y++) {
  pos = difference.indexOf(unique[y]);
  if (pos == -1) {
    difference.push(unique[y]);
  }
  else {
    difference.splice(pos, 1);
  }
}

}
return difference;
}