Sometimes I want to perform a set of operations on a stream, and then process the resulting stream two different ways with other operations.
Can I do this without ha
It's possible if you're buffering elements that you've consumed in one duplicate, but not in the other yet.
We've implemented a duplicate() method for streams in jOOλ, an Open Source library that we created to improve integration testing for jOOQ. Essentially, you can just write:
Tuple2, Seq> desired_streams = Seq.seq(
IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate();
(note: we currently need to box the stream, as we haven't implemented an IntSeq yet)
Internally, there is a LinkedList buffer storing all values that have been consumed from one stream but not from the other. That's probably as efficient as it gets if your two streams are consumed about at the same rate.
Here's how the algorithm works:
static Tuple2, Seq> duplicate(Stream stream) {
final LinkedList gap = new LinkedList<>();
final Iterator it = stream.iterator();
@SuppressWarnings("unchecked")
final Iterator[] ahead = new Iterator[] { null };
class Duplicate implements Iterator {
@Override
public boolean hasNext() {
if (ahead[0] == null || ahead[0] == this)
return it.hasNext();
return !gap.isEmpty();
}
@Override
public T next() {
if (ahead[0] == null)
ahead[0] = this;
if (ahead[0] == this) {
T value = it.next();
gap.offer(value);
return value;
}
return gap.poll();
}
}
return tuple(seq(new Duplicate()), seq(new Duplicate()));
}
More source code here
In fact, using jOOλ, you'll be able to write a complete one-liner like so:
Tuple2, Seq> desired_streams = Seq.seq(
IntStream.range(1, 100).filter(n -> n % 2 == 0).boxed()
).duplicate()
.map1(s -> s.filter(n -> n % 7 == 0))
.map2(s -> s.filter(n -> n % 5 == 0));
// This will yield 14, 28, 42, 56...
desired_streams.v1.forEach(System.out::println)
// This will yield 10, 20, 30, 40...
desired_streams.v2.forEach(System.out::println);