H.v.M.
H.v.M.

Reputation: 1659

Is Files.lines().allMatch() ordered? Attempting to find first three consecutive lines which each match different criteria

I'm trying to get away from while ((line = br.readLine()) != null), but the limitations of streams are proving to be hard to overcome in an elegant way in this case.

I want the first line which matches a certain criterion and the last two lines before this which matches two different criteria. This seems to be the best I can do, but it only works if lines are encountered in the right order so it doesn't terminate early. So are they? It's hard to tell from the documentation.

String[] savedLines = { "", "", "" };
try (Stream<String> lines = Files.lines(file)) {
    lines.allMatch(line -> {
        if (line.startsWith(s1)) {
            savedLines[0] = line;
        } else if (line.startsWith(s2) && line.contains(s3)) {
            savedLines[1] = line;
        } else if (line.startsWith(s4)) {
            savedLines[2] = line;
            return false;
        }
        return true;
    });
}

Upvotes: 0

Views: 213

Answers (2)

M. Justin
M. Justin

Reputation: 21189

The JEP 461: Stream Gatherers Java 22 preview language feature adds built-in support for sliding windows, which could be used to check each trio of adjacent lines, in order.

List<String> result;
try (Stream<String> lines = Files.lines(file)) {
    result = lines.gather(Gatherers.windowSliding(3))
            .filter(w -> w.get(0).startsWith(s1)
                    && w.get(1).startsWith(s2)
                    && w.get(1).contains(s3)
                    && w.get(2).startsWith(s4))
            .findFirst()
            .orElse(List.of());
}

This uses the new Stream.gather method with the new built-in Gatherers.windowSliding gatherer to convert the initial Stream<String> to a Stream<List<String>> of three-element lists of consecutive lines.

As this is still a preview language feature, it should not be used in production code until it graduates to being a permanent feature.

Javadocs

Gatherer:

An intermediate operation that transforms a stream of input elements into a stream of output elements, optionally applying a final action when the end of the upstream is reached. […]

[…]

There are many examples of gathering operations, including but not limited to: grouping elements into batches (windowing functions); de-duplicating consecutively similar elements; incremental accumulation functions (prefix scan); incremental reordering functions, etc. The class Gatherers provides implementations of common gathering operations.

Stream.gather:

Returns a stream consisting of the results of applying the given gatherer to the elements of this stream.

Gatherers.windowSliding

Returns a Gatherer that gathers elements into windows -- encounter-ordered groups of elements -- of a given size, where each subsequent window includes all elements of the previous window except for the least recent, and adds the next element in the stream. […]

Example:

// will contain: [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8]]
List<List<Integer>> windows2 =
    Stream.of(1,2,3,4,5,6,7,8).gather(Gatherers.windowSliding(2)).toList();

// will contain: [[1, 2, 3, 4, 5, 6], [2, 3, 4, 5, 6, 7], [3, 4, 5, 6, 7, 8]]
List<List<Integer>> windows6 =
    Stream.of(1,2,3,4,5,6,7,8).gather(Gatherers.windowSliding(6)).toList();

Upvotes: 1

Andy Turner
Andy Turner

Reputation: 140454

Given that the lines are being read from a file, I can't see why they wouldn't be in file order - it would take more storage to do anything other than read the current line, then read the next line etc.

Irrespective, don't do it like this: you are going against what it says in the documentation about the predicate of Stream.allMatch:

predicate - a non-interfering, stateless predicate to apply to elements of this stream

Your predicate is not stateless. You shouldn't do this in allMatch.

Stick with BufferedReader.

Upvotes: 2

Related Questions