2

I have next data:

const data = [
    {amount: 100, percent: 1, days: 7},
    {amount: 75, percent: 3, days: 8},
    {amount: 75, percent: 3, days: 3},
    {amount: 100, percent: 2, days: 5},
    {amount: 100, percent: 1, days: 10},
    {amount: 50, percent: 3, days: 9}
];

The task is to group objects by amount and percent, so that if amount and percent in different objects are the same I need to add their days. The result should look the next way:

const data = [
    {amount: 100, percent: 1, days: 17},
    {amount: 75, percent: 3, days: 11},
    {amount: 100, percent: 2, days: 5},
    {amount: 50, percent: 3, days: 9}
];

1st and 3rd objects are not grouped because of different percent. I have tried to use reduce method:

const result = data.reduce((groupedByAmount, current) => {
    let amount = current.amount;
    let percent= current.percent;
    if (amount == amount && percent == percent) {
        groupedByAmount.amount = amount;
        current.days += current.days
    }
    return groupedByAmount
}, {
    amount: 0,
    percent: null,
    days: 0
});

I don't how to check if amount and percent are the same, maybe map or filter would help

Max
  • 55
  • 3
  • Looks like it might help: [groupby on array of objects](https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects) – freedomn-m Jan 04 '22 at 09:56
  • Note `.reduce()` always returns a single value, not an array - solutions in the above return a single object with keys. – freedomn-m Jan 04 '22 at 09:57
  • might be soluion with map or filter for example? – Max Jan 04 '22 at 10:03

4 Answers4

1

const data = [
    {amount: 100, percent: 1, days: 7},
    {amount: 75, percent: 3, days: 8},
    {amount: 75, percent: 3, days: 3},
    {amount: 100, percent: 2, days: 5},
    {amount: 100, percent: 1, days: 10},
    {amount: 50, percent: 3, days: 9}
];

const doGroup = (data) => {
    const obj = data.reduce((result, next) => {
    const key = `${next.amount}_${next.percent}`
    const dd =  result[key]?.days ?? 0;
    result[key] = {...next, days: next.days + dd};
    return result;
  }, {});
  return Object.values(obj);
}

console.log(doGroup(data))
Jafar Jabr
  • 642
  • 5
  • 11
  • 2
    Say data had objects `{amount: 10, percent: 11, days: ?}` and `amount: 101, percent: 1, days: ?}` will they not get incorrectly get grouped together with the `key` you are using right now? Maybe add a character between `amount` and `percent` e.g., `${next.amount}_${next.percent}` – Sash Sinha Jan 04 '22 at 10:25
  • make sense, will edit it – Jafar Jabr Jan 04 '22 at 11:18
1

Considering each of the methods you've suggested:

  • reduce => reduce array elements to a single value. Can be used to create an object, then convert that object back to the array

  • map => return a value for every value in the array

  • filter=> return multiple (or single) matching values

You can loop through each value, building a new array and using filter() to find existing values to add to.

Note: this likely won't perform well with large arrays due to the multiple use of .filter() - using a composite key (combining two values into a single value) would likely perform better.

const data = [
    {amount: 100, percent: 1, days: 7},
    {amount: 75, percent: 3, days: 8},
    {amount: 75, percent: 3, days: 3},
    {amount: 100, percent: 2, days: 5},
    {amount: 100, percent: 1, days: 10},
    {amount: 50, percent: 3, days: 9}
];

var result = [];

data.forEach(e => {
    var current = result.filter(d => d.amount == e.amount && d.percent == e.percent);
    if (current.length === 0) {
        result.push(e);
    } else {
        current[0].days += e.days;
    }
});
console.log(result);
freedomn-m
  • 27,664
  • 8
  • 35
  • 57
0

const data = [
    {amount: 100, percent: 1, days: 7},
    {amount: 75, percent: 3, days: 8},
    {amount: 75, percent: 3, days: 3},
    {amount: 100, percent: 2, days: 5},
    {amount: 100, percent: 1, days: 10},
    {amount: 50, percent: 3, days: 9}
];

const temp = data.reduce((acc, {amount, percent, days}) => {
  acc[amount] ||= {}
  acc[amount][percent] ||= 0
  acc[amount][percent] += days
  return acc;
}
,{});

const result = Object.entries(temp).reduce((acc, [amount, obj]) => {
  acc.push(...Object.entries(obj).map(([percent, days]) => ({ amount: Number(amount), percent: Number(percent), days })))
  return acc
},[])

console.log(result)
bel3atar
  • 913
  • 4
  • 6
0

This example is larger in number of lines, but simpler, perhaps.

const data = [
  {amount: 100, percent: 1, days: 7},
  {amount: 75, percent: 3, days: 8},
  {amount: 75, percent: 3, days: 3},
  {amount: 100, percent: 2, days: 5},
  {amount: 100, percent: 1, days: 10},
  {amount: 50, percent: 3, days: 9}
];

function group_by_amount_percent(array) {
  var ret = []
  var cache = {}
  array.forEach(function(e, i, a) {
    if (cache[e.amount] == null) { cache[e.amount] = {} }
    if (cache[e.amount][e.percent] == null) { cache[e.amount][e.percent] = 0 }
    cache[e.amount][e.percent] += e.days
  })
  for (var amount in cache) {
    for (var percent in cache[amount]) {
      ret.push({amount: amount, percent: percent, days: cache[amount][percent]})
    }
  }
  return ret
}

console.log('group_by_amount_percent(data): ', group_by_amount_percent(data))

Zlatov
  • 126
  • 1
  • 6