Bea Bea - 3 months ago 21
TypeScript Question

How to calculate a subtotal sum from category / subcategory - TypeScript / Angular2

I try to go ahead with sum using category and subcategory datas but have to say it's difficult to get the right syntaxe.

Here is my datas :

{ id : 1, category: 12, subcategory: 14, inv_amt: 470 },
{ id : 2, category: 12, subcategory: 14, inv_amt: 660 },
{ id : 3, category: 12, subcategory: 15, inv_amt: 605 },
{ id : 4, category: 13, subcategory: 14, inv_amt: 4760 },
{ id : 5, category: 13, subcategory: 16, inv_amt: 6600 },
{ id : 6, category: 13, subcategory: 16, inv_amt: 6050 },
{ id : 7, category: 14, subcategory: 17, inv_amt: 460 }


I would like to get the total amount by categories and subcategory, as :

for category = 12
subcategory : 14 => Sum = 1130
subcategory : 15 => Sum = 605

for (category = 13) :
subcategory : 14 => Sum = 4760
subcategory : 16 => Sum = 12650

for (category = 14) :
subcategory : 17 => Sum = 460


I use this code which gave me the sum for a category.

this.totalCreances = this.creances
.filter(c => c.category === remise.category)
.map(c => c.inv_amt)
.reduce((sum, current) => sum + current);


It give one result (number)

I tried to add a second filter (on the map), but it didn't work.

How is it possible to made a 2nd selection on the result of the first one ?

Thanks,
Bea

Answer

You can achieve this using array.reduce

items.reduce<{[id:number]: {[id:number]: number}}>((acc, cur) => {
    if (cur.category in acc) {
        if (cur.subcategory in acc[cur.category]) {
            acc[cur.category][cur.subcategory] += cur.inv_amt;
        } else {
            acc[cur.category][cur.subcategory] = cur.inv_amt;
        }
    } else {
        acc[cur.category] = {
            [cur.subcategory]: cur.inv_amt
        }
    }
    return acc;
}, {});

This will produce an object that contains your data. With your example data, that object looks like this:

{ '12': { '14': 1130, '15': 605 },
  '13': { '14': 4760, '16': 12650 },
  '14': { '17': 460 } }

You can then traverse that object to print out the various totals.

The generic form of what you're looking for is called groupBy. You could probably find an implementation of that for JS and use that if you preferred to.


There's another way to do it which is slightly slower but may be intuitively clearer. This includes the printing step.

let uniqueCategories = [...new Set(items.map(x => x.category))];

uniqueCategories.forEach(category => {
    console.log(`for (category = ${category}) :`);
    // get entries with a category
    let subcategories = [...new Set(items.filter(x => x.category === category).map(x => x.subcategory))];
    subcategories.forEach(subcategory => {
        let sum :number = items.filter(x => x.category === category && x.subcategory === subcategory)
            .reduce((acc, cur) => acc + cur.inv_amt, 0);
        console.log(`subcategory : ${subcategory} => Sum = ${sum}`)
    });
});

You get the categories, you loop through them, you get the subcategories, you loop through them. You filter the entire list down each time.