Home > Mobile >  Java streams sum by
Java streams sum by

Time:05-20

I have a class like this:

@Data
@AllArgsConstructor
class AccountLink {
    private String code;
    private String account;
    private BigDecimal participation;
}

And a list like this:

        List<AccountLink> accountLinks = List.of(
                new AccountLink("001", "331", new BigDecimal(100)),
                new AccountLink("001", "332", new BigDecimal(100)),
                new AccountLink("001", "336", new BigDecimal(100)),
                new AccountLink("001", "678", new BigDecimal(100)),

                new AccountLink("002", "331", new BigDecimal(100)),
                new AccountLink("002", "332", new BigDecimal(100)),
                new AccountLink("002", "333", new BigDecimal(100)),
                new AccountLink("002", "334", new BigDecimal(100)),
                new AccountLink("002", "335", new BigDecimal(100)),
                new AccountLink("002", "336", new BigDecimal(100)),
                new AccountLink("002", "678", new BigDecimal(100)),

                new AccountLink("003", "331", new BigDecimal(100)),
                new AccountLink("003", "332", new BigDecimal(100)),
                new AccountLink("003", "333", new BigDecimal(100)),
                new AccountLink("003", "336", new BigDecimal(100)),
                new AccountLink("003", "678", new BigDecimal(100))
        );

I have to total the participation for each account and then divide that by the total number of codes which is 3 in this example, the output should be like this:

331 - 100
332 - 100
333 - 66.6666
334 - 33.3333
335 - 33.3333
336 - 100
678 - 100

This is the code I wrote to achieve this:

        Map<String, Double> linksCalculation = new HashMap<>();
        Map<String, List<AccountLink>> linksPerAccount = accountLinks.stream()
                .collect(Collectors.groupingBy(AccountLink::getAccount));

        linksPerAccount.forEach((account, linksList) -> {
            linksCalculation.compute(account, (key, value) -> {
                BigDecimal sum = linksList.stream()
                        .map(AccountLink::getParticipation)
                        .reduce(BigDecimal.ZERO, BigDecimal::add);

                if (value == null) value = 0d;
                value = Double.sum( value, sum.divide(new BigDecimal(3), 4, RoundingMode.DOWN).doubleValue() );
                return value;
            });
        });

linksCalculation would display this information correctly.

However, I'd like to know if there's a cleaner way to do this using Java streams API.

CodePudding user response:

It's a little difficult to do this in one step because you have two very different calculations to perform. However, it's possible to do it somewhat cleanly in two steps. First get the count of codes, then divide each participation value by that as you go rather than waiting to divide the total.

long numCodes = accountLinks.stream().map(AccountLink::getCode).distinct().count();

Map<String, Double> linksCalculation = accountLinks.stream().collect(groupingBy(
        AccountLink::getAccount,
        reducing(
                0.0d,
                al -> al.getParticipation().doubleValue() / numCodes,
                Double::sum
        )
));

Collectors.reducing() is really the key here. The arguments are (1) the initial value, (2) a function to generate the value for each element, and (3) a function to combine the new value with the ongoing reduction value — the sum, in this case.

CodePudding user response:

In order to preserve your BigDecimal computations, I did it as follows. compute the number of distinct codes.

final long nCodes = accountLinks.stream().map(AccountLink::getCode).distinct().count();
  • first, use Collectors.groupingBy for the Account number as key
  • then use Collectors.mapping to pull the participation from the class.
  • Then use Collectors.collectingAndThen to
    • first reduce the list of participations into a sum
    • then apply a finishing function to divide by nCodes.
    • and then return a double value of the result.
Map<String, Double> linksCalculation = accountLinks.stream()
        .collect(Collectors.groupingBy(AccountLink::getAccount,
            Collectors.mapping(AccountLink::getParticipation,
              Collectors.collectingAndThen(
                Collectors.reducing(BigDecimal.ZERO,(sum, val) ->sum.add(val)),
                 sum -> sum.divide(BigDecimal.valueOf(nCodes),4,RoundingMode.DOWN)
                       .doubleValue()))));

linksCalculation.entrySet().forEach(System.out::println);

prints

331=100.0
332=100.0
333=66.6666
334=33.3333
335=33.3333
336=100.0
678=100.0

CodePudding user response:

Using groupingBy with a downstream collector (summingDouble):

final int n = (int) accountLinks.stream().map(a -> a.code).distinct().count();
         
Map<String, Double> result = accountLinks.stream()
    .collect(Collectors.groupingBy(
         a -> a.account, 
         Collectors.summingDouble(a -> a.participation.doubleValue() / n)
    ));
  •  Tags:  
  • java
  • Related