Search code examples
javascriptarrayssymmetric-difference

Trying to solve symmetric difference using Javascript


I am trying to figure out a solution for symmetric difference using javascript that accomplishes the following objectives:

  • accepts an unspecified number of arrays as arguments
  • preserves the original order of the numbers in the arrays
  • does not remove duplicates of numbers in single arrays
  • removes duplicates occurring across arrays

Thus, for example, if the input is ([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]), the solution would be, [1, 1, 6, 5, 4].

I am trying to solve this as challenge given by an online coding community. The exact instructions of the challenge state,

Create a function that takes two or more arrays and returns an array of the symmetric difference of the provided arrays.

The mathematical term symmetric difference refers to the elements in two sets that are in either the first or second set, but not in both.

Although my solution below finds the numbers that are unique to each array, it eliminates all numbers occuring more than once and does not keep the order of the numbers.

My question is very close to the one asked at finding symmetric difference/unique elements in multiple arrays in javascript. However, the solution does not preserve the original order of the numbers and does not preserve duplicates of unique numbers occurring in single arrays.

function sym(args){
    var arr = [];
    var result = [];
    var units;
    var index = {};
    for(var i in arguments){
        units = arguments[i];

    for(var j = 0; j < units.length; j++){
         arr.push(units[j]);
        }
    }

    arr.forEach(function(a){
        if(!index[a]){
            index[a] = 0;
        }
            index[a]++;

    });

       for(var l in index){
           if(index[l] === 1){
               result.push(+l);
           }
       }

    return result;
}
symsym([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]); // => Desired answer: [1, 1, 6. 5. 4]

Solution

  • Here's a version that uses the Set object to make for faster lookup. Here's the basic logic:

    1. It puts each array passed as an argument into a separate Set object (to faciliate fast lookup).
    2. Then, it iterates each passed in array and compares it to the other Set objects (the ones not made from the array being iterated).
    3. If the item is not found in any of the other Sets, then it is added to the result.

    So, it starts with the first array [1, 1, 2, 6]. Since 1 is not found in either of the other arrays, each of the first two 1 values are added to the result. Then 2 is found in the second set so it is not added to the result. Then 6 is not found in either of the other two sets so it is added to the result. The same process repeats for the second array [2, 3, 5] where 2 and 3 are found in other Sets, but 5 is not so 5 is added to the result. And, for the last array, only 4 is not found in the other Sets. So, the final result is [1,1,6,5,4].

    The Set objects are used for convenience and performance. One could use .indexOf() to look them up in each array or one could make your own Set-like lookup with a plain object if you didn't want to rely on the Set object. There's also a partial polyfill for the Set object that would work here in this answer.

    function symDiff() {
        var sets = [], result = [];
        // make copy of arguments into an array
        var args = Array.prototype.slice.call(arguments, 0);
        // put each array into a set for easy lookup
        args.forEach(function(arr) {
            sets.push(new Set(arr));
        });
        // now see which elements in each array are unique 
        // e.g. not contained in the other sets
        args.forEach(function(array, arrayIndex) {
            // iterate each item in the array
            array.forEach(function(item) {
                var found = false;
                // iterate each set (use a plain for loop so it's easier to break)
                for (var setIndex = 0; setIndex < sets.length; setIndex++) {
                    // skip the set from our own array
                    if (setIndex !== arrayIndex) {
                        if (sets[setIndex].has(item)) {
                            // if the set has this item
                            found = true;
                            break;
                        }
                    }
                }
                if (!found) {
                    result.push(item);
                }
            });
        });
        return result;
    }
    
    var r = symDiff([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]);
    log(r);
    
    function log(x) {
        var d = document.createElement("div");
        d.textContent = JSON.stringify(x);
        document.body.appendChild(d);
    }

    One key part of this code is how it compares a given item to the Sets from the other arrays. It just iterates through the list of Set objects, but it skips the Set object that has the same index in the array as the array being iterated. That skips the Set made from this array so it's only looking for items that exist in other arrays. That allows it to retain duplicates that occur in only one array.


    Here's a version that uses the Set object if it's present, but inserts a teeny replacement if not (so this will work in more older browsers):

    function symDiff() {
        var sets = [], result = [], LocalSet;
        if (typeof Set === "function") {
            try {
                // test to see if constructor supports iterable arg
                var temp = new Set([1,2,3]);
                if (temp.size === 3) {
                    LocalSet = Set;
                }
            } catch(e) {}
        }
        if (!LocalSet) {
            // use teeny polyfill for Set
            LocalSet = function(arr) {
                this.has = function(item) {
                    return arr.indexOf(item) !== -1;
                }
            }
        }
        // make copy of arguments into an array
        var args = Array.prototype.slice.call(arguments, 0);
        // put each array into a set for easy lookup
        args.forEach(function(arr) {
            sets.push(new LocalSet(arr));
        });
        // now see which elements in each array are unique 
        // e.g. not contained in the other sets
        args.forEach(function(array, arrayIndex) {
            // iterate each item in the array
            array.forEach(function(item) {
                var found = false;
                // iterate each set (use a plain for loop so it's easier to break)
                for (var setIndex = 0; setIndex < sets.length; setIndex++) {
                    // skip the set from our own array
                    if (setIndex !== arrayIndex) {
                        if (sets[setIndex].has(item)) {
                            // if the set has this item
                            found = true;
                            break;
                        }
                    }
                }
                if (!found) {
                    result.push(item);
                }
            });
        });
        return result;
    }
    
    
    var r = symDiff([1, 1, 2, 6], [2, 3, 5], [2, 3, 4]);
    log(r);
    
    function log(x) {
        var d = document.createElement("div");
        d.textContent = JSON.stringify(x);
        document.body.appendChild(d);
    }