'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 find an item with an equal item.name value of result1 within result2 instead of directly comparing a non existing (thus undefined) name property of result2."

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