membersound
membersound

Reputation: 86935

Stream equivalent of summing values within nested foreach loops with a filter

How can a sum be calculated using the Stream API with nested foreach loops where each has a filter condition?

//java7
Double sum = null; 
for (FirstNode first : response.getFirstNodes()) {
    if (first.isValid()) {
        for (SndNnode snd : first.getSndNodes()) {
            if (snd.getType() == NodeType.AMOUNT) {
                sum += snd.getAmount();
                break;
            }
        }
    }
}

//java8
response.getFirstNodes().stream().filter(first -> first.isValid()).mapToDouble(???).sum();

My snd foreach loop would be:

first.getSndNodes().stream().filter(snd -> snd.getType() == NodeType.AMOUNT).mapToDouble(snd -> snd.getAmount()).findFirst().sum();

How could I now integrate the snd foreach loop into the first, to get a global sum of the nested lists?

Upvotes: 5

Views: 3856

Answers (4)

Holger
Holger

Reputation: 298579

Your attempt is close to the right solution

response.getFirstNodes().stream()
.filter(FirstNode::isValid)
.mapToDouble(first ->
   first.getSndNodes().stream()
        .filter(snd -> snd.getType() == NodeType.AMOUNT)
        .mapToDouble(snd -> snd.getAmount())
        .findAny().orElse(0))
.sum();

If you are sure that there is at most one match in the inner stream, you can use findAny as there is no requirement on the ordering then. I used the simplest solution for dealing with the possible absence of a match by replacing it with 0 which is transparent to the sum and saves us from additional filtering.

Upvotes: 4

Alexis C.
Alexis C.

Reputation: 93902

You could use flatMap:

response.getFirstNodes()
        .stream()
        .filter(first -> first.isValid())
        .flatMap(first -> first.getSndNodes().stream())
        .filter(snd -> snd.getType() == NodeType.AMOUNT)
        .mapToDouble(snd -> snd.getAmount())
        .sum();

I'm not sure whether that break; is intentional in your original code.


With the break; statement, it should looks like this:

response.getFirstNodes()
                .stream()
                .filter(first -> first.isValid())
                .map(first -> first.getSndNodes().stream().filter(snd -> snd.getType() == NodeType.AMOUNT).findFirst())
                .filter(Optional::isPresent)
                .mapToDouble(opt -> opt.get().getAmount())
                .sum();

Basically, for each FirstNode you test whether it's valid, then you map each FirstNode to a stream of its SndNodes for which you find the first that has the type NodeType.AMOUNT. You need then to filter to get only Optionals that are not empty and for them you get the SndNode they contains for which you get the corresponding amount.

Upvotes: 8

Konstantin Yovkov
Konstantin Yovkov

Reputation: 62884

You can use .flatMap() the nested nodes. For example:

response.getFirstNodes().stream()
                        .filter(FirstNode::isValid)
                        .flatMap(first -> first.getSndNodes().stream())
                        .filter(snd -> snd.getType == NodeType.AMOUNT)
                        .mapToDouble(SndNode::getAmount)
                        .sum();

Upvotes: 2

Eran
Eran

Reputation: 394156

You can use flatMap to create a single Stream of all the inner lists :

response.getFirstNodes()
        .stream()
        .filter (first -> first.isValid())
        .flatMap (first -> first.getSndNodes().stream())
        .filter(snd -> snd.getType() == NodeType.AMOUNT)
        .mapToDouble(snd -> snd.getAmount())
        .sum();

Upvotes: 2

Related Questions