How to include trailing empty strings in Pattern.compile("\\R").splitAsStream(input)?

Viewed 208

This is not a duplication of Java String split removed empty values, which deals with split() method returning a new array. In this case I would like to avoid the array.

I solved this problem with a workaround, which I am posting below as a possible solution to my question.

My goal is to process all lines including empty strings such as the following example:

String input = "foo\nbar\n\n\nzul\n\n\n";
Pattern NEWLINE = Pattern.compile("\\R");
int [] count = {1};
NEWLINE
    .splitAsStream(input)
    .forEach(line -> System.out.println(count[0]++ + ": " + line));

which produces:

1: foo
2: baz
3: 
4: 
5: zul

Yet, it is missing:

6: 
7:

How to include last empty lines?

3 Answers

You can use a lookahead (?=(\\R)) to ensure that the \\R delimiter is not consumed and then remove it yourself with String.trim().

String input = "foo\nbar\n\n\nzul\n\n\n";
Pattern NEWLINE = Pattern.compile("(?=(\\R))");
int[] count = {1};
NEWLINE.splitAsStream(input)
       .map(String::trim)
       .forEach(line -> System.out.println(count[0]++ + ": " + line));

It will however result in a zero-length match for "" after the last \n.

1: foo
2: bar
3: 
4: 
5: zul
6: 
7: 
8: 

As an alternative you can make your own implementation of an equivalent method to splitAsStream(), which includes trailing empty strings and still avoids the instantiation of an array, such as:

static Stream<String> splitAsStream(Pattern p, CharSequence input) {
    Spliterator<String> iter = new Spliterators.AbstractSpliterator<String>(
        Long.MAX_VALUE,
        Spliterator.ORDERED | Spliterator.SIZED
    ) {
        int index = 0;
        final Matcher m = p.matcher(input);

        @Override
        public boolean tryAdvance(Consumer<? super String> action) {
            while(m.find()) {
                if (index != 0 || index != m.start() || m.start() != m.end()) {
                    action.accept(input.subSequence(index, m.start()).toString());
                    index = m.end();
                    return true;
                }
            }
            if(index < input.length()) {
                // Add remaining segment
                action.accept(input.subSequence(index, input.length()).toString());
                index = input.length();
                return true;
            } else {
                return false;
            }
        }
    };
    return StreamSupport.stream(iter, false);
}

Since java 9 on can use Matcher.results() yielding a Stream<MatchResult>

Pattern.compile("(.*)\\R").matcher(input)
    .results(mr -> System.out.println(count[0]++ + ": " + mr.group(1)));

This guarantees that the final "line" has a terminating \n too. "....\nabc" will discard the last abc though.

For that I think the following should work (note group()), using a lookahead and requiring for the end $ at least one char ..

Pattern.compile(".*(?=\\R)|.$)").matcher(input)
    .results(mr -> System.out.println(count[0]++ + ": " + mr.group()));

A split with -1 and a check on the last entry seems a bit more readable.

Related