Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to sum values within nested foreach loops?

How can a sum be calcaulated using 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?

like image 348
membersound Avatar asked Mar 25 '15 12:03

membersound


2 Answers

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.

like image 109
Alexis C. Avatar answered Sep 30 '22 03:09

Alexis C.


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.

like image 37
Holger Avatar answered Sep 30 '22 05:09

Holger