Eliya Cohen
Eliya Cohen

Reputation: 11478

Aggregate two arrays of objects

Let's say I've got these two arrays:

const initial: Array<GivenObj> = [
  {name: 'a', times: 40, other: 50},
  {name: 'b', times: 10, other: 15},
  {name: 'c', times: 15, other: 12}
];

const toBeMerged: Array<GivenObj> = [
  {name: 'a', times: 45, other: 30},
  {name: 'c', times: 10, other: 10},
  {name: 'd', times: 23, other: 10}
];

These two arrays contain different values, but similar keys. I need to aggregate this data to a single array that will contain both of their values, but uniquely.

In code, the two arrays above should be aggregated as follows:

const aggregated: Array<GivenObj> = [
  {name: 'a', times: 85, other: 80},
  {name: 'b', times: 10, other: 15},
  {name: 'c', times: 25, other: 22},
  {name: 'd', times: 23, other: 10}
];

I was wondering what's the best way to aggregate data between two arrays.

Upvotes: 3

Views: 874

Answers (7)

Nina Scholz
Nina Scholz

Reputation: 386654

You could reduce the given data and look for same name, then update, otherwise add a new object.

It takes

  • a new array with items from initial and toBeMerged,
  • reducing the items by

    • looking for an item with same name in the accumulator r,
    • checking if the item is not found, then return a new array with collected itms and a copy of the actual object
    • otherwise increment the needed properties with some values.

const
    initial = [{ name: 'a', times: 40, other: 50 }, { name: 'b', times: 10, other: 15 }, { name: 'c', times: 15, other: 12 }],
    toBeMerged = [{ name: 'a', times: 45, other: 30 }, { name: 'c', times: 10, other: 10 }, { name: 'd', times: 23, other: 10 }],
    merged = [...initial, ...toBeMerged].reduce((r, o) => {
        var temp = r.find(p => o.name === p.name);
        if (!temp) return [...r, { ...o }];
        temp.times += o.times;
        temp.other += o.other;
        return r;
    }, []);

console.log(merged);

Upvotes: 1

Shidersz
Shidersz

Reputation: 17190

Here you have one approach using reduce() and findIndex() over the new array to be merged. If a new object to be merged already exists (i.e the name property match for some object), we increment the rest of the matching properties and add the non-existing ones, otherwise we push the entire new object:

const initial = [
  {name: 'a', times: 40, other: 50},
  {name: 'b', times: 10, other: 15},
  {name: 'c', times: 15, other: 12}
];

const toBeMerged = [
  {name: 'a', times: 45, other: 30, another: 76},
  {name: 'c', times: 10, other: 10},
  {name: 'd', times: 23, other: 10}
];

let newArray = toBeMerged.reduce((res, curr) =>
{
    let found = res.findIndex(x => x.name === curr.name);

    if (found >= 0)
    {
        res[found] = Object.keys(curr).reduce((r, c) =>
        {
           r[[c]] = (r[[c]] && c !== 'name') ? r[[c]] + curr[[c]] : curr[[c]];
           return r;
        }, res[found]);
    }
    else
    {
        res.push(curr);
    }

    return res;

}, initial);

console.log(newArray);

Upvotes: 0

Nick
Nick

Reputation: 16576

Here is how I have approached this problem in the past.

  1. Create a final array, which will contain the final result.
  2. The arrays are merged using the Array concat method
  3. The concatenated array is sorted by the name property, meaning that all the a's will be in order, then the b's, etc.
  4. The forEach method is used to iterate through the concatenated, sorted array. If the current element has the same name property as the last element of the final array, then add the numeric properties of el to the last element of the final array. Otherwise, add el to the end of the final array.

const initial = [
  {name: 'a', times: 40, other: 50},
  {name: 'b', times: 10, other: 15},
  {name: 'c', times: 15, other: 12}
];

const toBeMerged = [
  {name: 'a', times: 45, other: 30},
  {name: 'c', times: 10, other: 10},
  {name: 'd', times: 23, other: 10}
];

let final = [];

initial.concat(toBeMerged).sort((a, b) => a.name > b.name).forEach(el => {
  if (final.length > 0 && el.name === final[final.length - 1].name) {
    final[final.length - 1].times += el.times;
    final[final.length - 1].other += el.other;
  } else {
    final.push(el);
  }
})

console.log(final);

Upvotes: 0

Code Maniac
Code Maniac

Reputation: 37755

You can do it with help of reduce.

  1. First merge both the array's using concat.
  2. Now on the concated array using reduce we check if the object property is already present in output than we add times and other in existing property if not than we add a new property.

const initial= [{name: 'a', times: 40, other: 50},{name: 'b', times: 10, other: 15},{name: 'c', times: 15, other: 12}];
const toBeMerged= [{name: 'a', times: 45, other: 30},{name: 'c', times: 10, other: 10},{name: 'd', times: 23, other: 10}];
let temp = initial.concat(toBeMerged)

let op = temp.reduce((output,current)=>{
  if( output[current.name] ){
    output[current.name].times += current.times
    output[current.name].other += current.other
  } else{
    output[current.name] = current;
  }
  return output;
},{})

console.log(Object.values(op))

Upvotes: 0

kockburn
kockburn

Reputation: 17616

Using spread operator, destructuring, Array#reduce, Object#values and Map

const initial=[{name:'a',times:40,other:50},{name:'b',times:10,other:15},{name:'c',times:15,other:12}];const toBeMerged=[{name:'a',times:45,other:30},{name:'c',times:10,other:10},{name:'d',times:23,other:10}]

const res = [...[...initial, ...toBeMerged]
.reduce((a,{name,times,other})=>{
    const b = a.get(name);
    return a.set(name,{name, times: (b?b.times:0) + times, other: (b?b.other:0) + other});
}, new Map()).values()];

console.log(res);

Upvotes: 0

jmcgriz
jmcgriz

Reputation: 3358

I'd approach this by merging the two arrays, then running a reduce against that combined array.

Inside the reduce, it first checks to see if an entry with that name exists, if not it pushes that entry to the result array. If it does find an existing entry, it creates any property that doesn't exist, and adds the values of any numerical properties that already exist. This should be flexible enough for whatever your actual use case is.

const initial = [
  {name: 'a', times: 40, other: 50},
  {name: 'b', times: 10, other: 15},
  {name: 'c', times: 15, other: 12}
];

const toBeMerged = [
  {name: 'a', times: 45, other: 30},
  {name: 'c', times: 10, other: 10},
  {name: 'd', times: 23, other: 10}
];

const result = [ ...initial, ...toBeMerged ].reduce((arr, t) => {
  let existing = arr.filter(x => x.name == t.name)[0]
  if(!existing) arr.push(t)
  else {
    const keys = Object.keys(t)
    keys.forEach(key => {
      if(!existing.hasOwnProperty(key)) existing[key] = t[key]
      else if(typeof existing[key] === "number") existing[key] += t[key]
    })
  }
  
  return arr
}, [])

console.log(result)

Upvotes: 3

Mohammad Ali Rony
Mohammad Ali Rony

Reputation: 4915

Try this code

const initial = [
  {name: 'a', times: 40, other: 50},
  {name: 'b', times: 10, other: 15},
  {name: 'c', times: 15, other: 12}
];

const toBeMerged = [
  {name: 'a', times: 45, other: 30},
  {name: 'c', times: 10, other: 10},
  {name: 'd', times: 23, other: 10}
];

//console.log(initial);

function arrayUnique(array) {
    var a = array.concat();
    for(var i=0; i<a.length; ++i) {
        for(var j=i+1; j<a.length; ++j) {
            if(a[i].name === a[j].name)
            {
                a[i].times +=a[j].times;
                a[i].other +=a[j].other;
                a.splice(j--, 1);
            }
        }
    }

    return a;
}


    // Merges both arrays and gets unique items
var array = arrayUnique(initial.concat(toBeMerged));
console.log(array);

Upvotes: 0

Related Questions