Mr. Benedict Mr. Benedict - 2 months ago 5
Javascript Question

Compare IDs in a two-dimensional array with IDs in another two-dimensional array

Ok, another in the series of grouping Javascript arrays of objects by ID but this time, we have an array of IDs in an object of arrays (item3) which will be compared with another array of objects.

var existingArray = [
{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0200","0300"],
"item4": "blah4",
"item5": "blah5"
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0100","0300"],
"item4": "blah4",
"item5": "blah5"
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0100"],
"item4": "blah4",
"item5": "blah5"
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0300"],
"item4": "blah4",
"item5": "blah5"
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0200", "0100"],
"item4": "blah4",
"item5": "blah5"
}
]


Here's our favourite DATA2 array which contains the piece of information we want to pull out (CandidateName) if the "relatedId" is the same as the any of the IDs in item3 in EXISTINGARRAY.

var data2 = [
{"CandidateName": "Mary", "relatedId": ["0100", "0200"]},
{ "CandidateName": "John", "relatedId": ["0200"]},
{ "CandidateName":"Peter", "relatedId": ["0300", "0100"]},
{ "CandidateName": "Paul", "relatedId": ["0300"]}
];


So the idea is if any of the IDs in data2[i].relatedId[j] === existingArray[k].item3[l], pull out the "CandidateName" and add it to the EXISTINGARRAY so we end up with something like the following.

existingArray = [
{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0200","0300"],
"item4": "blah4",
"item5": "blah5",
"item6": ["Mary", "Jonh", "Peter", "Paul"]
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0100","0300"],
"item4": "blah4",
"item5": "blah5",
"item6": ["Mary", "Peter", "Paul"]
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0100"],
"item4": "blah4",
"item5": "blah5",
"item6": ["Mary", "Peter"]
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0300"],
"item4": "blah4",
"item5": "blah5",
"item6": ["Peter", "Paul"]
},{
"item1": "Blah1",
"item2": "blah2",
"item3": ["0200", "0100"],
"item4": "blah4",
"item5": "blah5",
"item6": ["Mary", "John","Peter"]
}
]

Answer

Here is an ES6 solution for this:

existingArray.forEach( function (obj) {
    obj.item6 = [...new Set(obj.item3.reduce( (acc, id) => acc.concat(this.get(id)), [] ))]
}, data2.reduce (
    (acc, obj) => obj.relatedId.reduce (
        (acc, id) => acc.set(id, (acc.get(id) || []).concat(obj.CandidateName)), acc
    ), new Map()
));

var existingArray = [
    {
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200","0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100","0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0100"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0300"],
      "item4": "blah4",
      "item5": "blah5"
    },{
      "item1": "Blah1",
      "item2": "blah2",
      "item3": ["0200", "0100"],
      "item4": "blah4",
      "item5": "blah5"
    }
]

var data2 = [
    {"CandidateName": "Mary", "relatedId": ["0100", "0200"]},
    { "CandidateName": "John", "relatedId": ["0200"]},
    { "CandidateName":"Peter", "relatedId": ["0300", "0100"]},
    { "CandidateName": "Paul", "relatedId": ["0300"]}
];

existingArray.forEach( function (obj) {
    obj.item6 = [...new Set(obj.item3.reduce( (acc, id) => acc.concat(this.get(id)), [] ))]
}, data2.reduce (
    (acc, obj) => obj.relatedId.reduce (
        (acc, id) => acc.set(id, (acc.get(id) || []).concat(obj.CandidateName)), acc
    ), new Map()
));

console.log(existingArray);

Explanation

The code really starts at the end, with the creation of an empty Map:

new Map()

This becomes the variable (named acc) that accumulates data while data2 is iterated with reduce:

data2.reduce

This reduce operation is nested in order to iterate each relatedId individually. If the accumulated value (acc) does not yet contain the found id, a new array is created:

acc.get(id) || []

... otherwise the found array value is used. To that the candidate name is appended:

.concat(obj.CandidateName)

... and this is put back into acc for key id:

acc.set(id, ...)

As the set method returns acc itself, it works very nicely with reduce which needs this return value in order to pass acc again to the next call of the callback.

The result of the outer reduce call is this a Map that is keyed by all id values found in data2, and the value for each is an array of associated names.

This value is then passed as second argument to the forEach call, and thus becomes the value of this. So when you see:

this.get(id)

it is retrieving the candidate name array for id from the Map described above.

In the forEach callback another reduce is made to iterate over the id values in item3:

obj.item3.reduce

This accumulates to an array of names which is then passed to the Set constructor:

new Set(...)

This is done to remove duplicates from the array of names. This Set is immediately converted to an array again with the spread operator:

[...new Set()]

And so all item6 properties get their value.