1

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.

6
  • What will be returned if you have two in common? Commented Jul 25, 2019 at 6:35
  • 4
    Please add the code you've tried so far Commented Jul 25, 2019 at 6:36
  • @huanfeng if two or more in common, return all of them. Commented Jul 25, 2019 at 6:38
  • So, you want to make an intersection of object properties? Commented Jul 25, 2019 at 6:51
  • @VLAZ exactly, is there any way of using math union or intersection ? Commented Jul 25, 2019 at 7:03

3 Answers 3

4

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/[email protected]/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/[email protected]/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>

Sign up to request clarification or add additional context in comments.

Comments

1

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);

1 Comment

The only relevant use of Lodash here is _.isEqual and somewhat _.forOwn. However, the latter is trivially swappable with for..of and Object.entries or even just Object.entries(x).forEach() with pretty much no issue. And _.every can be replaced with Array#every with no loss in functionality.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.