How to iterate nested for loops referring to parent elements using Java 8 streams?
Asked Answered
M

2

10

I want to iterate nested lists using java8 streams, and extract some results of the lists on first match. Unfortunately I have to also get a values from the parent content if a child element matches the filter.

How could I do this?

java7

Result result = new Result();

//find first match and pupulate the result object.
for (FirstNode first : response.getFirstNodes()) {
    for (SndNode snd : first.getSndNodes()) {
        if (snd.isValid()) {
            result.setKey(first.getKey());
            result.setContent(snd.getContent());
            return;
        }
    }
}

java8

 response.getFirstNodes().stream()
        .flatMap(first -> first.getSndNodes())
        .filter(snd -> snd.isValid())
        .findFirst()
        .ifPresent(???); //cannot access snd.getContent() here
Mahler answered 24/3, 2015 at 14:43 Comment(1)
possible duplicate of Java 8 - Streams Nested ForEach with different CollectionLoricate
A
13

When you need both values and want to use flatMap (as required when you want to perform a short-circuit operation like findFirst), you have to map to an object holding both values

response.getFirstNodes().stream()
  .flatMap(first->first.getSndNodes().stream()
    .map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
  .filter(e->e.getValue().isValid())
  .findFirst().ifPresent(e-> {
    result.setKey(e.getKey().getKey());
    result.setContent(e.getValue().getContent());
  });

In order to use standard classes only, I use a Map.Entry as Pair type whereas a real Pair type might look more concise.

In this specific use case, you can move the filter operation to the inner stream

response.getFirstNodes().stream()
  .flatMap(first->first.getSndNodes().stream()
     .filter(snd->snd.isValid())
     .map(snd->new AbstractMap.SimpleImmutableEntry<>(first, snd)))
  .findFirst().ifPresent(e-> {
    result.setKey(e.getKey().getKey());
    result.setContent(e.getValue().getContent());
  });

which has the neat effect that only for the one matching item, a Map.Entry instance will be created (well, should as the current implementation is not as lazy as it should but even then it will still create lesser objects than with the first variant).

Aldora answered 24/3, 2015 at 15:31 Comment(2)
Thanks for the explaination. Looking at your code I feel it's still better to stick stick to the java7 fashioned iteration style in case multiple/parent elements have to be accessed.Mahler
Yeah, the good old for loops aren’t outdated. Maybe a future Java version with language support for pairs/tuples opens the possibility for a nicer stream solution…Aldora
Z
-1

It should be like this:

Edit: Thanks Holger for pointing out that the code won't stop at the first valid FirstNode

response.getFirstNodes().stream()
  .filter(it -> {it.getSndNodes().stream().filter(SndNode::isValid).findFirst(); return true;})
  .findFirst()
  .ifPresent(first -> first.getSndNodes().stream().filter(SndNode::isValid).findFirst().ifPresent(snd -> {
    result.setKey(first.getKey());
    result.setContent(snd.getContent());
  }));

A test can be found here

Zwiebel answered 24/3, 2015 at 15:6 Comment(5)
This doesn’t do anything useful. The question’s original code called result.setKey(first.getKey()); result.setContent(snd.getContent()); and stopped processing subsequent items. Your code does nothing like that.Aldora
I was showing how to access snd.getContent() within ifPresent(), have modified the code nowZwiebel
@Aldora can I get the -1 off please, I have tested that my code works and does what the op intendedZwiebel
I didn’t downvote so I can’t do anything about it. Besides that, using forEach still doesn’t solve the question’s task which is to stop after the first match. Of course, testing with only one matching item won’t exhibit this behavior…Aldora
No problems, thanks for pointing mistakes in my code. I have now fixed the code to do the right thing. I'll leave the answer out there because it's correct. Whoever thought the answer is worth downvoting, it would have helped if I was told the reason.Zwiebel

© 2022 - 2024 — McMap. All rights reserved.