tutorials icon indicating copy to clipboard operation
tutorials copied to clipboard

insertInStream should work for infinite streams

Open GeoffChurch opened this issue 3 years ago • 3 comments

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

GeoffChurch avatar Jun 18 '21 15:06 GeoffChurch

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 avatar Jun 15 '22 10:06 AddeusExMachina

@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));
    }

GeoffChurch avatar Jun 16 '22 16:06 GeoffChurch

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));
  }

AddeusExMachina avatar Jul 02 '22 17:07 AddeusExMachina

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));

AddeusExMachina avatar Jul 31 '22 15:07 AddeusExMachina

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.

GeoffChurch avatar Jul 31 '22 17:07 GeoffChurch

That's annoying that we can't reuse the iterator after the stream's limit is reached

GeoffChurch avatar Jul 31 '22 17:07 GeoffChurch

Thanks @GeoffChurch We've merged your solution and updated the article.

lor6 avatar Sep 08 '22 07:09 lor6

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.

GeoffChurch avatar Oct 14 '22 14:10 GeoffChurch