1

I want to find the common elements of multiple array of objects based on a common property. In addition, if an element appears more than once, I want the resulting array to reflect the number of times it occurs in all the arrays.

I tried the following:

var arr = [
    [
        { name: 'kiwi', value: 12 },
        { name: 'apple', value: 5 },
        { name: 'apple', value: 12 },
        { name: 'pizza', value: 33 },
        { name: 'pizza', value: 24 },
        { name: 'fish', value: 5 },
        { name: 'milk', value: 5 },
        { name: 'banana', value: 7 },
        { name: 'orange', value: 11 },
    ],
    [
        { name: 'taco', value: 23 },
        { name: 'pizza', value: 78 },
        { name: 'apple', value: 12 },
        { name: 'pizza', value: 33 },
        { name: 'pizza', value: 24 },
        { name: 'fish', value: 5 },
        { name: 'pie', value: 1 },
        { name: 'cake', value: 3 },
        { name: 'banana', value: 7 },
        { name: 'beef', value: 123 },
        { name: 'lime', value: 72 },
        { name: 'pizza', value: 34 },
    ],
    [
        { name: 'apple', value: 12 },
        { name: 'pizza', value: 33 },
        { name: 'pizza', value: 24 },
        { name: 'pizza', value: 23 },
        { name: 'fish', value: 5 },
        { name: 'banana', value: 7 },
        { name: 'banana', value: 77 },
    ]
];

function findArraysWithCommonName(arr) {
  let arrays = [...arr];
  var result = arrays.shift().reduce(function(res, v) {
    if (arrays.every(function(a) {
        return (a.filter(function(e) {
          return e.name === v.name
        }).length > 0);
      })) res.push(v);
    return res;
  }, []);
  return result;
}

console.log(findArraysWithCommonName(arr))

The result I got is:

[
{name: "apple", value: 5},
{name: "apple", value: 12},
{name: "pizza", value: 33},
{name: "pizza", value: 24},
{name: "fish", value: 5},
{name: "banana", value: 7}
]

I expect the output to be:

[
{name: "apple", value: 12},
{name: "pizza", value: 33},
{name: "pizza", value: 24},
{name: "fish", value: 5},
{name: "banana", value: 7}
]

or

[
{name: "apple", value: 5},
{name: "pizza", value: 33},
{name: "pizza", value: 24},
{name: "fish", value: 5},
{name: "banana", value: 7}
]
5
  • Why do you want {name: "apple", value: 5} as a possible output? {name: "apple", value: 5} only occurs once. (or, why isn't {name: "apple", value: 12} present in the second possible output array? {name: "apple", value: 12} occurs in every array) Commented Aug 25, 2019 at 22:53
  • The 'value' is not important in this case. Since 'apple' is present once in the second and the third array, it is ok if we can pick any of the apple object from the first array. Commented Aug 25, 2019 at 22:57
  • I don't understand why you only want one of "apple" but both of "pizza" if value is not important Commented Aug 25, 2019 at 23:13
  • @vol7ron I'm pretty sure it's because the maximum number of occurrences of an apple object in each array is, at most, 1, whereas for pizza, that number is 2 Commented Aug 25, 2019 at 23:14
  • So intersection based on min count? Commented Aug 25, 2019 at 23:15

2 Answers 2

1

One approach would be to build a map that relates an object to it's "count" in the array (ie the number of times that object occours in arr).

This can be done via .reduce() where you serialize each object to a string via JSON.stringify(obj) - this string is a unique encoding of the corresponding object shape and state which is used as the key to identify the objects of this form in the mapping. The key is used to query and update the "count" value of the mapping, for each object encountered in the arr.

Once the mapping has been build, filter mapping entries by those with a "count" value greater than one.

Finally for any filtered entries, deserialize the corresponding keys of those entries via .map() to obtain an array of objects that occoured more that one in the original arr.

This approach could be implemented as:

var arr=[[{name:'kiwi',value:12},{name:'apple',value:5},{name:'apple',value:12},{name:'pizza',value:33},{name:'pizza',value:24},{name:'fish',value:5},{name:'milk',value:5},{name:'banana',value:7},{name:'orange',value:11}],[{name:'taco',value:23},{name:'pizza',value:78},{name:'apple',value:12},{name:'pizza',value:33},{name:'pizza',value:24},{name:'fish',value:5},{name:'pie',value:1},{name:'cake',value:3},{name:'banana',value:7},{name:'beef',value:123},{name:'lime',value:72},{name:'pizza',value:34}],[{name:'apple',value:12},{name:'pizza',value:33},{name:'pizza',value:24},{name:'pizza',value:23},{name:'fish',value:5},{name:'banana',value:7},{name:'banana',value:77}]];


/* Flatten array heirachy */
const flatArr = arr.flat();

/* Obtain a count mapping for each object's occourance in flatArr */
const mapObjectToCount = flatArr.reduce((map, item) => {

  const key = JSON.stringify(item);
  const count = (map[key] ? map[key] : 0) + 1;
  
  return { ...map, [ key ] : count };
}, {})

/* Get key/value pair of the prior mapping, filter the objects by
those that occour more that one time, and obtain the original object
by parsing the key */
const result = Object.entries(mapObjectToCount)
.filter(([json, count]) => count > 1)
.map(([json]) => JSON.parse(json));

console.log(result)

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

1 Comment

This is what I did originally, but OP said that the values aren't important. Since 'apple' is present once in the second and the third array, it is ok if we can pick any of the apple object from the first array. So I think stringification isn't reliable, eg if a particular name is present in all 3 arrays, but the objects don't have a common value
1

I'd first transform each subarray into an object indexed by the number of occurences of each name. Then, iterate through each of those sub-objects created, creating a new object whose values are the minimum of the values found on the combined object, for every key.

Lastly, return a .filter of the first array, checking whether the occurence count of the name being iterated over on that object is greater than 0, reducing that count by one when found:

function findArraysWithCommonName(arr) {
  const [oneArr, ...rest] = arr;
  
  /* Transform each subarray into, eg:
  {
    "taco": 1,
    "pizza": 4,
    "apple": 1,
    "fish": 1,
    "pie": 1,
    ...
  */
  const countsByName = rest.map(
    subarr => subarr.reduce((a, { name }) => {
      a[name] = (a[name] || 0) + 1;
      return a;
    }, {})
  );
  
  /* Combine the objects into one that contains only the minimum value for each property, eg:
  {
    "apple": 1,
    "pizza": 3,
    "fish": 1,
    "banana": 1
  }
  */
  const combinedCountsByName = countsByName.reduce((a, countObj) => {
    Object.entries(countObj).forEach(([key, val]) => {
      countObj[key] = Math.min(a[key], val) || 0;
    });
    return countObj;
  });
  console.log(combinedCountsByName);
  
  return oneArr.filter(({ name }) => {
    if (combinedCountsByName[name] > 0) {
      combinedCountsByName[name]--;
      return true;
    }
  });
}

var arr = [
    [
        { name: 'kiwi', value: 12 },
        { name: 'apple', value: 5 },
        { name: 'apple', value: 12 },
        { name: 'pizza', value: 33 },
        { name: 'pizza', value: 24 },
        { name: 'fish', value: 5 },
        { name: 'milk', value: 5 },
        { name: 'banana', value: 7 },
        { name: 'orange', value: 11 },
    ],
    [
        { name: 'taco', value: 23 },
        { name: 'pizza', value: 78 },
        { name: 'apple', value: 12 },
        { name: 'pizza', value: 33 },
        { name: 'pizza', value: 24 },
        { name: 'fish', value: 5 },
        { name: 'pie', value: 1 },
        { name: 'cake', value: 3 },
        { name: 'banana', value: 7 },
        { name: 'beef', value: 123 },
        { name: 'lime', value: 72 },
        { name: 'pizza', value: 34 },
    ],
    [
        { name: 'apple', value: 12 },
        { name: 'pizza', value: 33 },
        { name: 'pizza', value: 24 },
        { name: 'pizza', value: 23 },
        { name: 'fish', value: 5 },
        { name: 'banana', value: 7 },
        { name: 'banana', value: 77 },
    ]
];

console.log(findArraysWithCommonName(arr));

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.