简体   繁体   中英

Replace while loop with break with Java streams and lambda expression

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();

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);
  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: []

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM