How to interleave (merge) two Java 8 Streams?
As you can see from the question comments, I gave this a go using zip:
Stream<String> a = Stream.of("one", "three", "five");
Stream<String> b = Stream.of("two", "four", "six");
Stream<String> out = interleave(a, b);
public static <T> Stream<T> interleave(Stream<T> streamA, Stream<T> streamB) {
return zip(streamA, streamB, (o1, o2) -> Stream.of(o1, o2)).flatMap(s -> s);
}
/**
* https://stackoverflow.com/questions/17640754/zipping-streams-using-jdk8-with-lambda-java-util-stream-streams-zip
**/
private static <A, B, C> Stream<C> zip(Stream<A> streamA, Stream<B> streamB, BiFunction<A, B, C> zipper) {
final Iterator<A> iteratorA = streamA.iterator();
final Iterator<B> iteratorB = streamB.iterator();
final Iterator<C> iteratorC = new Iterator<C>() {
@Override
public boolean hasNext() {
return iteratorA.hasNext() && iteratorB.hasNext();
}
@Override
public C next() {
return zipper.apply(iteratorA.next(), iteratorB.next());
}
};
final boolean parallel = streamA.isParallel() || streamB.isParallel();
return iteratorToFiniteStream(iteratorC, parallel);
}
private static <T> Stream<T> iteratorToFiniteStream(Iterator<T> iterator, boolean parallel) {
final Iterable<T> iterable = () -> iterator;
return StreamSupport.stream(iterable.spliterator(), parallel);
}
A much dumber solution than Holger did, but may be it would fit your requirements:
private static <T> Stream<T> interleave(Stream<T> left, Stream<T> right) {
Spliterator<T> splLeft = left.spliterator();
Spliterator<T> splRight = right.spliterator();
T[] single = (T[]) new Object[1];
Stream.Builder<T> builder = Stream.builder();
while (splRight.tryAdvance(x -> single[0] = x) && splLeft.tryAdvance(builder)) {
builder.add(single[0]);
}
return builder.build();
}
I’d use something like this:
public static <T> Stream<T> interleave(Stream<? extends T> a, Stream<? extends T> b) {
Spliterator<? extends T> spA = a.spliterator(), spB = b.spliterator();
long s = spA.estimateSize() + spB.estimateSize();
if(s < 0) s = Long.MAX_VALUE;
int ch = spA.characteristics() & spB.characteristics()
& (Spliterator.NONNULL|Spliterator.SIZED);
ch |= Spliterator.ORDERED;
return StreamSupport.stream(new Spliterators.AbstractSpliterator<T>(s, ch) {
Spliterator<? extends T> sp1 = spA, sp2 = spB;
@Override
public boolean tryAdvance(Consumer<? super T> action) {
Spliterator<? extends T> sp = sp1;
if(sp.tryAdvance(action)) {
sp1 = sp2;
sp2 = sp;
return true;
}
return sp2.tryAdvance(action);
}
}, false);
}
It retains the characteristics of the input streams as far as possible, which allows certain optimizations (e.g. for count()
and toArray()
). Further, it adds the ORDERED
even when the input streams might be unordered, to reflect the interleaving.
When one stream has more elements than the other, the remaining elements will appear at the end.