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.

skube
  • 5,867
  • 9
  • 53
  • 77

8 Answers8

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.

user3637541
  • 675
  • 4
  • 15
  • This solution assumes that both arrays will always have the exact same index. – Scott Sword Jan 02 '19 at 22:47
  • No worries mate! – Scott Sword Jan 02 '19 at 23:01
  • What if one of the objects in the first set does not exist in the second? –  Jan 02 '19 at 23:17
  • 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] || {})}))` – user3637541 Jan 02 '19 at 23:25
  • 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. –  Jan 02 '19 at 23:32
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)
Ori Drori
  • 183,571
  • 29
  • 224
  • 209
  • Why create a new array every time instead of pushing onto the existing accumulator? –  Jan 02 '19 at 23:15
  • 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). – Ori Drori Jan 02 '19 at 23:22
  • 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? –  Jan 02 '19 at 23:27
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);
Samuli Hakoniemi
  • 18,740
  • 1
  • 61
  • 74
  • What if one of the objects in the first set does not exist in the second? –  Jan 02 '19 at 23:13
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))
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] })
    }
  }
}
NULL SWEΔT
  • 1,827
  • 2
  • 11
  • 13
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)
HSLM
  • 1,692
  • 10
  • 25
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)
Muhammad Usman
  • 10,039
  • 22
  • 39
  • What if one of the objects in the first set does not exist in the second? –  Jan 02 '19 at 23:12
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.

Scott Sword
  • 4,648
  • 6
  • 32
  • 37