The correct (though not very easy) way to do this is to write your own Spliterator
. The common algorithm is the following:
- Take the existing stream Spliterator using
stream.spliterator()
- Write your own Spliterator which may consume elements of existing one when advancing probably doing some additional operations.
- Create a new stream based on your spliterator via
StreamSupport.stream(spliterator, stream.isParallel())
- Delegate
close()
call to original stream like .onClose(stream::close)
.
Writing good spliterator which parallelizes well is often very non-trivial task. However if you don't care about parallelization you may subclass AbstractSpliterator
which is simpler. Here's an example how to write a new Stream operation which removes an element at given position:
public static <T> Stream<T> removeAt(Stream<T> src, int idx) {
Spliterator<T> spltr = src.spliterator();
Spliterator<T> res = new AbstractSpliterator<T>(Math.max(0, spltr.estimateSize()-1),
spltr.characteristics()) {
long cnt = 0;
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if(cnt++ == idx && !spltr.tryAdvance(x -> {}))
return false;
return spltr.tryAdvance(action);
}
};
return StreamSupport.stream(res, src.isParallel()).onClose(src::close);
}
This is minimal implementation and it can be improved to show better performance and parallelism.
In my StreamEx library I tried to simplify the addition of such custom stream operations via headTail
. Here's how to do the same using StreamEx
:
public static <T> StreamEx<T> removeAt(StreamEx<T> src, int idx) {
// head is the first stream element
// tail is the stream of the rest elements
// want to remove first element? ok, just remove tail
// otherwise call itself with decremented idx and prepend the head element to the result
return src.headTail(
(head, tail) -> idx == 0 ? tail : removeAt(tail, idx-1).prepend(head));
}
You can even support chaining with chain()
method:
public static <T> Function<StreamEx<T>, StreamEx<T>> removeAt(int idx) {
return s -> removeAt(s, idx);
}
Usage example:
StreamEx.of("Java 8", "Stream", "API", "is", "not", "great")
.chain(removeAt(4)).forEach(System.out::println);
Finally note that even without headTail
there are some ways to solve your problems using StreamEx. To remove at specific index you may zip with increasing numbers, then filter and drop indexes like this:
StreamEx.of(stream)
.zipWith(IntStreamEx.ints().boxed())
.removeValues(pos -> pos == idx)
.keys();
To collapse adjacent repeats there's dedicated collapse
method (it even parallelizes quite well!):
StreamEx.of(stream).collapse(Object::equals);