I have a list of result. I need to find the passed results count. But there is a relation between some items in list. For Example. I have list like
1.0 - false
2.0 - true
3.0 - false
4.0 - true
1.1 - true
3.1 - true
Then the passed count should be 2 and not the 4. Because I want to group the list based on id (1,1.2,1.3,1.xx in to single group) and mark it as pass if all the items in the group is pass . I have tried group using groupingBy
and I have got a map of my expected behavior. I can iterate the map and get the count. But I want to know is there any way I can do this simply using Java 8.
public class Main {
static class Resultx {
double id = 1;
Boolean passed = false;
public void setId(double id) {
this.id = id;
}
public double getId() {
return id;
}
public void setAsPassed() {
this.passed = true;
}
public Boolean getPassed() {
return passed;
}
@Override
public String toString() {
return getId() + " - " + getPassed();
}
}
public static void main(String[] args) {
List<Resultx> results = new ArrayList<>();
for (int i = 1; i < 5; i++) {
Resultx result = new Resultx();
result.setId(i);
if (i % 2 == 0) {
result.setAsPassed();
}
results.add(result);
}
for (int i = 1; i < 5; i += 2) {
Resultx result = new Resultx();
result.setId(i + .1);
result.setAsPassed();
results.add(result);
}
System.out.println(results.size());
results.forEach(System.out::println);
System.out.println(results.stream().filter(Resultx::getPassed).count());
System.out.println(results.stream().filter(e -> !e.getPassed()).count());
System.out.println(results.stream().collect(Collectors.groupingBy(r -> (int) (r.getId()))));
}
}
Output
Total count - 6
1.0 - false
2.0 - true
3.0 - false
4.0 - true
1.1 - true
3.1 - true
Total pass count - 4
Total fail count - 2
{1=[1.0 - false, 1.1 - true], 2=[2.0 - true], 3=[3.0 - false, 3.1 - true], 4=[4.0 - true]}
I want Overall pass count and overall fail count. which is 2 and 2
Try this one
Map<Boolean, Long> collect = results.stream()
.collect(Collectors.groupingBy(r -> (int) (r.getId()))).values()
.stream().map(l -> l.stream().allMatch(p -> p.getPassed()))
.collect(Collectors.partitioningBy(k -> k, Collectors.counting()));
System.out.println(collect);
Which display:
{false=2, true=2}
There are some strange things about your code, like using a double
ID, which you just cast to int
in your grouping operation or using a Boolean
for you passed
property instead of just boolean
. Using the reference type Boolean
, opens the opportunity to be null
, which you would have to handle, if this possibility is intended. Otherwise, just use boolean
.
It’s also not clear, what result you actually want. The example is not sufficient to describe it.
If you only want to count the groups, where true
means “all where true” and false
means “some where false
”, you can do it as simple as
Map<Boolean,Long> counts = results.stream()
.collect(Collectors.collectingAndThen(
Collectors.toMap(r -> (int)r.getId(), Resultx::getPassed, Boolean::logicalAnd),
m -> m.values().stream()
.collect(Collectors.partitioningBy(b -> b, Collectors.counting()))
));
If you want to count the elements of the groups, it gets more complicated.
int totalPassedCount = results.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(r -> (int)r.getId(),
Collector.of(() -> new Object() { int count = 0; boolean pass = true; },
(o, r) -> { o.count++; o.pass &= r.getPassed(); },
(x, y) -> { x.count += y.count; x.pass &= y.pass; return x; },
o -> o.pass? o.count: 0
)),
(Map<Integer,Integer> x) -> x.values().stream().mapToInt(i -> i).sum()
));
System.out.println(totalPassedCount);
This uses a custom collector as downstream collector for groupingBy
. This custom collector collects into an object holding the element count and whether all elements passed, then, in a finishing step, it replaces these object with the count, if all elements of the group passed or zero if not. Then, a finishing step is added to the groupingBy
collector which will sum up all these values.
Above solution is for getting the passed counts, as you asked for in the beginning of your question. Since you’re asking for both at the end, you could use
Map<Boolean,Integer> counts = results.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(r -> (int)r.getId(),
Collector.of(() -> new Object() { int count = 0; boolean pass = true; },
(o, r) -> { o.count++; o.pass &= r.getPassed(); },
(x, y) -> { x.count += y.count; x.pass &= y.pass; return x; }
)),
m -> m.values().stream().collect(
Collectors.partitioningBy(o -> o.pass, Collectors.summingInt(o -> o.count)))
));
for getting both.
Or, as it’s tricky to get the intended rule from a single example,
Map<Boolean,Integer> counts = results.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(r -> (int)r.getId(),
Collector.of(() -> new Object() { int passed, failed; },
(o, r) -> { if(r.getPassed()) o.passed++; else o.failed++; },
(x, y) -> { x.passed += y.passed; x.failed += y.failed; return x; }
)),
m -> m.values().stream()
.filter(o -> o.passed == 0 || o.failed == 0)
.collect(Collectors.partitioningBy(o -> o.failed==0,
Collectors.summingInt(o -> o.failed==0? o.passed: o.failed)))
));
this will only count passed
and failed
, if all elements of a group passed or failed. But since you said, you’d expect 2
and 2
, you may remove the .filter(o -> o.passed == 0 || o.failed == 0)
line. Then, it will count passed
only if all elements of the groups passed, but count all failed
, even when some elements of the group passed. Then, you’d get 2
and 2
as result.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With