'Check if two arrays have an object with the same property
Here's my goal: compare two objects and figure out if there is 1 or more items in common. If there are 1 or more in common, return true otherwise (no items in common), return false.
Current issues: I'm trying to use the .some() method with 1 object from an API and 1 object that's local, but a bit confused why it's not working...any ideas? It should return true since John is in both objects, but it returns false 🙃
Code example: in this example, it should return true because John is a name that is both object 1 (result1) and object 2 (result 2). However, it returns false.
Is anyone able to help me understand what I'm doing wrong here?
var result1 = [
{id:1, name:'Sandra', type:'user', username:'sandra'},
{id:2, name:'John', type:'admin', username:'johnny2'},
{id:3, name:'Peter', type:'user', username:'pete'},
{id:4, name:'Bobby', type:'user', username:'be_bob'}
];
var result2 = [
{id:2, name:'John', email:'[email protected]'},
{id:4, name:'Bobby', email:'[email protected]'}
];
const hasSimilarElement = result1.some((item) => item.name === result2.name);
console.log(hasSimilarElement);
Solution 1:[1]
You can create a function that takes a function f to use for the lookup then two arrays xs & ys:
const checkBy = f => (xs, ys) => {
let [long, short] = xs.length > ys.length ? [xs, ys] : [ys, xs];
short = new Set(short.map(f));
return long.some(x => short.has(f(x)));
};
Then:
const checkByName = checkBy(x => x.name);
checkByName( [ {id: 1, name:'Sandra', type: 'user', username: 'sandra'}
, {id: 2, name: 'John', type: 'admin', username: 'johnny2'}
, {id: 3, name: 'Peter', type: 'user', username: 'pete'}
, {id: 4, name: 'Bobby', type: 'user', username: 'be_bob'}]
, [ {id: 2, name: 'John', email: '[email protected]'}
, {id: 4, name: 'Bobby', email: '[email protected]'}]);
//=> true
checkByName( [ {id: 1, name:'Sandra', type: 'user', username: 'sandra'}
, {id: 2, name: 'John', type: 'admin', username: 'johnny2'}
, {id: 3, name: 'Peter', type: 'user', username: 'pete'}
, {id: 4, name: 'Bobby', type: 'user', username: 'be_bob'}]
, [ {id: 2, name: 'xxxx', email: '[email protected]'}
, {id: 4, name: 'yyyyy', email: '[email protected]'}]);
//=> false
Solution 2:[2]
I would use the technique proposed by customcommander for your exact scenario, and for anywhere you can extract a unique key from your objects as a primitive type. (Here names are strings, so that works.)
If you need something more generic, then you can supply a function that tells if two elements are equal and use it like this:
const overlap = (equal) => (xs, ys) =>
xs .some (x => ys .some (y => equal (x, y)))
const result1 = [{id:1, name:'Sandra', type:'user', username:'sandra'}, {id:2, name:'John', type:'admin', username:'johnny2'}, {id:3, name:'Peter', type:'user', username:'pete'}, {id:4, name:'Bobby', type:'user', username:'be_bob'}]
const result2 = [{id:2, name:'John', email:'[email protected]'}, {id:4, name:'Bobby', email:'[email protected]'}]
console .log (overlap ((x, y) => x .name == y .name) (result1, result2))
This is less efficient in those cases where you could generate a unique key, but it's more generic. You can even use it for two arrays where the two arrays have objects with different structures, passing for instance (x, y) => x .id == y .primaryKey.
Solution 3:[3]
Array.prototype.some() The some() method tests whether at least one element in the array passes the test implemented by the provided function.
In your example you are trying to compare 2 arrays of objects. You have false as result2.name is undefined, you might need to specify the index of an particular element e.g. result2[0].name
If you'd like to compare 2 arrays with some you need to iterate through result2 items as well. You can use somehting like this:
var result1 = [
{id:1, name:'Sandra', type:'user', username:'sandra'},
{id:2, name:'John', type:'admin', username:'johnny2'},
{id:3, name:'Peter', type:'user', username:'pete'},
{id:4, name:'Bobby', type:'user', username:'be_bob'}
];
var result2 = [
{id:2, name:'John', email:'[email protected]'},
{id:4, name:'Bobby', email:'[email protected]'}
];
const hasSimilarElement = result2.filter((item1) => !result1.some(item2 => item1.name === item2.name ));
console.log(hasSimilarElement);
Solution 4:[4]
The reason your code fails is that you are trying to find a match for a name in the elements of result1 Array to result2.name
However, result2 is also an Array, Array's do not have a .name that happens to be all of the elements .names that somehow matches the one you are searching for - that's not how it works
You need to iterate both Arrays to look for a match
var result1 = [
{id:1, name:'Sandra', type:'user', username:'sandra'},
{id:2, name:'John', type:'admin', username:'johnny2'},
{id:3, name:'Peter', type:'user', username:'pete'},
{id:4, name:'Bobby', type:'user', username:'be_bob'}
];
var result2 = [
{id:2, name:'John', email:'[email protected]'},
{id:4, name:'Bobby', email:'[email protected]'}
];
const hasSimilarElement = result1.some((item) => result2.some(item2 => item.name === item2.name));
console.log(hasSimilarElement);
.as-console-wrapper { min-height: 100%!important; top: 0; }
However, for very large result sets this is inefficient - I'm no "big O notation expert, but I believe this would be O(n2) - you're potentially going to compare result1.length * result2.length times
For this simple case, you can extract just the .name from both results, and create a Set ... if the Set size is less than the combined result .lengths it means there's a duplicate
here, you'll iterate each result once each - if both sets have 1000 elements that's 2,000 iterations, compared to 1,000,000 using the naive method above
var result1 = [
{id:1, name:'Sandra', type:'user', username:'sandra'},
{id:2, name:'John', type:'admin', username:'johnny2'},
{id:3, name:'Peter', type:'user', username:'pete'},
{id:4, name:'Bobby', type:'user', username:'be_bob'}
];
var result2 = [
{id:2, name:'John', email:'[email protected]'},
{id:4, name:'Bobby', email:'[email protected]'}
];
const hasSimilarElement = new Set([...result1, ...result2].map(({name}) => name)).size < result1.length + result2.length
console.log(hasSimilarElement);
.as-console-wrapper { min-height: 100%!important; top: 0; }
However, there is a flawif there's a duplicate name within one of the results, this will not work as expected
Here each set is iterated twice - but, given 2 x 1,000 long results, 4,000 iterations is still better than 1,000,000
var result1 = [
{id:1, name:'Sandra', type:'user', username:'sandra'},
{id:2, name:'John', type:'admin', username:'johnny2'},
{id:3, name:'Peter', type:'user', username:'pete'},
{id:4, name:'Bobby', type:'user', username:'be_bob'}
];
var result2 = [
{id:2, name:'John', email:'[email protected]'},
{id:4, name:'Bobby', email:'[email protected]'}
];
const set1 = new Set(result1.map(({name}) => name));
const set2 = new Set(result2.map(({name}) => name));
const both = new Set([...set1, ...set2]);
const hasSimilarElement = both.size < set1.size + set2.size;
console.log(hasSimilarElement);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Solution 5:[5]
From the above comment ...
"The OP needs to
findan item with an equalitem.namevalue ofresult1withinresult2instead of directly comparing a non existing (thusundefined)nameproperty ofresult2."
Or as correctly suggested by 3limin4t0r one could swap find for another some again.
const result1 = [
{ id: 1, name: 'Sandra', type: 'user', username: 'sandra' },
{ id: 2, name: 'John', type: 'admin', username: 'johnny2' },
{ id: 3, name: 'Peter', type: 'user', username: 'pete' },
{ id: 4, name: 'Bobby', type: 'user', username: 'be_bob' },
];
const result2 = [
{ id: 5, name: 'Lea', email: '[email protected]' },
{ id: 2, name: 'John', email: '[email protected]' },
];
const result3 = [
{ id: 5, name: 'Lea', email: '[email protected]' },
];
const result4 = [];
console.log(
'similar elements within `result1` and `result2` ..?',
result1.some(itemA =>
result2.some(itemB => itemB.name === itemA.name)
)
);
console.log(
'similar elements within `result1` and `result3` ..?',
result1.some(itemA =>
result3.some(itemB => itemB.name === itemA.name)
)
);
console.log(
'similar elements within `result1` and `result4` ..?',
result1.some(itemA =>
result4.some(itemB => itemB.name === itemA.name)
)
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Within a next iteration one of cause could come up with a generically implemented function which allows the to be compared property name to be freely chosen.
Implementing a function also gives the opportunity to get rid of the two nested some iterations (though some of cause exits early for a successful comparison).
Here one would build from the shorter array a key (property name) specific value-index (a single iteration task), where, while iterating the longer array, one would look up (no iteration) the key specific value for item similarity.
function hasSimilarItemsByKey(key, arrA, arrB) {
const [
// the shorter array will be the base
// for a `key` specific value index.
lookupArray,
// the longer array will be iterated
// and have its item `key` specific
// values looked up at a value index.
targetArray,
] = [
arrA,
arrB,
].sort((a, b) => a.length - b.length);
// create the object based value index
// (from the shorter array).
const valueIndex = lookupArray
.reduce((lookup, item) => {
const value = item[key];
lookup[value] ??= value;
return lookup;
}, Object.create(null)); // A `prototype`-less object. Thus,
// using the `in` operator is safe.
return targetArray.some(item => item[key] in valueIndex);
}
const result1 = [
{ id: 1, name: 'Sandra', type: 'user', username: 'sandra' },
{ id: 2, name: 'John', type: 'admin', username: 'johnny2' },
{ id: 3, name: 'Peter', type: 'user', username: 'pete' },
{ id: 4, name: 'Bobby', type: 'user', username: 'be_bob' },
];
const result2 = [
{ id: 5, name: 'Lea', email: '[email protected]' },
{ id: 2, name: 'John', email: '[email protected]' },
];
const result3 = [
// Attention,
// A matching `id` and a non matching `name`.
{ id: 1, name: 'Lea', email: '[email protected]' },
];
const result4 = [];
console.log(
'similar elements within `result1` and `result2`\n',
'...by `name`..?', hasSimilarItemsByKey('name', result1, result2),
'...by `id`..?', hasSimilarItemsByKey('id', result1, result2),
);
console.log(
'similar elements within `result1` and `result3`\n',
'...by `name`..?', hasSimilarItemsByKey('name', result1, result3),
'...by `id`..?', hasSimilarItemsByKey('id', result1, result3),
);
console.log(
'similar elements within `result1` and `result4`\n',
'...by `name`..?', hasSimilarItemsByKey('name', result1, result4),
'...by `id`..?', hasSimilarItemsByKey('id', result1, result4),
);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Scott Sauyet |
| Solution 3 | maximus |
| Solution 4 | |
| Solution 5 |
