Stream¶
A lazy list that remembers what it computed. The first element is computed when the Stream is built, each of the
others when it is first reached, and then kept. It can be infinite.
Its name clashes with java.util.stream.Stream: import com.guizmaii.zazr.collection.Stream, and write the JDK one
in full when you need both.
When to choose it¶
For a sequence computed on demand: an infinite series, a sequence whose elements are expensive and only partly read, or a sequence defined in terms of itself.
Stream<Integer> naturals = Stream.from(1);
Vector<Integer> squares = naturals.map(n -> n * n).filter(n -> n % 2 == 1).take(4).toVector();
// Vector(1, 9, 25, 49)
Stream<Long> fibonacci = Stream.of(0L, 1L).appendSelf(self -> self.zipWith(self.tail(), Long::sum));
Vector<Long> firstTen = fibonacci.take(10).toVector();
// Vector(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)
Stream.iterate(seed, f), Stream.continually(supplier) and Stream.ofAll are other ways to create one.
Costs¶
lazy means the call does no work beyond the first element; each element is computed when it is read. The notes
say which elements a call computes ("forces") right away.
| Operation | Cost | Note |
|---|---|---|
head |
O(1) | O(1). |
tail |
O(1) | O(1); the tail is forced when it is asked for, and memoised. |
last |
O(n) | O(n); the whole Stream is forced, so it does not terminate on an infinite Stream. |
init |
lazy | lazy; the result runs one element behind this Stream, so only the first two elements are forced. |
get |
O(index) | O(index); the first index + 1 elements are forced. |
update |
O(index) | O(index); the first index + 1 elements are forced. |
prepend |
O(1) | O(1); nothing is forced. |
append |
O(1) | O(1); the head is forced, the rest of this Stream stays deferred and the element is reached last. |
prependAll |
O(1) | O(1); the head of this Stream is forced, the rest stays deferred. |
appendAll |
O(1) | O(1); the head of this Stream and of elements is forced, the rest stays deferred. |
insert |
lazy | lazy; the first index elements are forced when the result reaches them. |
removeAt |
lazy | lazy; the first index elements are forced when the result reaches them. |
take |
lazy | lazy; O(1), one element is forced and the rest on demand. |
drop |
O(n) | O(n); the first n elements are forced, the rest stays deferred. |
slice |
O(beginIndex) | O(beginIndex); the elements up to beginIndex are forced, the rest when the result reaches them, so it works on an infinite Stream. |
splitAt(Predicate<? super T>) |
O(k) | O(k); the k elements before the split are forced, the suffix stays deferred. |
splitAt(int) |
O(n) | O(n); the first n elements are forced, the suffix stays deferred. |
reverse |
O(n) | O(n); the whole Stream is forced. |
sorted |
O(n log n) | O(n log n) comparisons; the whole Stream is forced. |
length |
O(n) | O(n); the whole Stream is forced, so it does not terminate on an infinite Stream. |
contains |
O(n) | O(n) for this default, which walks the elements; the sets and the maps override it with their own lookup. |
indexOf |
O(n) | O(n); the elements are forced until the element is found. |
zip |
lazy | lazy; O(min(n, m)) pairs when consumed. |
sliding(int) |
lazy | lazy; a window is built when the result reaches it and its elements are forced when the window is consumed, so an infinite Stream can be windowed. Whether a further window exists is decided when the result's tail is reached, which forces up to max(size, step) + 1 elements past the window's start. |
sliding(int, int) |
lazy | lazy; a window is built when the result reaches it and its elements are forced when the window is consumed, so an infinite Stream can be windowed. Whether a further window exists is decided when the result's tail is reached, which forces up to max(size, step) + 1 elements past the window's start. |
grouped |
lazy | lazy; a block is built when the result reaches it and its elements are forced when the block is consumed, so an infinite Stream can be grouped. Whether a further block exists is decided when the result's tail is reached, which forces one element past the end of the block. |
distinct |
lazy | lazy; each element is forced and hashed when the result reaches it. |
Every method: complexity page.
Sharp edges¶
- Operations that need the whole sequence force it and never return on an infinite
Stream:length,size,last,reverse,sorted,maxandmin(their notes say so), and anything that reads every element, such asfoldLeft,mkStringortoVector. - The first element is never lazy: building a
Streamcomputes it, andmap,filterand the others compute the first element of their result. partitionMaplooks for the first element of each side right away. On an infiniteStreamwhose elements all go to one side, it never returns.- A
Streamkeeps every element it computed. Holding on to the start of a longStreamwhile walking it keeps all of it in memory.