Migster
Migster

Reputation: 153

Java Lambda Stream group By and summing integer values/average

I have a List of Customer Objects List (Customer: int id, bool isActive, int billingCount, ...) and want the sum and average of billingCount. Unfortunately, my code didn't work. How do I need to change the code so it will work?

sum and average sould look like this:

true 1234

false 1234

 Map<Boolean, Integer> sum = customer.stream()
                .map(c -> c.getIsActive())
                .collect(Collectors.groupingBy(c -> c, Collectors.summingInt(Customer::getBillingCount)));


Map<Boolean, Integer> average = customer.stream()
                .map(c -> c.getIsActive())
                .collect(Collectors.groupingBy(c -> c, Collectors.averagingInt(Customer::getBillingCount)));
    }

I get the following error:

Error:(146, 17) java: no suitable method found for collect(java.util.stream.Collector<Customer,capture#1 of ?,java.util.Map<java.lang.Object,java.lang.Integer>>)
    method java.util.stream.Stream.<R>collect(java.util.function.Supplier<R>,java.util.function.BiConsumer<R,? super java.lang.Boolean>,java.util.function.BiConsumer<R,R>) is not applicable
      (cannot infer type-variable(s) R
        (actual and formal argument lists differ in length))
    method java.util.stream.Stream.<R,A>collect(java.util.stream.Collector<? super java.lang.Boolean,A,R>) is not applicable
      (inference variable T has incompatible bounds
        lower bounds: java.lang.Object,Customer
        lower bounds: java.lang.Boolean)

Upvotes: 2

Views: 1718

Answers (3)

Michał Ziober
Michał Ziober

Reputation: 38655

You do not need to use map. See below example:

List<Customer> customers = Arrays.asList(
        new Customer(10, true, 5),
        new Customer(11, true, 3),
        new Customer(20, false, 12),
        new Customer(21, false, 11));

Map<Boolean, Integer> sum = customers
        .stream()
        .collect(Collectors.groupingBy(Customer::isActive, Collectors.summingInt(Customer::getBillingCount)));
System.out.println(sum);

Map<Boolean, Double> avg = customers
        .stream()
        .collect(Collectors.groupingBy(Customer::isActive, Collectors.averagingInt(Customer::getBillingCount)));
System.out.println(avg);

Above code prints:

{false=23, true=8}
{false=11.5, true=4.0}

Upvotes: 1

Ricola
Ricola

Reputation: 2922

Do you really need a map for active and inactive? What about simply this:

IntSummaryStatistics summaryActive = customer.stream()
        .filter(Customer::getIsActive)
        .mapToInt(Customer::getBillingCount)
        .summaryStatistics();

long sumActive  = summary.getSum();
double averageActive = summary.getAverage();

And you can do the same with inactive by replacing the filter to .filter(c -> !c.getIsActive())

Upvotes: 1

rgettman
rgettman

Reputation: 178263

With your map calls, your are converting your Stream<Customer> to a Stream<Boolean>, or a stream of just trues and falses about your active and inactive customers. You can't call Customer's getBillingCount on Booleans.

You can use the partitioningBy Collector to group by a boolean value without the prior map call. The downstream collector can be the summarizingInt Collector to collect sum and average (plus a few others you may not need: count, max, min) at the same time.

Map<Boolean, Integer> stats = customer.stream()
    .collect(Collectors.partitioningBy(Customer::getIsActive,
                                       Collectors.summarizingInt(Customer::getBillingCount)));

This should get you the stats for true and false in one statement.

Upvotes: 3

Related Questions