javascriptarraysarray-comparison

How to get the difference between two arrays in JavaScript with repeating values


How can I check to see if one array is contained in another and return the missing values? I have found ways to do this in this post but none account for repeating values in the arrays. For example, I am trying to do something like this:

getDiff([1, 2, 3], [1, 2, 3, 4]) --> []

getDiff([1, 2, 2, 3], [1, 2, 3, 4]) --> [2]

getDiff(["A", "B", "C"], ["B", "B", "A", "C"]) --> []

getDiff(["B", "B", "B"], [3, 2, 1]) --> ["B", "B", "B"]

Solution

  • One possible approach:

    function getRepeatingDiff(source, diffed) {
      const diffedCounter = diffed.reduce((acc, el) => {
        acc[el] = (acc[el] || 0) + 1
        return acc;
      }, {});
      
      const diff = source.reduce((acc, el) => {
        return diffedCounter[el]-- > 0 ? acc : [ ...acc, el ];
      }, []);
      
      return diff;
    }
    
    console.log( getRepeatingDiff([1, 2, 3], [1, 2, 3, 4]) );
    console.log( getRepeatingDiff([1, 3, 2, 2, 3], [1, 2, 3, 4]) );
    console.log( getRepeatingDiff(["A", "B", "C"], ["B", "B", "A", "C"]) );
    console.log( getRepeatingDiff(["B", "B", "B"], [3, 2, 1]) );

    Essentially, it's a two-step process: calculate the number of items in diffed array, then go through source array one more time - and add a new item into diff resulting array for each copy missing in diffedCounter. This particular implementation has one deficiency though: as it uses Object to collect the counts, it doesn't differentiate between 3 as number and '3' as a string when counting elements.

    This might be fixed in two different ways: either switch to Map (but that'll make code more complicated, as there's no such things as decrementing for map values) - or just use type prefixes when creating a counter keys. Here's the approach based on former:

    function getRepeatingDiff(source, diffed) {
      const diffedCounter = diffed.reduce((map, el) => {
        const prev = map.get(el);
        return map.set(el, (prev || 0 ) + 1);
      }, new Map());
      
      const diff = source.reduce((acc, el) => {
        const remainingCount = diffedCounter.get(el);
        if (remainingCount) diffedCounter.set(el, remainingCount - 1);
        return remainingCount ? acc : [ ...acc, el ];
      }, []);
      
      return diff;
    }
    
    console.log( getRepeatingDiff([1, 2, 3], [1, 2, 3, 4]) );
    console.log( getRepeatingDiff([1, 3, 2, 2, 3], [1, 2, 3, 4]) );
    console.log( getRepeatingDiff(["A", "B", "C"], ["B", "B", "A", "C"]) );
    console.log( getRepeatingDiff(["B", "B", "B"], [3, 2, 1]) );