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:
list1 operation list2
should return the intersection of elements:
[ { userId: 1235, userName: 'ABC' }, { userId: 1236, userName: 'IJKL' } ]
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' } ]
list2 operation list1
should return the list of elements from list2
which don't occur in list1
:
[ { userId: 1252, userName: 'AAAA' } ]
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.
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:
for
loopsfilter
and some
array methodsSet
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));
filter
and some
array methodsThis 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));
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));
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With