tutorials
tutorials copied to clipboard
insertInStream should work for infinite streams
The entire stream shouldn't be greedily consumed if we only need a finite prefix.
Code: https://github.com/eugenp/tutorials/blob/950bbadc353bdca114befc98cf4a18476352220e/core-java-modules/core-java-streams-2/src/test/java/com/baeldung/streams/StreamAddUnitTest.java#L44
Conclusion should be updated: https://www.baeldung.com/java-stream-append-prepend#conclusion
Hi, what if there was a Supplier in insertInStream signature?
public <T> Stream<T> insertInStream(Supplier<Stream<T>> supplier, T elem, int index) {
Stream<T> limited = supplier.get().limit(index);
Stream<T> skipped = supplier.get().skip(index + 1);
return Stream.concat(Stream.concat(limited, Stream.of(elem)), skipped);
}
@AddeusExMachina Supplier<Stream<T>>
is a supplier of multiple streams, not a single stream.
This looks pretty hacky, but works without creating intermediate structures (so you can insert an element at some very high index and not run out of memory):
private static <T> Stream<T> insertInStream(Stream<T> stream, T elem, int index) {
final Spliterator<T> spliterator = stream.spliterator();
final Iterator<T> iterator = Spliterators.iterator(spliterator);
return Stream.concat(Stream.concat(
Stream.generate(iterator::next).limit(index),
Stream.of(elem)),
StreamSupport.stream(spliterator, /* parallel= */ false));
}
I came up with this solution without affecting the method signature
private static <T> Stream<T> insertInStream(Stream<T> stream, T elem, int index) {
Iterator<T> iterator = stream.iterator();
return Stream.concat(Stream.concat(
Stream.generate(iterator::next).limit(index),
Stream.of(elem)),
Stream.generate(iterator::next));
}
It seems that Stream.generate(iterator::next)
won't let it work for finite streams.
I had to adjust it like below
return Stream.concat(Stream.concat(
Stream.generate(iterator::next).limit(index),
Stream.of(elem)),
StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, /* characteristics= */ 0), /* parallel= */ false));
At that point it may be better to use the original approach of
final Spliterator<T> spliterator = stream.spliterator();
final Iterator<T> iterator = Spliterators.iterator(spliterator);
because I think Spliterator
is strictly more expressive than Iterator
, and can theoretically make use of more properties of the source object. So Stream -> Spliterator -> Iterator
is potentially less lossy than Stream -> Iterator -> Spliterator
, the latter requiring a free/arbitrary choice of characteristics
.
That's annoying that we can't reuse the iterator after the stream's limit is reached
Thanks @GeoffChurch We've merged your solution and updated the article.
Hi @lor6, I think the article still needs a minor update.
It says
In this case, since the stream is not consumed greedily, it is still partially lazy
The new approach is actually fully lazy, so you can e.g. insert an element at index 3 billion in constant time.
Also, the conclusion says
Keep in mind that although prepending an element works for any Stream, adding it to the end or at a specific index only works for finite streams.
This sentence can probably be removed because inserting at a specific finite index now works for any Stream, and it does technically work to append to the end of an infinite stream, you just wouldn't reach the element after a finite number of steps.