Shay
Shay

Reputation: 474

Count in single pass multiple items using java 8 stream

Suppose I have the following class:

class Z {
    X x;
    Y y;
}

And I have a list of Z elements. I want to count in a single pass how many elements have in their x field the value x1, and how many have in their y field the value y1.

Using a loop it is straight forward:

int countOfx1 = 0;
int countOfy1 = 0;
for (Z z: list) {
    if (z.x == x1) {
        countOfx1++
    }
    if (z.y == y1) {
        countOfy1++
    }
 }

Can it be done as simply using streams?

Upvotes: 6

Views: 2068

Answers (2)

Tagir Valeev
Tagir Valeev

Reputation: 100309

You can use multiClassify collector which I posted in this answer:

List<Predicates> preds = Arrays.asList(z -> z.x == x1, z -> z.y == y1);
List<Long> counts = stream.collect(multiClassify(preds, Collectors.counting()));
// counts.get(0) -> counts for z.x == x1
// counts.get(1) -> counts for z.y == y1

The simple alternative is, of course, to traverse the input twice:

long countsX = list.stream().filter(z -> z.x == x1).count();
long countsY = list.stream().filter(z -> z.y == y1).count();

Such solution is short and usually not so bad in terms of performance for usual inputs like ArrayList.

Upvotes: 3

sprinter
sprinter

Reputation: 27986

You can do this by creating a collector for the totals:

class Zcount {
    private int xCount = 0;
    private int yCount = 0;

    public Zcount accept(Z z) {
        if (z.x == x1)
            xCount++;
        if (z.y == y1)
            yCount++;
        return this;
    }

    public Zcount combine(ZCount other) {
        xCount += other.xCount;
        yCount += other.yCount;
        return this;
    }
}

Zcount count = list.stream().collect(Zcount::new, Zcount::accept, Zcount::combine);

This has the advantage over the iterative solution that you can make the stream parallel which could have performance advantages if your list is very large.

Upvotes: 8

Related Questions