Merge objects with the same id but sum values of the objects (ok)

https://stackoverflow.com/questions/44332180/merge-objects-with-the-same-id-but-sum-values-of-the-objects

Ask QuestionAsked 5 years, 7 months agoModified 5 years, 7 months agoViewed 3k times1

I want to reduce my array of objects by comparing previous and current object from the array, if the id of previous object is different then current object, then I write the previous object to my result list and override it with the current object else I sum the values of both objects. In the end it should be a reduced array, no duplicates.

I have data like this:

[{
    Clicks: 210,
    Company: "A",
    _id: { CompanyID: 5 }
},
{
    Clicks: 35,
    Company: "C",
    _id: { CompanyID: 3 }
},
{
    Clicks: 15,
    Company: "B",
    _id: { CompanyID: 2 }
},
{
    Clicks: 13,
    Company: "A",
    _id: { CompanyID: 5 }
}]

And want to reduce it to this form:

[{
    Clicks: 223,
    Company: "A",
    _id: { CompanyID: 5 }
},
{
    Clicks: 35,
    Company: "C",
    _id: { CompanyID: 3 }
},
{
    Clicks: 15,
    Company: "B",
    _id: { CompanyID: 2 }
}]

Here is my not correctly working solution so far:

$scope.reduce = function () {
    var result = [];
    var prev = null;

    angular.forEach($scope.data, function (value, key) {
        if (prev != null) {
            if (prev._id.CompanyID != value._id.CompanyID) {
                result.push(prev);
                prev = value;
            } else {
                prev.Clicks += value.Clicks;
            }
        } else {
            prev = value;
        }
    });
}

My result looks good, it reduce all duplicates but it does not sum the values of objects with the same ids, it just overrides the ids with the last object.

2 Answers

Sorted by: Highest score (default) Trending (recent votes count more) Date modified (newest first) Date created (oldest first) 5

You can use thisArg parameter in forEach loop and pass a empty object to store values.

var data = [{"Clicks":210,"Company":"A","_id":{"CompanyID":5}},{"Clicks":35,"Company":"C","_id":{"CompanyID":3}},{"Clicks":15,"Company":"B","_id":{"CompanyID":2}},{"Clicks":13,"Company":"A","_id":{"CompanyID":5}}];
var result = [];

data.forEach(function(obj) {
  var id = obj._id.CompanyID
  if(!this[id]) result.push(this[id] = obj);
  else this[id].Clicks += obj.Clicks;
}, Object.create(null));

console.log(result);
  • Thanks for you help. I actually wanted to know what my mistake was. I already found out what i did wrong, i have logic error, because i reference to my first object instead of doing a deep copy. And your code is somewhat irritating by using the keyword this. I think it is not good to use this in javascript, specially here. :) – Rep Jun 2, 2017 at 21:13

  • I think it is not good to use this in javascript why is that? – Nenad Vracar Jun 2, 2017 at 21:40

  • In your example "this" is irritating, but it works fine, but not a good design from my point. It is not clear what "this" actually is. So for me and a lot of other developer will agree that "this" can be confusing ("this" in object scope or object's function scope?) and bring inconsistent behavior. Here is how i changed your code: linkRep Jun 3, 2017 at 10:07

Add a comment4

For a version with Array#reduce, you could use a hash table as reference to the same company with a closure over the hash table.

var data = [{ Clicks: 210, Company: "A", _id: { CompanyID: 5 } }, { Clicks: 35, Company: "C", _id: { CompanyID: 3 } }, { Clicks: 15, Company: "B", _id: { CompanyID: 2 } }, { Clicks: 13, Company: "A", _id: { CompanyID: 5 } }],
    result = data.reduce(function (hash) {
        return function (r, a) {
            var key = a._id.CompanyID;
            if (!hash[key]) {
                hash[key] = { Clicks: 0, Company: a.Company, _id: a._id };
                r.push(hash[key]);
            }
            hash[key].Clicks += a.Clicks;
            return r;
        };
    }(Object.create(null)), []);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Ask QuestionAsked 12 months agoModified 3 months agoViewed 3k times2

I need to understand the simplest way of doing this. I've got an array of objects:

const data = [
  {
    group: 'A',
    incomes: {
      "2019-12": 100,
      "2020-12": 200,
      "2021-12": 15
    }
  },
  {
    group: 'B',
    incomes: {
      "2019-12": 25,
      "2020-12": 50,
    }
  }
]

What I'm trying to get is simple object where its key is the month from data.incomes and the value is sum of relative month values, so the final result looks like:

const totalIncomes = {
  "2019-12": 125,
  "2020-12": 250,
  "2021-12": 15
}

Can anybody explain it to me step by step, please?

4 Answers

Sorted by: Highest score (default) Trending (recent votes count more) Date modified (newest first) Date created (oldest first) 3

solved using reduce and forEach

Inside the reduce function I'm running a forEach on the array of keys of the incomes object/attribute. For each key which is a date I'm checking if the accumulator of the reduce function contains an attribute for each date and creates if not. After creating the attribute I'm summing the value for the current date attribute.

const data = [{
    group: 'A',
    incomes: {
      "2019-12": 100,
      "2020-12": 200,
      "2021-12": 15
    }
  },
  {
    group: 'B',
    incomes: {
      "2019-12": 25,
      "2020-12": 50,
    }
  }
]

const totalIncomes = data.reduce((acc, curr) => {
  Object.keys(curr.incomes).forEach((key, index) => {
    if (!acc[key]) {
      acc[key] = 0
    }
    acc[key] += curr.incomes[key]
  })
  return acc
}, {})

console.log(totalIncomes)

Add a comment1

Maybe this is not the pretties solutions but you can do it like this, the function is of course not necessary.

const data = [
  {
    group: "A",
    incomes: {
      "2019-12": 100,
      "2020-12": 200,
      "2021-12": 15,
    },
  },
  {
    group: "B",
    incomes: {
      "2019-12": 25,
      "2020-12": 50,
    },
  },
];

getterInformation(data);

function getterInformation(object) {
  let objectWithCalculatedValues = {};

  object.forEach((items) => {
    for (const key in items.incomes) {
      if (objectWithCalculatedValues[key] === undefined) {
        objectWithCalculatedValues[key] = 0;
      }

      objectWithCalculatedValues[key] += items.incomes[key];
    }
  });

  console.log(objectWithCalculatedValues);
}

Add a comment1

Assuming that this information may be useful to readers who may be unable to obtain necessary guidance (due to various possible reasons), here is one possible way to achieve the objective (solution):

const aggregateIncomesByMonth = () => (
  data.map(d => Object.entries(d.incomes).map(([k, v]) => ({
    key: k,
    value: v
  }))).flat().reduce((fin, itm) => ({
    ...fin,
    [itm.key]: (fin[itm.key] || 0) + itm.value
  }), {})
);

Explanation

  1. Extract only the incomes from the data array

  2. For each income object, get the key-value pair and transform into another object of the structure {key: 20yy-mm, value: nn}

  3. Use .flat() to transform the result from step-2 into a 1-dimensional array

  4. Use .reduce to sum the value for those cases where the key (ie, 20yy-mm) matches.

Code-snippet

const data = [{
    group: 'A',
    incomes: {
      "2019-12": 100,
      "2020-12": 200,
      "2021-12": 15
    }
  },
  {
    group: 'B',
    incomes: {
      "2019-12": 25,
      "2020-12": 50,
    }
  }
];

const aggregateIncomesByMonth = () => (
  data.map(d => Object.entries(d.incomes).map(([k, v]) => ({
    key: k,
    value: v
  }))).flat().reduce((fin, itm) => ({
    ...fin,
    [itm.key]: (fin[itm.key] || 0) + itm.value
  }), {})
);

console.log(aggregateIncomesByMonth());

My approach here is to destructure the array. This way I have all the data of the incomes of group A in the variable A and the same for B.

Then I do a double loop to compare both objects data and see if the dates match. If so, sum the incomes and add the data to the total object.

const data = [
  {
    group: 'A',
    incomes: { "2019-12": 100, "2020-12": 200, "2021-12": 15 }
  },
  {
    group: 'B',
    incomes: { "2019-12": 25, "2020-12": 50 }
  }
]

let A, B, total = {};

[A, B] = [data[0].incomes, data[1].incomes]

for(const date in A){
  for(const d in B){
   total[date] = date === d ? A[date] + B[date] : A[date]
  }  
}

console.log(total)

Last updated