0

So basically my program looks like this:

Map<Month, List<Purchase>> map2 = purchases
                .stream()
                .collect(Collectors.groupingBy(Purchase::getMonthOfDate));

which creates Map containing the Month and a list of Purchases as values (each Purchase contains a Price).

My goal is getting a Map that looks like this:

Map<Month, Double>

where Month stays the same as the old map but all prices of the Purchases for each month get summed up and put in as double.

Is there any way to achieve that?

Pshemo
  • 122,468
  • 25
  • 185
  • 269
Godzy
  • 49
  • 5
  • 4
    This is already answered here - https://stackoverflow.com/questions/26340688/group-by-and-sum-objects-like-in-sql-with-java-lambdas – dream2work Sep 01 '20 at 13:54

3 Answers3

4

You could use public static <T,​K,​A,​D> Collector<T,​?,​Map<K,​D>> groupingBy​(Function<? super T,​? extends K> classifier, Collector<? super T,​A,​D> downstream).

Here downstream Collector handles elements in same group and Collectors.summingDouble looks like what you may want to use here.

So instead of

.collect(Collectors.groupingBy(Purchase::getMonthOfDate));

you could use something like

.collect(Collectors.groupingBy(
      Purchase::getMonthOfDate,                     // group using month names
      Collectors.summingDouble(Purchase::getValue)) // sum price of each purchase in group
      //                                 ^^^^^^^^
      //      pick method which returns value/price of that purchase 
);
Pshemo
  • 122,468
  • 25
  • 185
  • 269
4

Assuming each Purchase has getPrice method returning double for each particular purchase, use the Collectors.summingDouble downstream collector within the Collectors.groupingBy:

Map<Month, Double> monthlySumOfPurchases = purchases
                .stream()
                .collect(Collectors.groupingBy(
                        Purchase::getMonthOfDate, 
                        Collectors.summingDouble(Purchase::getPrice)));
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • 3
    Or `collect(Collectors.toMap(Purchase::getMonthOfDate, Purchase::getPrice, Double::sum));` – Holger Sep 01 '20 at 16:44
  • @Holger: I always forget the `BinaryOperator` in `Collectors.map`. Which way is more preferable in your opinion? Do you have an idea of an use-case using `toMap` would win over `groupingBy`? – Nikolas Charalambidis Sep 01 '20 at 21:59
  • 1
    This depends on the ratio between “number of groups” and “elements per group”. The more groups and less elements in a group, the better will `toMap` perform. The `groupingBy` collector can avoid the boxing overhead for intermediate values, but it still has to box the final result and it needs a temporary mutable container for the intermediate result. So this only pays off if you have more than two elements per group. It needs even more for a large number of groups, to compensate the finishing action that runs over the entire map and replaces the container with the final result. – Holger Sep 02 '20 at 06:41
  • 1
    Of course, this doesn’t apply to downstream collectors where the mutable container is identical to the final result, like `groupingBy(…, toList())`. This always wins over the equivalent `toMap`. – Holger Sep 02 '20 at 06:43
2

Here's an alternative that uses forEach:

Map<Month, Double> monthlyPurchasePriceSums = new HashMap<>();
map2.forEach((key, value) -> 
        monthlyPurchasePriceSums.put(key,
                                    value.stream()
                                        .mapToDouble(Purchase::getPrice).sum()
                                    )
);

Admittedly, it's not as elegant as the other answers given so far because it needs an extra line for the Map that takes the results, but it's working and streaming (a little).

deHaar
  • 17,687
  • 10
  • 38
  • 51