0

Given two arrays:

const inputOne = [
 {id: "valueA", prop: 123},
 {id: "valueB", prop: 456}
]

const inputTwo = [
 {id: "valueA", other: 54},
 {id: "valueB", other: 98},
 {id: "valueC", other: 11}
]

I'm trying to filter inputTwo based on inputOne's id and then merge the properties found in both.

Desired output:

combinedAndFiltered = [
 {id: "valueA", other: 54, prop: 123},
 {id: "valueB", other: 98, prop: 456}
]

I've tried various combinations of map, filter and/or reduce but somehow can't get my head around it.

0

8 Answers 8

1

This solution assumes that there will only be two sets, and the id property is unique within each set.

You can create an intersection of two sets of merged objects based on a common property (id) by:

  • Iterate the first set, then search the second set for the current element of the first set.
  • If a match is found, combine the two objects into a new object then add that object to an accumulator set.
  • Return the accumulator

This method returns a new set containing merged objects that are found in both sets. If an object is missing from either set, it will not be included in the output.

const inputOne = [ {id: "valueA", prop: 123}, {id: "valueB", prop: 456}, {id: "valueD", prop: 789} ]
const inputTwo = [ {id: "valueA", other: 54}, {id: "valueB", other: 98}, {id: "valueC", other: 11} ]

function intersectAndMerge(a, b) {
  const accumulator = []
  for(let { id, ...props } of a) {
    const match = b.find(e =>  e.id === id)
    if(match !== undefined) {
      accumulator.push({ ...match, ...props })
    }
  }
  return accumulator
}

console.log(intersectAndMerge(inputOne, inputTwo))

This can also be done with a reduce loop, but I find it less readable:

const inputOne = [ {id: "valueA", prop: 123}, {id: "valueB", prop: 456}, {id: "valueD", prop: 789} ]
const inputTwo = [ {id: "valueA", other: 54}, {id: "valueB", other: 98}, {id: "valueC", other: 11} ]

function intersectAndMerge(a, b) {
  return a.reduce((accumulator, { id, ...props }) => {
    const match = b.find(e =>  e.id === id)
    if(match !== undefined) {
      accumulator.push({ ...match, ...props })
    }
    return accumulator
  }, [])
}

console.log(intersectAndMerge(inputOne, inputTwo))

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

Comments

1
inputOne.map((a, i) => ({...a, ...inputTwo[i]}))

It assumes, that inputOne is the shorter one, you might use an if to make sure that that it is true in all cases.

EDIT: In a truly functional matter one would use something like zip in haskell, but without further libraries or implementing it yourself you will be stuck with something like this, I am afraid.

EDIT 2:

inputOne.map((a, i) => ({...a, ...(inputTwo.filter(x => x.id === a.id)[0])}))

This works based on the id property, didn't read well enough.

5 Comments

This solution assumes that both arrays will always have the exact same index.
No worries mate! 🙌
What if one of the objects in the first set does not exist in the second?
Things get messy, but there is an easy fix for that, too, of course... inputOne.map((a, i) => ({...a, ...(inputTwo.filter(x => x.id === a.id)[0] || {})}))
But as far as I can tell based on the question asked, only elements that exist in both sets should be output. That solution would still output the element if it does not exist in the second set, it just won't merge it.
1

Convert the 1st array to a Map using Array.reduce(), then reduce the 2nd array, and if the object is found in the Map, get the object from the Map, combine the objects, and add to accumulator:

const combine = (arr1, arr2) => {
  const arr1Map = arr1.reduce((m, o) => m.set(o.id, o), new Map)
  
  return arr2.reduce((r, o) => arr1Map.has(o.id) ? 
    [...r, { ...o, ...arr1Map.get(o.id) }] : r
  , [])
}

const inputOne = [{id: "valueA", prop: 123},{id: "valueB", prop: 456}]

const inputTwo = [{id: "valueA", other: 54},{id: "valueB", other: 98},{id: "valueC", other: 11}]

const result = combine(inputOne, inputTwo)

console.log(result)

3 Comments

Why create a new array every time instead of pushing onto the existing accumulator?
Functional programming immutability - the accumulator should be replaced by a new value not mutated. If you mutate the accumulator, you are using reduce as a for loop. However, as you mentioned, it's a bit wasteful, so depending on the case, you might want to reuse the accumulator (very large arrays for example).
I think that is a bit of a stretch. Do you have any references to support that statement? Why would mutating the accumulator be a bad thing?
1

This should do the trick (assuming that the inputOne acts like a source);

const inputOne = [
 {id: "valueA", prop: 123},
 {id: "valueB", prop: 456}
]

const inputTwo = [
 {id: "valueA", other: 54},
 {id: "valueB", other: 98},
 {id: "valueC", other: 11}
]

const mergeArrays = (first, second) => {
  return first.map((firstItem) => {
    const obj = second.find((secondItem) => secondItem.id === firstItem.id);
    return {...firstItem, ...obj};
  });
};

const combinedArrays = mergeArrays(inputOne, inputTwo);

console.log(combinedArrays);

1 Comment

What if one of the objects in the first set does not exist in the second?
0

Simple solution using for:

// declare an empty array:
let resArr = []

for(i=0; i<inputOne.length; i++) {
  for(j=0; j<inputTwo.length; j++) {
    if(inputOne[i].id === inputTwo[j].id) {
      resArr.push({ ...inputOne[i], ...inputTwo[j] })
    }
  }
}

1 Comment

Why continue iterating inputTwo once a match has been found?
0

Well, you can first build an Object and use it to get the other values while iterating over the other array with a .map

const inputOne = [
 {id: "valueA", prop: 123},
 {id: "valueB", prop: 456}
],
 inputTwo = [
 {id: "valueA", other: 54},
 {id: "valueB", other: 98},
 {id: "valueC", other: 11}
],
ids = {};

inputTwo.forEach(function (o) {
    ids[o.id] = o;
});

var res = inputOne.map(function (o) {
    return {
        other: ids[o.id].other,
        prop: o.prop,
        id: o.id
    };
});

console.log(res)

1 Comment

What if one of the objects in the first set does not exist in the second?
0

I think pretty much all solutions will be some variety of double iterating, whether it be by for loop, map, or filter. The other way different from the ones listed above is to use a library like lodash. This answer already gives a pretty good example of the unionBy function.

Comments

0

you can use map and filter to solve this. If I am not wrong about what you want this might be helpful

let result = inputOne.map((item) => {
    let leftItems = inputTwo.filter((item2) => {
        return item2.id == item.id
    }) 
    if(leftItems.length > 0) {
        return {id: item.id, other: leftItems[0].other, prop: item.prop}
        // or return {...item, ...leftItems[0]}
    }
}).filter(item => !! item)

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.