Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference and intersection of two arrays containing objects

I have two arrays list1 and list2 which have objects with some properties; userId is the Id or unique property:

list1 = [     { userId: 1234, userName: 'XYZ'  },      { userId: 1235, userName: 'ABC'  },      { userId: 1236, userName: 'IJKL' },     { userId: 1237, userName: 'WXYZ' },      { userId: 1238, userName: 'LMNO' } ]  list2 = [     { userId: 1235, userName: 'ABC'  },       { userId: 1236, userName: 'IJKL' },     { userId: 1252, userName: 'AAAA' } ] 

I'm looking for an easy way to execute the following three operations:

  1. list1 operation list2 should return the intersection of elements:

    [     { userId: 1235, userName: 'ABC'  },     { userId: 1236, userName: 'IJKL' } ] 
  2. list1 operation list2 should return the list of all elements from list1 which don't occur in list2:

    [     { userId: 1234, userName: 'XYZ'  },     { userId: 1237, userName: 'WXYZ' },      { userId: 1238, userName: 'LMNO' } ] 
  3. list2 operation list1 should return the list of elements from list2 which don't occur in list1:

    [     { userId: 1252, userName: 'AAAA' } ] 
like image 672
Shashi Avatar asked Oct 26 '15 22:10

Shashi


People also ask

What is the intersection of two arrays?

The intersection of two arrays is a list of distinct numbers which are present in both the arrays. The numbers in the intersection can be in any order.


2 Answers

You could define three functions inBoth, inFirstOnly, and inSecondOnly which all take two lists as arguments, and return a list as can be understood from the function name. The main logic could be put in a common function operation that all three rely on.

Here are a few implementations for that operation to choose from, for which you can find a snippet further down:

  • Plain old JavaScript for loops
  • Arrow functions using filter and some array methods
  • Optimised lookup with a Set

Plain old for loops

// Generic helper function that can be used for the three operations:         function operation(list1, list2, isUnion) {     var result = [];          for (var i = 0; i < list1.length; i++) {         var item1 = list1[i],             found = false;         for (var j = 0; j < list2.length && !found; j++) {             found = item1.userId === list2[j].userId;         }         if (found === !!isUnion) { // isUnion is coerced to boolean             result.push(item1);         }     }     return result; }  // Following functions are to be used: function inBoth(list1, list2) {     return operation(list1, list2, true); }  function inFirstOnly(list1, list2) {     return operation(list1, list2); }  function inSecondOnly(list1, list2) {     return inFirstOnly(list2, list1); }  // Sample data var list1 = [     { userId: 1234, userName: 'XYZ'  },      { userId: 1235, userName: 'ABC'  },      { userId: 1236, userName: 'IJKL' },     { userId: 1237, userName: 'WXYZ' },      { userId: 1238, userName: 'LMNO' } ]; var list2 = [     { userId: 1235, userName: 'ABC'  },       { userId: 1236, userName: 'IJKL' },     { userId: 1252, userName: 'AAAA' } ];    console.log('inBoth:', inBoth(list1, list2));  console.log('inFirstOnly:', inFirstOnly(list1, list2));  console.log('inSecondOnly:', inSecondOnly(list1, list2)); 

Arrow functions using filter and some array methods

This uses some ES5 and ES6 features:

// Generic helper function that can be used for the three operations:         const operation = (list1, list2, isUnion = false) =>     list1.filter( a => isUnion === list2.some( b => a.userId === b.userId ) );  // Following functions are to be used: const inBoth = (list1, list2) => operation(list1, list2, true),       inFirstOnly = operation,       inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);  // Sample data const list1 = [     { userId: 1234, userName: 'XYZ'  },      { userId: 1235, userName: 'ABC'  },      { userId: 1236, userName: 'IJKL' },     { userId: 1237, userName: 'WXYZ' },      { userId: 1238, userName: 'LMNO' } ]; const list2 = [     { userId: 1235, userName: 'ABC'  },       { userId: 1236, userName: 'IJKL' },     { userId: 1252, userName: 'AAAA' } ];    console.log('inBoth:', inBoth(list1, list2));  console.log('inFirstOnly:', inFirstOnly(list1, list2));  console.log('inSecondOnly:', inSecondOnly(list1, list2));

Optimising lookup

The above solutions have a O(n²) time complexity because of the nested loop -- some represents a loop as well. So for large arrays you'd better create a (temporary) hash on user-id. This can be done on-the-fly by providing a Set (ES6) as argument to a function that will generate the filter callback function. That function can then perform the look-up in constant time with has:

// Generic helper function that can be used for the three operations:         const operation = (list1, list2, isUnion = false) =>     list1.filter(         (set => a => isUnion === set.has(a.userId))(new Set(list2.map(b => b.userId)))     );  // Following functions are to be used: const inBoth = (list1, list2) => operation(list1, list2, true),       inFirstOnly = operation,       inSecondOnly = (list1, list2) => inFirstOnly(list2, list1);  // Sample data const list1 = [     { userId: 1234, userName: 'XYZ'  },      { userId: 1235, userName: 'ABC'  },      { userId: 1236, userName: 'IJKL' },     { userId: 1237, userName: 'WXYZ' },      { userId: 1238, userName: 'LMNO' } ]; const list2 = [     { userId: 1235, userName: 'ABC'  },       { userId: 1236, userName: 'IJKL' },     { userId: 1252, userName: 'AAAA' } ];    console.log('inBoth:', inBoth(list1, list2));  console.log('inFirstOnly:', inFirstOnly(list1, list2));  console.log('inSecondOnly:', inSecondOnly(list1, list2));
like image 96
trincot Avatar answered Sep 20 '22 23:09

trincot


short answer:

list1.filter(a => list2.some(b => a.userId === b.userId));   list1.filter(a => !list2.some(b => a.userId === b.userId));   list2.filter(a => !list1.some(b => a.userId === b.userId));   

longer answer:
The code above will check objects by userId value,
if you need complex compare rules, you can define custom comparator:

comparator = function (a, b) {     return a.userId === b.userId && a.userName === b.userName };   list1.filter(a => list2.some(b => comparator(a, b))); list1.filter(a => !list2.some(b => comparator(a, b))); list2.filter(a => !list1.some(b => comparator(a, b))); 

Also there is a way to compare objects by references
WARNING! two objects with same values will be considered different:

o1 = {"userId":1}; o2 = {"userId":2}; o1_copy = {"userId":1}; o1_ref = o1; [o1].filter(a => [o2].includes(a)).length; // 0 [o1].filter(a => [o1_copy].includes(a)).length; // 0 [o1].filter(a => [o1_ref].includes(a)).length; // 1 
like image 23
Trizalio Avatar answered Sep 21 '22 23:09

Trizalio