Grouping list of object by a field & applying binary function in one of the field to reduce it by sum in one line of code using stream java 8



Let’s take for an example I have list of transaction which contains txnId, paymentCode, amount. Output should be each payment code wise how much amount collected.

I tried to group it based on payment code as key, & list of amount for the payment code, but I am not able to reduce it.

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

class Payment {

    Integer txnId;
    String paymentCode;
    Integer amount;

    Payment(Integer txnId, String paymentCode, Integer amount) {
        this.txnId = txnId;
        this.paymentCode = paymentCode;
        this.amount = amount;
    }

    public String getPaymentCode() {
        return this.paymentCode;
    }

    public Integer getAmount() {
        return this.amount;
    }

    @Override
    public String toString() {
        return "Payment [txnId=" + txnId + ", paymentCode=" + paymentCode + ", amount=" + amount + "]";
    }

}

public class ListGroupingSum {

    public static void main(String[] args) {

        List<Payment> payments = new ArrayList<>();
        payments.add(new Payment(100, "GPAY", 100));
        payments.add(new Payment(101, "CC", 200));
        payments.add(new Payment(102, "PAYTM", 300));
        payments.add(new Payment(103, "GPAY", 400));
        payments.add(new Payment(104, "CC", 500));

        // @formatter:off
        Map<String, List<Integer>> paymentListByCodeAndAmount = payments.stream()
                .collect(Collectors.groupingBy(Payment::getPaymentCode, Collectors.mapping(Payment::getAmount, Collectors.toList())));
        System.out.println(paymentListByCodeAndAmount);
        
        //System.out.println(paymentListByCodeAndAmount.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));
        // @formatter:on
    }
}

Which gives the output of

{CC=[200, 500], GPAY=[100, 400], PAYTM=[300]} 

But, expectation is

{CC=700, GPAY=500, PAYTM=300} 

Answer

Map<String, Integer> paymentListByCodeAndAmount = payments.stream()
  .collect(groupingBy(Payment::getPaymentCode, summingInt(Payment::getAmount)));

(with exactly your expected output)

enter image description here

Really, summingInt is a Collector where groupingBy will put every group, you can “sum ints” or whatever you want.

For example, if the group data is not directly integers, you can map:

Map<String, Integer> paymentListByCodeAndAmount = payments.stream()
        .collect(groupingBy(Payment::getPaymentCode, 
                mapping(p -> p.amount * p.txnId, 
                        summingInt(x -> x))));

readed as “with payments as stream, grouping by payment code, take the product of the amount and the txnId and give me the sum total”.

But, of course, exists millions of combinations, instead simply sum you could summarize (min, max, avg, …) and do something with this:

Map<String, Double> paymentListByCodeAndAmount = payments.stream()
        .collect(groupingBy(Payment::getPaymentCode,
                collectingAndThen(summarizingInt(p -> p.amount * p.txnId),
                        IntSummaryStatistics::getAverage)));

readed as “with payments as stream, grouping by payment code, summarize the product of the amount and the txnId and then give me the average”.



Source: stackoverflow