0

One of the advantages of streams is that you can avoid visiting the whole structure for some operations, like anyMatch or filter+findFirst. However, if you have your own data structure, depending on how you turn it into a stream you may end up visiting it all anyway. What is the right way to turn a custom tree data type into a stream? Consider the following example:

interface Tree{
  void forEach(Consumer<Integer> c);
}

final class EmptyTree implements Tree{
  public void forEach(Consumer<Integer> c){}
}

interface NonEmptyTree extends Tree{}

record Leave(int label) implements NonEmptyTree{
  public void forEach(Consumer<Integer> c){ 
    System.out.println("In forEachLeave "+label);
    c.accept(label);
    }
}

record Node(NonEmptyTree left, NonEmptyTree right) implements NonEmptyTree{
  public void forEach(Consumer<Integer> c){ 
    left.forEach(c); right.forEach(c);
  }
}

The two main ways to turn a tree into a stream would be

var sb=Stream.<Integer>builder();
myTree.forEach(sb);
sb.build()

or

Stream.of(myTree).mapMulti(Tree::forEach)

However, both of them call forEach, thus both of them will visit all the tree (and call the prints for all the labels, in this example).

How do you implement a .stream() method in the Tree type so that it would not even visit the whole tree if it is not needed? (because of .anyMatch, for example)

Marco Servetto
  • 684
  • 1
  • 5
  • 14
  • I would have `Tree` extend `Iterable`. Then you could add a default `stream()` method to the interface that simply calls `StreamSupport.stream(spliterator(), false)`. Of course, that means you'd have to create an `Iterator` implementation for your trees. And you might want to override the `spliterator()` method to provide your own `Spliterator` implementation. – Slaw Jan 07 '23 at 04:22
  • Does this answer your question? [In Java, how do I efficiently and elegantly stream a tree node's descendants?](https://stackoverflow.com/questions/32749148/in-java-how-do-i-efficiently-and-elegantly-stream-a-tree-nodes-descendants) – Didier L Jan 08 '23 at 00:46
  • Also https://stackoverflow.com/q/26158082/525036 – Didier L Jan 08 '23 at 00:47

2 Answers2

1

Ok, I sorted it. I'm quite sure that what I'm doing is pretty standard with immutable trees (parent fields only make sense in mutable trees)

Here is my result, for reference for future programmers doing streams on immutable trees. The class TreeIterator<E> is the one really relevant to this ordeal. I could make nested classes to be able to make more stuff private, but as a code example I think it is more clear in this non nested form.

interface Tree<E> extends Iterable<E>{
  
  Tree<E> and(Tree<E> other);
  
  default Tree<E> left(){ return empty(); }
  
  default Tree<E> right(){ return empty(); }
  
  default E label(Supplier<E> orElse){ return orElse.get(); }

  @SuppressWarnings("unchecked")
  static <E> Tree<E> empty(){ return (Tree<E>)EmptyTree.empty; }
  
  static <E> Tree<E> leaf(E label){ return new Leaf<E>(label); }

  default Stream<E> stream(){ return StreamSupport.stream(spliterator(), false); }
}

final class EmptyTree<E> implements Tree<E>{
  public Tree<E> and(Tree<E> other){ return other; }
  
  private EmptyTree(){} //Singleton pattern: only one EmptyTree can exists
  static final Tree<?> empty = new EmptyTree<>();
  
  public Iterator<E> iterator(){ return List.<E>of().iterator(); }

  public String toString(){ return "<EMPTY>"; }
}

interface NonEmptyTree<E> extends Tree<E>{
  
  Leaf<E> itAdvance(ArrayList<NonEmptyTree<E>> stack);
  
  default Tree<E> and(Tree<E> other){
    if (!(other instanceof NonEmptyTree<E> net)){ return this; }
    return new Node<E>(this, net);
  }
}
record Leaf<E>(E label) implements NonEmptyTree<E>{
  
  public E label(Supplier<E> orElse){ return label; }
  
  public Leaf<E> itAdvance(ArrayList<NonEmptyTree<E>> stack){ return this; }
  
  public Iterator<E> iterator(){ return List.<E>of(label).iterator(); }

  public String toString(){ return label+""; }
}

record Node<E>(NonEmptyTree<E> left, NonEmptyTree<E> right) implements NonEmptyTree<E>{

  public Node{ assert left!=null && right!=null; }//null is not a valid tree
  
  public Leaf<E> itAdvance(ArrayList<NonEmptyTree<E>> stack){
    stack.add(right);
    return left.itAdvance(stack);
    }

  public Iterator<E> iterator(){ return new TreeIterator<E>(this); }
  
  public String toString(){ return "("+left+", "+right+")"; }
  
}
class TreeIterator<E> implements Iterator<E>{
  
  private final ArrayList<NonEmptyTree<E>> stack = new ArrayList<>(32);
  
  public boolean hasNext(){ return !stack.isEmpty(); }
    
  public TreeIterator(Node<E> n){ stack.add(n); }
  
  public E next(){
    if(stack.isEmpty()){ throw new NoSuchElementException(); }
    var last=stack.remove(stack.size()-1);
    return last.itAdvance(stack).label();
  }
}
Marco Servetto
  • 684
  • 1
  • 5
  • 14
  • 1
    It’s strongly recommended to implement `Spliterator` directly instead of wrapping an `Iterator` when creating streams. But even when implementing `Iterator`, it’s worth overriding [`forEachRemaining`](https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html#forEachRemaining-java.util.function.Consumer-) (both kinds of iterator have this method), to implement a straight-forward recursive traversal w/o data structure on the heap. All non-lazy operations may benefit from this. If you prefer simplicity, use the solution [discussed here](https://stackoverflow.com/q/32749148/2711488). – Holger Jan 27 '23 at 08:19
0

Looking at record definition Leave(int label) implements NonEmptyTree, I have two questions:

  1. Did you mean "leaf"?
  2. A tree consists of nodes (either a leaf or an internal node), but a node or leaf do not implement a tree, i.e., they are not are specific type of a tree. Are you sure about your node/leaf/tree implementation?

I would recommend a simple implementation like this one here: https://www.baeldung.com/java-binary-tree

When it comes to stream, you have two options:

  1. Implement your own Stream-enabled class (see discussion here)
  2. Provide a method that returns a (specific) stream, e.g. filtered.

Keep in mind that there are many different trees out there, e.g. red–black tree, n-ary tree, AVL Tree, B-Tree ...

Roland J.
  • 76
  • 4
  • Hi, Yes I meant Leaf of course (English is not my first language). On the other side, when it comes to immutable data structures, usually Leafs and Nodes are kinds of trees. The link you pointed at is about mutable trees. The comment of Slaw before was quite helpful. Still I'm struggling to make the method Node.iterator(), you need to keep the stack of uphill nodes and is giving me some headeaches... – Marco Servetto Jan 07 '23 at 05:27