Home > database >  Java Stream - groupingBy() and counting() when a curtain Condition is met
Java Stream - groupingBy() and counting() when a curtain Condition is met

Time:08-03

Given the following class Test

class Test {
    String testName;
    String studName;
    String status;
}

and a list of tests

List<Test> tests = List.of(
        new Test("English",     "John", "passed"),
        new Test("English",     "Dave", "passed"),
        new Test("Science",     "Alex", "failed"),
        new Test("Science",     "Jane", "failed"),
        new Test("History",     "Dave", "passed"),
        new Test("Mathematics", "Anna", "passed"),
        new Test("Mathematics", "Lisa", "passed"),
        new Test("Mathematics", "Paul", "failed"),
        new Test("Geography",   "Mark", "passed"),
        new Test("Physics",     "John", "failed"));

I need to group by testName and count only where status equals "passed". I need to do the equivalent of below code with streams :

Map<String, Long>  result2 = new HashMap<>();
for (Test t : tests) {
    result2.putIfAbsent(t.getTestName(), 0L);
    if (t.getStatus().equals("passed")) {
        result2.computeIfPresent(t.getTestName(), (k, v) -> v   1);
    }
}

The correct and desired output:

{Geography=1, English=2, Science=0, Mathematics=2, History=1, Physics=0}

I'm looking for a stream approach, but couldn't find a solution yet. A simple Collectors.counting will count all, regardless of status "failed/passed":

Map<String, Long> resultCounting = tests.stream()
    .collect(Collectors.groupingBy(
        Test::getTestName,
        Collectors.counting()
    ));

Output:

{Geography=1, English=2, Science=2, Mathematics=3, History=1, Physics=1}

I thought about filtering beforehand, but then I will loose those subjects where all statuses are "failed".

Map<String, Long> resultFilter = tests.stream()
    .filter(t -> t.getStatus().equals("passed"))
    .collect(Collectors.groupingBy(
        Test::getTestName,
        Collectors.counting()
    ));

Output:

{Geography=1, English=2, Mathematics=2, History=1}

How can I group all tests by testName, but count only those where status is "passed" ?

Is it possible to wrap Collectors.counting() in some kind of condition?

CodePudding user response:

You can achieve the desired result by using collector toMap(keyMapper,valueMapper,mergeFunction).

valueMapper function would either produce 1 or 0 depending on on the status.

Map<String, Integer> passCountByTestName = tests.stream()
    .collect(Collectors.toMap(
        Test::getTestName,
        test -> test.getStatus().equals("passed") ? 1 : 0,
        Integer::sum
    ));
    
passCountByTestName.forEach((k, v) -> System.out.println(k   " -> "   v));

Output:

Geography -> 1
English -> 2
Science -> 0
Mathematics -> 2
History -> 1
Physics -> 0

Sidenote: it would be better to use boolean or enum as type for the status property instead of relying on string values.

  • Related