简体   繁体   中英

Javascript / Lodash deep comparison object from an array of object

Suppose i have an array of objects like this

let arr = [
  {
    abcdef: {name: 'Robin', uid: '123'},
    ghijkl: {name: 'Simon', uid: '456'}
  },
  {
    mnopqr: {name: 'Alex', uid: '789'},
    abcdef: {name: 'Robin', uid: '123'}
  },
  {
    abcdef: {name: 'Robin', uid: '123'},
    stuvwx: {name: 'Julianna', uid: '111'},
    yzxwuv: {name: 'Elon', uid: '007'}
  }
];

In position of arr[0] , arr[1] and arr[2] , i define a object and inside that object, i define couple of objects.

Here this abcdef: {name: 'Robin', uid: '123'} is common among the three(arr[0], arr[1], arr[2]). So i need to write a function that returns the common one. In this case abcdef: {name: 'Robin', uid: '123'}

UPDATE: If there is nothing in common, return false. And two or more in common, return all of them.

You can easily do that by using intersectionWith - it accepts a custom comparator for your elements. The easiest way is to convert the object into an array of entries using toPairs and then compare those with isEqual . The intersection would then be an array containing pairs of attribute and value, so you can then convert it back to an object using fromPairs

 let arr = [ { abcdef: {name: 'Robin', uid: '123'}, ghijkl: {name: 'Simon', uid: '456'} }, { mnopqr: {name: 'Alex', uid: '789'}, abcdef: {name: 'Robin', uid: '123'} }, { abcdef: {name: 'Robin', uid: '123'}, stuvwx: {name: 'Julianna', uid: '111'}, yzxwuv: {name: 'Elon', uid: '007'} } ]; const inputPairs = arr.map(_.toPairs); const resultPairs = _.intersectionWith(...inputPairs, _.isEqual); const resultObject = _.fromPairs(resultPairs); console.log(resultObject); 
 <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script> 

Using chaining this can be written as:

 let arr = [ { abcdef: {name: 'Robin', uid: '123'}, ghijkl: {name: 'Simon', uid: '456'} }, { mnopqr: {name: 'Alex', uid: '789'}, abcdef: {name: 'Robin', uid: '123'} }, { abcdef: {name: 'Robin', uid: '123'}, stuvwx: {name: 'Julianna', uid: '111'}, yzxwuv: {name: 'Elon', uid: '007'} } ]; const resultObject = _(arr) .map(_.toPairs) .thru(pairs => _.intersectionWith(...pairs, _.isEqual)) .fromPairs() .value(); console.log(resultObject); 
 <script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script> 

Using thru here because the chain contains an array with other array in it and each needs to be passed as a separate argument to intersectionWith . It's the easiest way to do that.

Alternatively, if you prefer a more FP approach then it can look like this:

 const {spread, intersectionWith, isEqual, flow, map, toPairs, fromPairs} = _; let arr = [ { abcdef: {name: 'Robin', uid: '123'}, ghijkl: {name: 'Simon', uid: '456'} }, { mnopqr: {name: 'Alex', uid: '789'}, abcdef: {name: 'Robin', uid: '123'} }, { abcdef: {name: 'Robin', uid: '123'}, stuvwx: {name: 'Julianna', uid: '111'}, yzxwuv: {name: 'Elon', uid: '007'} } ]; const process = flow( map(toPairs), spread(intersectionWith(isEqual)), fromPairs ); const resultObject = process(arr); console.log(resultObject); 
 <script src="https://cdn.jsdelivr.net/g/lodash@4(lodash.min.js+lodash.fp.min.js)"></script> 

Solution

Here's a quick solution I was able to whip up, and I am doing this without lodash intentionally just to illustrate how to do it natively

  const arr = [ { abcdef: { name: 'Robin', uid: '123' }, ghijkl: { name: 'Simon', uid: '456' }, }, { mnopqr: { name: 'Alex', uid: '789' }, abcdef: { name: 'Robin', uid: '123' }, }, { abcdef: { name: 'Robin', uid: '123' }, stuvwx: { name: 'Julianna', uid: '111' }, yzxwuv: { name: 'Elon', uid: '007' }, }, ]; function isEqual(a, b) { const aProps = Object.getOwnPropertyNames(a); const bProps = Object.getOwnPropertyNames(b); if (aProps.length !== bProps.length) { return false; } for (const prop of aProps) { if (a[prop] !== b[prop]) { return false; } } return true; } function findIntersection() { const results = []; const objCount = {}; arr.forEach(element => { Object.entries(element).forEach(entry => { const [key, value] = entry; if (!objCount[key]) { objCount[key] = {}; objCount[key].value = value; objCount[key].count = 1; } else if (isEqual(value, objCount[key].value)) { objCount[key].count += 1; } }); }); Object.keys(objCount).forEach(key => { if (objCount[key].count === arr.length) { results.push({ [key]: objCount[key].value }); } }); if (results.length > 0) { return results; } return false; } const results = findIntersection(arr); console.log('Results', results); 

Explanation

The logic is as follows,

  • Iterate over each array element and in turn over all the objects and keep a count of how many times they occur. Only increase the count if the key and value are both identical
  • After getting the count for all the objects, iterate over each object's count value and check if it's equal to the number of elements in the array thus showing the object is common for all elements in the array
  • For each object that satisfies the above condition push it to results array and return it

Hope this helps. Let me know if it works for you.

Just for your reference:

let arr = [
  {
    abcdef: {name: 'Robin', uid: '123'},
    ghijkl: {name: 'Simon', uid: '456'}
  },
  {
    mnopqr: {name: 'Alex', uid: '789'},
    abcdef: {name: 'Robin', uid: '123'}
  },
  {
    abcdef: {name: 'Robin', uid: '123'},
    stuvwx: {name: 'Julianna', uid: '111'},
    yzxwuv: {name: 'Elon', uid: '007'}
  }
];
let counterMap = {}, 
names=[], //names collect the keys only which exists in all item, here the value should be ['abcdef']
commons=[];
for (let itm of arr) {
  for (let key of Object.keys(itm)) {
    if (!counterMap[key]) {
      counterMap[key] = 1;
    } else {
      counterMap[key] = counterMap[key] + 1;
    }
  }
}
_.forOwn(counterMap, (val, key) => {
  if (val === arr.length) {
    names.push(key);
  }
})
names.forEach(name => {
  if (_.every(arr, itm => {
    return _.isEqual(itm[name], arr[0][name]);
  })){
    let result = {};
    result[name] = arr[0][name];
    commons.push(result)
  }
})
console.log(commons);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM