-5

I want to replace the following while with break condition with a Java stream and lambda expression. I am not able to find a way for this. I use Java 8.

Lets say if the input Arraylist(result) has sorted values: [1,2,3,4,5,10,12,30,40,50]

The output should contain values [1,2,3,4,5,10]

List<String> finalResult = new ArrayList();
    while (index < maxSize) {
        if (inputList.get(index) > 10)) {
            finalResult.add(inputList.get(index));
        } else {            
                break;              
        }
        index++;
    }

If I try using findFirst() to break the condition, then I'm not able to collect the list.

Optional<String> result =
inputList.stream().filter(obj -> some_condition_met).findFirst();
Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
GrandPa
  • 484
  • 9
  • 28
  • 1
    Why do you want that? What is the point? – luk2302 Nov 09 '20 at 09:33
  • I am just trying to find a way to do this java streams which will be like single statement. Is it possible to do that? – GrandPa Nov 09 '20 at 09:35
  • And what have you tried yourself? You see, there are many tutorials out there that explain step by step how to use streams ... it should be your first impulse to solve this yourself, and only come here AFTER trying it yourself. – GhostCat Nov 09 '20 at 09:38
  • I have updated the question , i see that findFirst() can break the loop without looping all the elements , but i am not able to collect the elements. – GrandPa Nov 09 '20 at 09:53
  • @SSK try .collect() method. e.g. .collect(Collectors.toList()) – Filip Nov 09 '20 at 09:59
  • I am not able to use Collect and findFirst together. If we only use Collect , then it will iterate over all the elements which i want to avoid. – GrandPa Nov 09 '20 at 10:09
  • 1
    The code you provided does not return expected output `[1,2,3,4,5,10]` after fixing some compilation issues like mixing String and Integer for `input` and `finalResult`, instead it returns an empty list `[]` because it breaks out on the first value of the list. – Nowhere Man Nov 09 '20 at 14:43

3 Answers3

4

If you are using java 9 something like below is may be what you are looking for:

List<Integer> myList = List.of(1,2,3,4,5,10,12,30,40,50);
List<Integer> under5 = myList.stream()
                             .takeWhile(i -> i <= 5)
                             .collect(Collectors.toList());
System.out.println(under5);
Eritrean
  • 15,851
  • 3
  • 22
  • 28
  • This is one is good to know. Do we have something similar in Java8 , because i am not able to use Colllect and findFirst together. – GrandPa Nov 09 '20 at 10:06
  • 1
    @SSK Takewhile and dropwhile are only available with java 9. If you still use java 8 you have to implement it yourself. I don't know if it's worth the effort but you can have a look at the following post and see how it can be done: [limit-a-stream-by-a-predicate](https://stackoverflow.com/questions/20746429/limit-a-stream-by-a-predicate#answer-20765715) – Eritrean Nov 09 '20 at 10:15
  • @Eritrean This is very good example. Cheers – Abhishek Aug 17 '21 at 03:58
1
  1. For the provided input [1,2,3,4,5,10,12,30,40,50], the expected output [1,2,3,4,5,10] should be returned by simple filter:
static List<Integer> findBelow(List<Integer> inputList, Predicate<Integer> condition) {
    return inputList.stream()
                    .filter(condition::test)
                    .collect(Collectors.toList());
}
  1. If a range of indexes needs to be specified, this should be implemented using IntStream::range:
static List<Integer> findBelow(List<Integer> inputList, int start, int end, Predicate<Integer> condition) {
    start = Math.max(0, start);
    end = Math.min(inputList.size(), end);
    return IntStream.range(start, end)
                    .filter(i -> condition.test(inputList.get(i)))
                    .mapToObj(inputList::get)
                    .collect(Collectors.toList());
}

Obviously, these implementations do not break out of the loop, and Java 8 Stream API does not have facilities for that.

The following workarounds can be offered:

  1. Use findFirst to detect the index of the first non-match and then limit the range of index:
static List<Integer> findWithFirst(List<Integer> inputList, int start, int end, Predicate<Integer> condition) {
    start = Math.max(0, start);
    end = Math.min(inputList.size(), end);
    int firstNonMatch = IntStream.range(start, end)
                                 .filter(i -> !condition.test(inputList.get(i)))
                                 .findFirst()
                                 .orElse(maxSize);

    return IntStream.range(start, firstNonMatch) // no filter needed any longer
                    .mapToObj(inputList::get)
                    .collect(Collectors.toList());
}
  1. Use boolean flag outside the stream to check if the inverted break condition is met while filtering the input list:
// using AtomicBoolean
static List<Integer> findAndBreak2(List<Integer> inputList, int start, int end, Predicate<Integer> condition) {
    AtomicBoolean breakFound = new AtomicBoolean(false);
    return IntStream.range(start, Math.min(inputList.size(), end))
                    .filter(i -> { 
                        breakFound.set(
                            breakFound.get() || !condition.test(inputList.get(i))
                        ); 
                        return !breakFound.get(); 
                    })
                    .mapToObj(inputList::get)
                    .collect(Collectors.toList());
}
// using static class field
static boolean found = false;
static List<Integer> findAndBreak(List<Integer> inputList, int start, int end, Predicate<Integer> condition) {
    found = false;
    return IntStream.range(start, Math.min(inputList.size(), end))
                    .filter(i -> { found |= !condition.test(inputList.get(i)); return !found; })
                    .mapToObj(inputList::get)
                    .collect(Collectors.toList());
}

Tests:

Condition i <= 10 is trivial, as it works for all cases. Invert condition i > 10 as in the OP code demonstrates break producing empty lists.

    Predicate<Integer> condition = (i) -> i > 10;
        
    List<Integer> inputList = Arrays.asList(1,2,3,4,5,10,12,30,40,50);
    List<Integer> finalResult = new ArrayList<>();
    int index = 0, maxSize = 10;
    while (index < maxSize) {
        if (condition.test(inputList.get(index))) {
            finalResult.add(inputList.get(index));
        } else {            
            break;              
        }
        index++;
    }
    
    System.out.println("loop: " + finalResult);

    System.out.println("plain filter: " + findBelow(inputList, condition));
    
    System.out.println("filter and range limit: " + findBelow(inputList, 0, maxSize, condition));
    
    System.out.println("filter and break static: " + findAndBreak(inputList, 0, maxSize, condition));

    System.out.println("filter and break atomic: " + findAndBreak2(inputList, 0, maxSize, condition));

    System.out.println("filter and findFirst: " + findWithFirst(inputList, 0, maxSize, condition));

Output:

loop: []
plain filter: [12, 30, 40, 50]
filter and range limit: [12, 30, 40, 50]
filter and break static: []
filter and break atomic: []
filter and findFirst: []
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
0

As far as i understand your question. Why are you trying to use a stream for this job. A simple loop would do the trick don't try complicating things. If the only thing is the code being too long extract it to a separate private method. The main point is stream is not what your looking for in this case. Hope this helped

  • Thanks for the answer , i am learning java , was just curious to know if we have something in streams to achieve that. – GrandPa Nov 09 '20 at 09:52
  • Its nice to play around with the features. Aside from this, imagin stream is like a flowing river and the data as the objects flowing in the current, you can find a floating object or filter it with a net, but trying to end the flowing river is not possible ( easily that is :D ). I know this was not a good analogy but your looking for a dam or pond in this case and trying to achieve it with a flowing river. Sorry for my bad english , hope this makes sense. :D – Paritosh Bhattarai Nov 09 '20 at 09:56