Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Picking elements of a list until condition is met with Java 8 Lambdas

I am trying to switch my mind to think the functional way and recently faced a situation in which I needed to pick up elements from a list until a condition is met and I could not find an easy natural way of achieving this. Obviously I am still learning.

Say I have this list:

List<String> tokens = Arrays.asList("pick me", "Pick me", "pick Me",
    "PICK ME", "pick me and STOP", "pick me", "pick me and Stop", "pick me");

// In a non lambdas was you would do it like below
List<String> myTokens = new ArrayList<>();
for (String token : tokens) {
    myTokens.add(token);
    if (token.toUpperCase().endsWith("STOP")) {
        break;
    }
}

Thank you in advance for your inputs

NOTE: Before publishing this I read Limit a stream by a predicate but I could not see how I can adapt that answer to my problem. Any help would be appreciated thanks.

like image 779
Julian Avatar asked Aug 29 '15 20:08

Julian


People also ask

How to get particular element from List in Java?

The get() method of List interface in Java is used to get the element present in this list at a given specific index. Syntax : E get(int index) Where, E is the type of element maintained by this List container.


2 Answers

If you really must use Streams API, keep it simple and use a stream of indexes:

int lastIdx = IntStream.range(0, tokens.size())
        .filter(i -> tokens.get(i).toUpperCase().endsWith("STOP"))
        .findFirst()
        .orElse(-1);

List<String> myTokens = tokens.subList(0, lastIdx + 1);

Or make a new List out of the sublist if you want an independent copy that's not backed by the original list.

like image 166
Misha Avatar answered Oct 04 '22 19:10

Misha


In JDK9 there will be a new Stream operation called takeWhile which does the thing similar to what you need. I backported this operation to my StreamEx library, so you can use it even in Java-8:

List<String> list = StreamEx.of(tokens)
                            .takeWhile(t -> !t.toUpperCase().endsWith("STOP"))
                            .toList();

Unfortunately it does not take the "STOP" element itself, so the second pass is necessary to add it manually:

list.add(StreamEx.of(tokens).findFirst(t -> t.toUpperCase().endsWith("STOP")).get());

Note that both takeWhile and findFirst are short-circuit operations (they will not process the whole input stream if unnecessary), so you can use them with very long or even infinite streams.

However using StreamEx you can solve it in single pass using the trick with groupRuns. The groupRuns method groups adjacent Stream elements to the List based on the supplied predicate which tells whether two given adjacent elements should be grouped or not. We may consider that the group ends with the element containing "STOP". Then we just need to take the first group:

List<String> list = StreamEx.of(tokens)
                            .groupRuns((a, b) -> !a.toUpperCase().endsWith("STOP"))
                            .findFirst().get();

This solution also will not do extra work when the first group is finished.

like image 25
Tagir Valeev Avatar answered Oct 04 '22 18:10

Tagir Valeev