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.