The main issue with your code is that it operates with side-effects by leveraging the forEach
operation. If you were to run your code in parallel, it would be broken and you wouldn't have the expected result. In this case, what you want is to use a mutable reduction approach, that is collect elements into a container.
All propose solutions hardcodes the fact that we reindex even and odd indexes. However, it would be possible to make them generic easily.
The first simple approach is to use 2 Stream pipeline (as you have done). The first one creates the first part of the String and the second one creates the second. Instead of using forEach
to add elements to a StringBuilder
, we collect the elements into a new StringBuilder
. As such, each index is mapped to the character at that index and accumulated into a new StringBuilder
with appendCodePoint
. The 3 arguments to the collect
call corresponds to:
- the supplier of the mutable container, (
StringBuilder::new
);
- the accumulator of each element into the container (appending the code point of the character);
- a combiner combining two containers into one (used in parallel processing).
private static String reindex(String input) {
StringBuilder result = new StringBuilder(input.length() + 1);
result.append(filter(input, index -> index % 2 == 0));
result.append(' ');
result.append(filter(input, index -> index % 2 == 1));
return result.toString();
}
private static StringBuilder filter(String input, IntPredicate predicate) {
return IntStream.range(0, input.length())
.filter(predicate)
.map(input::charAt)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append);
}
The duplicate logic has been extracted to a utility method that filter the indexes matching the given predicate and collects the filtered characters into a StringBuilder
.
But the issue is that it loops around the String two times when such an operation should be possible using a single traversal. This is a bit more complicated to do and I would first measure the performance of the first solution before doing this one: it is possible that your concerned String do not have a length big enough to warrant such an approach.
A possible solution is to collect the elements inside an array of StringBuilder
. The array to append the current character to is determined by the result of i % 2
: when i
is even, this will be 0 and it will append it to the first array; when i
is odd, it will append it to the second array.
private static String reindex(String input) {
StringBuilder[] result =
IntStream.range(0, input.length())
.collect(
() -> new StringBuilder[] { new StringBuilder(), new StringBuilder() },
(b, i) -> b[i % 2].appendCodePoint(input.charAt(i)),
(b1, b2) -> {
b1[0].append(b2[0]);
b1[1].append(b2[1]);
}
);
return result[0] + " " + result[1];
}