Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 - Group a list and find the count

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

like image 497
Madhan Avatar asked Mar 05 '23 03:03

Madhan


2 Answers

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}
like image 200
SEY_91 Avatar answered Mar 15 '23 06:03

SEY_91


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.

like image 20
Holger Avatar answered Mar 15 '23 06:03

Holger