Comparing stuff in arrays

the second most highly rated answer here is a very lucid version of what to write, this post is just to add notes on top of it: https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript

I want to find overlapping stuff in between two arrays

it's called an intersection.

intersection

const intersectionBetweenArrays = (array1, array2) =>
  array1.filter(x => array2.includes(x))

example results

const arr1 = [1, 2, 3]
const arr2 = [2, 3, 5]

const res1 = intersectionBetweenArrays(arr1, [2, 3])
const res2 = intersectionBetweenArrays(arr1, arr2)

console.log(res1) // [ 2, 3 ]
console.log(res2) // [ 2, 3 ]

I want to find the non-overlapping stuff in between two arrays

it's called a symmetric difference

this is the inverse of an intersection. there's also 'difference', which is asymmetric, meaning its only the difference in the first array

const differenceBetweenArrays = (array1, array2, type = 'both') => {
  const diffA = array1.filter(x => !array2.includes(x))
  if (type === 'left') return diffA // difference

  const diffB = array2.filter(x => !array1.includes(x))
  if (type === 'right') return diffB

  if (type === 'both') return diffA.concat(diffB) // symmetric difference
  throw new Error(
    "incorrect type specified. please select either 'left', 'right', or 'both'"
  )
}

example results

const arr1 = [1, 2, 3]
const arr2 = [2, 3, 5]

const res1 = differenceBetweenArrays(arr1, [2, 3])
const res2 = differenceBetweenArrays(arr1, arr2)
const res3 = differenceBetweenArrays([2, 3], arr2)

console.log(res1) // [ 1 ]
console.log(res2) // [ 1, 5 ]
console.log(res3) // [ 5 ]

I want to do the above, but on arrays that are not flat

for example, I want 'foo' and 'bar' objects:

const first = [
  { key: 'foo', value: 1212 },
  { key: 'bar', value: 1234 },
]
const second = [
  { key: 'foo', value: 1212 },
  { key: 'bar', value: 1234 },
  { key: 'baz', value: 3434 },
]

if you want them, with the assumption that the values are the same:

doing it without checking for similarity

params:

const intersectionBetweenArraysNaive = (array1, array2, selector) => {
  if (!selector) return array1.filter(x => array2.includes(x))
  return array1.filter(x => array2.map(selector).includes(selector(x)))
}

so when your data matches, it does this:

const arr1 = [
  { key: 'foo', value: 1212 }, // these are the same within arr1 and arr2
  { key: 'bar', value: 1234 }, // these are the same within arr1 and arr2
]
const arr2 = [
  { key: 'foo', value: 1212 }, // these are the same within arr1 and arr2
  { key: 'bar', value: 1234 }, // these are the same within arr1 and arr2
  { key: 'baz', value: 3434 },
]

const res1 = intersectionBetweenArraysNaive(arr1, arr2, obj => obj.key)

console.log(res1) // [ { key: 'foo', value: 1212 }, { key: 'bar', value: 1234 } ]

and when you data DOESN'T match, it just takes the one from array1 due to the implementation:

const arr1 = [
  { key: 'foo', value: 1212 },
  { key: 'bar', value: 1234 },
]
const arr2 = [
  { key: 'foo', value: 1111 },
  { key: 'bar', value: 5555 },
  { key: 'baz', value: 3434 },
]

const res1 = intersectionBetweenArraysNaive(arr1, arr2, obj => obj.key)

console.log(res1) // [ { key: 'foo', value: 1212 }, { key: 'bar', value: 1234 } ]

with checks

strict means we compare stringified versions of the array objects, so no selectors needed.

const intersectionBetweenArraysStrict = (array1, array2) =>
  array1.filter(x => array2.map(JSON.stringify).includes(JSON.stringify(x)))

this will give you only full matches:

const arr1 = [
  { key: 'foo', value: 1212 },
  { key: 'bar', value: 1234 },
]
const arr2 = [
  { key: 'foo', value: 1111 },
  { key: 'bar', value: 5555 },
  { key: 'baz', value: 3434 },
]

const res2 = intersectionBetweenArraysStrict(arr1, arr2)

console.log(res2) // []

if you want them, with the assumption that the values are not going to be the same:

**then you're gonna have to work out what you want to do with the clashing values (do you make them an array? do you sum them up?)

here's a very long winded first go at doing something like this

const intersectionBetweenArraysWithResolvers = (
  array1,
  array2,
  selector,
  valueResolver
) => {
  if (!selector) return array1.filter(x => array2.includes(x))
  return array1.map(x => {
    const array2match = array2.filter(y => selector(y) === selector(x))

    if (array2match.length === 0) return false
    if (array2match.length !== 1)
      throw new Error('do something when there is more than 1 match')
    if (array2match.length === 1) {
      // compare values inside and allow user to pass a function to do something about it
      const array1Object = x
      const array2Object = array2match[0]

      if (JSON.stringify(array1Object) === JSON.stringify(array2Object))
        return true
      // if selector is the same, but other stuff is different...

      let combined = {}
      const allKeys = [
        ...new Set([
          ...Object.keys(array1Object),
          ...Object.keys(array2Object),
        ]),
      ]
      for (const key of allKeys) {
        if (!array2Object[key]) {
          // if it's unique to array1's object, use it
          combined[key] = array1Object[key]
        }
        if (!array1Object[key]) {
          combined[key] = array2Object[key]
        }

        // else compare the values in the keys
        const array1Value = array1Object[key]
        const array2Value = array2Object[key]

        const bothValuesMatch =
          JSON.stringify(array1Value) === JSON.stringify(array2Value)
        const isOneOfTheValuesIUndefined =
          !JSON.stringify(array1Value) || !JSON.stringify(array2Value)

        if (bothValuesMatch) {
          combined[key] = array1Object[key]
        } else {
          const combinedValues = [array1Value, array2Value].filter(Boolean)

          if (combinedValues.length === 1) {
            // one of the values is undefined
            combined[key] = combinedValues[0]
          }
          if (combinedValues.length === 2) {
            if (valueResolver) {
              // if the values are different, what do we wanna do?
              const userFunction = valueResolver
              combined[key] = userFunction(
                array1Object[key],
                array2Object[key],
                key
              )
            } else {
              // default resolver turns value into array of values
              let combinedArray = []
              array1Object[key] && combinedArray.push(array1Object[key])
              array2Object[key] && combinedArray.push(array2Object[key])
              combined[key] =
                combinedArray.length === 1 ? combinedArray[0] : combinedArray
            }
          }
        }
      }

      return combined
    }
  })
}

params:

const arr1 = [
  { key: 'foo', value: 1212 },
  { key: 'bar', value: 1234 },
]
const arr2 = [
  { key: 'foo', value: 1111 },
  { key: 'bar', value: 5555, hoot: 'woot woot!' },
  { key: 'baz', value: 3434 },
]

const res1 = intersectionBetweenArraysWithResolvers(arr1, arr2, obj => obj.key)
const res2 = intersectionBetweenArraysWithResolvers(
  arr1,
  arr2,
  obj => obj.key,
  (value1, value2) => value1 + value2
)

console.log(res1) // [ { key: 'foo', value: [ 1212, 1111 ] }, { key: 'bar', value: [ 1234, 5555 ], hoot: 'woot woot!' } ]
console.log(res2) // [ { key: 'foo', value: 2323 }, { key: 'bar', value: 6789, hoot: 'woot woot!' } ]

using Set for faster runtime

from SO answer

ES6 Set, for very large arrays The code above works on all browsers. However, for large arrays of more than about 10,000 items, it becomes quite slow, because it has O(n²) complexity. On many modern browsers, we can take advantage of the ES6 Set object to speed things up. Lodash automatically uses Set when it's available. If you are not using lodash, use the following implementation, inspired by Axel Rauschmayer's blog post:

function difference(a1, a2) {
  var a2Set = new Set(a2)
  return a1.filter(function(x) {
    return !a2Set.has(x)
  })
}

function symmetricDifference(a1, a2) {
  return difference(a1, a2).concat(difference(a2, a1))
}

Notes The behavior for all examples may be surprising or non-obvious if you care about -0, +0, NaN or sparse arrays. (For most uses, this doesn't matter.)

References

https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript