0

Is there any way to manually short circuit a stream (like in findFirst)?

Example:

Imagine a huge dictionary ordered by word size and alphabet:

cat
... (many more)
lamp
mountain
... (many more)

Only ready and compute the file from beginning, return immediately when line size exceeds 4:

read cat, compute cat
...
read tree, compute lamp
read mountain, return

The following code is very concise but does not take into advantage the order of the stream, it has to ready every line:

try (Stream<String> lines = Files.lines(Paths.get(DICTIONARY_PATH))) {
        return lines
                // filter for words with the correct size
                .filter(line -> line.length() == 4)
                // do stuff...
                .collect(Collectors.toList());
}
Journeycorner
  • 2,474
  • 3
  • 19
  • 43
  • 3
    Related: http://stackoverflow.com/questions/20746429/limit-a-stream-by-a-predicate – Alexis C. Sep 12 '16 at 17:28
  • 2
    What you want is generally called `takewhile`. According to [this question](http://stackoverflow.com/questions/20746429/limit-a-stream-by-a-predicate) it seems like there is no built-in way to do that, but you can write one yourself. – Bakuriu Sep 12 '16 at 17:35

1 Answers1

0

Answer based on Limit a stream by a predicate, processing correctly stops when predicate returns false. Hopefully this method comes available in Java 9:

private static List<String> getPossibleAnswers(int numberOfChars, char[][] possibleChars) throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(DICTIONARY_PATH)) {
        return takeWhile(lines, line -> line.length() <= numberOfChars)
                // filter length
                .filter(line -> line.length() == numberOfChars)
                // do stuff
                .collect(Collectors.toList());
    }
}

static <T> Spliterator<T> takeWhile(Spliterator<T> splitr, Predicate<? super T> predicate) {
    return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {              boolean stillGoing = true;

        @Override
        public boolean tryAdvance(Consumer<? super T> consumer) {
            if (stillGoing) {
                boolean hadNext = splitr.tryAdvance(elem -> {
                    if (predicate.test(elem)) {
                        consumer.accept(elem);
                    } else {
                        stillGoing = false;
                    }
                });
                return hadNext && stillGoing;
            }
            return false;
        }
    };
}

static <T> Stream<T> takeWhile(Stream<T> stream, Predicate<? super T> predicate) {
    return StreamSupport.stream(takeWhile(stream.spliterator(), predicate), false);
}
Community
  • 1
  • 1
Journeycorner
  • 2,474
  • 3
  • 19
  • 43
  • 1
    This is quite inefficient when compared to a classical loop. – aventurin Sep 12 '16 at 18:15
  • @aventurin there might be a faster implementation with java9. At least the code is concise, no need for temporary variables. – Journeycorner Sep 12 '16 at 18:32
  • 1
    The stream API might hide the bloat behind it from the programmer, but there's no magic that can reduce the number of CPU cycles, memory reallocations (ArrayList-collector) or extra object and class creation. Besides that, the equivalent code using a loop is easier to understand and takes just three lines of code plus two variable definitions. – aventurin Sep 12 '16 at 18:57
  • @aventurin That's not the point of the question. However, the problem statement of my example is bigger. I was first coding it with while-loops and then switched to streams because I wanted to increase my knowledge about them. Implicit version: https://github.com/Journeycorner/tom-turbo-solver/commit/7607a90a5e1ecc5cae054ae8b19b1ecc313ab580 vs. functional version: https://github.com/Journeycorner/tom-turbo-solver/blob/master/src/at/journeycorner/Main.java . If you can code it with less lines of code, without omitting brackets, write me a pull request ;). – Journeycorner Sep 15 '16 at 09:08