Assume we have a collection of objects that are identified by unique String
s, along with a class Tree
that defines a hierarchy on them. That class is implemented using a Map
from nodes (represented by their IDs) to Collection
s of their respective children's IDs.
class Tree {
private Map<String, Collection<String>> edges;
// ...
public Stream<String> descendants(String node) {
// To be defined.
}
}
I would like to enable streaming a node's descendants. A simple solution is this:
private Stream<String> children(String node) {
return edges.getOrDefault(node, Collections.emptyList()).stream();
}
public Stream<String> descendants(String node) {
return Stream.concat(
Stream.of(node),
children(node).flatMap(this::descendants)
);
}
Before continuing, I would like to make the following assertions about this solution. (Am I correct about these?)
Walking the
Stream
returned fromdescendants
consumes resources (time and memory) – relative to the size of the tree – in the same order of complexity as hand-coding the recursion would. In particular, the intermediate objects representing the iteration state (Stream
s,Spliterator
s, ...) form a stack and therefore the memory requirement at any given time is in the same order of complexity as the tree's depth.As I understand this, as soon as I perform a terminating operation on the
Stream
returned fromdescendants
, the root-level call toflatMap
will cause all containedStream
s – one for each (recursive) call todescendants
– to be realized immediately. Thus, the resultingStream
is only lazy on the first level of recursion, but not beyond. (Edited according to Tagir Valeevs answer.)
If I understood these points correctly, my question is this: How can I define descendants
so that the resulting Stream
is lazy?
I would like the solution to be as elegant as possible, in the sense that I prefer a solution which leaves the iteration state implicit. (To clarify what I mean by that: I know that I could write a Spliterator
that walks the tree while maintaining an explicit stack of Spliterator
s on each level. I would like to avoid that.)
(Is there possibly a way in Java to formulate this as a producer-consumer workflow, like one could use in languages like Julia and Go?)