Java 8 Query - Grouping calculation based on Key with Multiple Lists of Objects

Viewed 197

I needed some help with a Java 8 solution to calculate totals.
Suppose that I have 3 item objects and each of these objects contain a list of (2 or 3) tax objects. Please find below a detailed imaging of this object -

Item 1
(List)
Tax 1 - Id-1 | Name-A | Desc | Amount-Rs.5 | More objects
Tax 2 - Id-2 | Name-B | Desc | Amount-Rs.10 | More objects
Tax 3 - Id-3 | Name-C | Desc | Amount-Rs.8 | More objects

Item 2
(List)
Tax 1 - Id-1 | Name-A | Desc | Amount-Rs.9 | More objects
Tax 3 - Id-3 | Name-C | Desc | Amount-Rs.10 | More objects

Now,
I have created a new Object (Aggregate) that needs to be the total based on Id
For ex: The above information should be have 3 aggregate objects with amounts summed
Tax-1 | Name-A | Desc | Amount-Rs.14
Tax-2 | Name-B | Desc | Amount-Rs.10
Tax-2 | Name-B | Desc | Amount-Rs.18

I was able to achieve this by looping through each object and adding it to a map <String Id,Object Aggregate>. If the keys existed, I would pull out the amount value from the Object Aggregate and add the amount to the existing amount. If not, I would create a new Aggregate object and add it to the map but I was hoping if there was a more optimised way to do this in Java 8

Global variable - Map<String, Aggregate> taxAggreg

for (Item item: Items) {
    List < Tax > taxes = item.getTaxes;
    for (Tax tax: taxes) {
        createTax(Tax tax);
    }
}

private void createTax(Tax tax) {
    if (!taxAggreg.isEmpty() && taxAggreg.containsKey(tax.getTaxId())) {
        Aggregate aggreg = taxAggreg.get(tax.getTaxId());
        aggreg.setTaxAmount(aggreg.getTaxAmount().add(tax.getTaxAmount()));
        taxAggreg.put(tax.getTaxId(), aggreg);
    } else {
        taxAggreg.put(tax.getTaxId(), new Aggregate(tax.getTaxId(), tax.getName(),
            tax.getTaxAmount()));
    }
}
2 Answers

First, flat the list of tag for item, then transform the Tax object into Aggregate object. Then map by tax id using Collectors.toMap then merge tax objects with same id.

Map<Integer, Aggregate> resMap = items.stream()
                .flatMap(item -> item.getTaxes().stream())
                .map(tax -> new Aggregate(tax.getTaxId(), tax.getName(), tax.getTaxAmount()))
                .collect(Collectors.toMap(Tax::getTaxId, e -> e,    
                                  (a, b) -> new Aggregate(a.getTaxId(), a.getName(), 
                                                   a.getTaxAmount().add(b.getTaxAmount()))));

Here using Collectors.toMap map by Tax::getTaxId which is the first parameter of toMap. The next one is the key of the map e -> e for the whole Aggregate obj as key. And finally, the last parameter in the merge function will be used for merging two Aggregate objects into one Aggregate object so that all values of the same key merged into one Aggregate object

Collectors.toMap(Tax::getTaxId, e -> e,    
                    (a, b) -> new Aggregate(a.getTaxId(), a.getName(),
                                            a.getTaxAmount().add(b.getTaxAmount())))

Maybe something like this could work:

    List<Aggregate> list1 = new ArrayList<Aggregate>();      
    List<Aggregate> list2 = new ArrayList<Aggregate>();    

    // Add your data to lists...

    // Now create maps with the identity for each object in lists
    Map<String, Aggregate> aggregateMap1 = list1.stream().collect(Collectors.toMap(Aggregate::getId, Function.identity()));
    Map<String, Aggregate> aggregateMap2 = list2.stream().collect(Collectors.toMap(Aggregate::geId, Function.identity()));

    // Now merge lists and add the amounts together
    List<Aggregate> result = aggregateMap1.keySet().forEach(key -> aggregateMap1.merge(key,
            aggregateMap2.get(key),
            (aggr1, aggr2) -> {
                aggr1.setAmount(aggr1.getAmount() + aggr2.getAmount());
                return aggr1;
            })).values().stream().collect(Collectors.toList());;

Not tested. Also could be fine tuned.

Related