在树结构上创建深度流

时间:2016-04-15 09:26:37

标签: java tree java-8 java-stream

所以,我有一个基本的树结构,由树节点链接到其他树节点,父节点和子节点引用。我想创建一个返回Stream的方法,该流从叶节点流到根节点或从根流到叶。我已经实现了这个,但我正在寻找一个创建最少量对象的解决方案。优选没有。这是我的代码:

  public class TreeNode<TNode> {

      private TNode iValue;

      private TreeNode<TNode> iParentNode = null;

      private List<TreeNode<TNode>> iChildren = new ArrayList<>();

      public TreeNode(TNode value) {
          this(value, null);
      }

      private TreeNode(TNode value, TreeNode<TNode> parentNode) {
          iValue = value;
          iParentNode = parentNode;
      }

      public Stream<TreeNode<TNode>> streamFromLeaf() {
          return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new LeafFirstIterator(this), Spliterator.SIZED),
            false);
      }

      public Stream<TreeNode<TNode>> streamFromRoot() {
          return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new RootFirstIterator(this), Spliterator.SIZED),
            false);
      }

      public TNode getValue() {
          return iValue;
      }

      public TreeNode<TNode> getParent() {
          return iParentNode;
      }

      public TreeNode<TNode> addChild(TNode childValue) {
          TreeNode<TNode> childNode = new TreeNode<TNode>(childValue, iNodeNameFunction, this);
          iChildren.add(childNode);
          return childNode;
      }

      public boolean isLeaf() {
          return iChildren.size() == 0;
      }

      public boolean isRoot() {
          return iParentNode == null;
      }

      public List<TreeNode<TNode>> getChildren() {
          return iChildren;
      }

      class LeafFirstIterator implements Iterator<TreeNode<TNode>> {

          private TreeNode<TNode> iNextNode;

          LeafFirstIterator(TreeNode<TNode> leafNode) {
              iNextNode = leafNode;
          }

          @Override
          public boolean hasNext() {
              return iNextNode != null;
          }

          @Override
          public TreeNode<TNode> next() {
              TreeNode<TNode> current = iNextNode;
              iNextNode = current.getParent();
              return current;
          }

      }

      class RootFirstIterator implements Iterator<TreeNode<TNode>> {

          private List<TreeNode<TNode>> iNodes = new ArrayList<>();

          private int iNextIndex;

          RootFirstIterator(TreeNode<TNode> leafNode) {
              TreeNode<TNode> currentNode = leafNode;
              while (currentNode != null) {
                  iNodes.add(currentNode);
                  currentNode = currentNode.getParent();
              }
              iNextIndex = iNodes.size() - 1;
           }

           @Override
           public boolean hasNext() {
               return iNextIndex >= 0;
           }

           @Override
           public TreeNode<TNode> next() {
               return iNodes.get(iNextIndex--);
           }

       }
   }

这很好但我的“问题”是,为每个流调用创建了很多对象。

  • StreamSupport.stream创建新的ReferencePipeline
  • Spliterators.spliteratorUnknownSize创建新的IteratorSpliterator
  • 我创建自己的Iterator实现以传递给Spliterator
  • RootFirstIterator创建新的ArrayList

流将被大量使用,所以我想尽可能避免创建对象。在没有流的情况下迭代树结构是一件简单的事情。现在我只使用流的map方法。我可以有一个方法,它接受一个Consumer,迭代深度并为每个节点调用消费者,并且不会创建任何对象,但是我会丢失所有流功能。这看起来像这样:

public void iterateUp(Consumer<TreeNode<TNode>> consumer) {
    doIterateUp(this, consumer);
}

public static <T> void doIterateUp(TreeNode<T> node, Consumer<TreeNode<T>> consumer) {
    if (node == null)
        return;
    consumer.accept(node);
    doIterateUp(node.getParent(), consumer);
}

从根向下迭代就是这么简单。

有关于此的任何想法吗?我是以错误的方式来做这件事的吗? TreeNode应该实现还是扩展一些接口/类?如果有什么不清楚,请告诉我。

谢谢!

2 个答案:

答案 0 :(得分:1)

不要使用流。使用您的替代方案,其名称为visitor pattern

Streams并不总是正确的方法,这是使用访问者模式的经典案例。

如果您必须拥有一个流,请让您的消费者收集List中的节点然后对其进行流式传输,或者将消费者连接到Iterator的next()方法(使用一些代码使其行为正常)并使用StreamSupport将其转换为流。

答案 1 :(得分:1)

我不会分享您的性能问题,但是,您的代码可以简化,这可能会解决您的一些担忧,因为这会产生副作用。

你犯了一个常见错误,就是从Iterator实现开始,很可能是因为Iterator接口很老而且众所周知。但实现它很麻烦,如果你直接实现Iterator,你不需要在Spliterator中包装Spliterator,这只是一个很好的副作用:

public Stream<TreeNode<TNode>> streamFromLeaf() {
    return StreamSupport.stream(new LeafFirstSpliterator<>(this), false);
}
static class LeafFirstSpliterator<TNode>
extends Spliterators.AbstractSpliterator<TreeNode<TNode>> {
    private TreeNode<TNode> iNextNode;
    LeafFirstSpliterator(TreeNode<TNode> leafNode) {
        super(100, ORDERED|NONNULL);
        iNextNode = leafNode;
    }
    public boolean tryAdvance(Consumer<? super TreeNode<TNode>> action) {
        if(iNextNode==null) return false;
        action.accept(iNextNode);
        iNextNode=iNextNode.getParent();
        return true;
    }
}

对我来说,Spliterator实现看起来更清晰,至少它没什么可担心的,它可能允许更快的流遍历。您可以考虑覆盖forEachRemaining方法以及直接实现。

对于从root到leaf的流,临时存储似乎是不可避免的,但如果是,不要浪费时间进行低级编码,只需使用存储的内置流功能:

public Stream<TreeNode<TNode>> streamFromRoot() {
    ArrayDeque<TreeNode<TNode>> deque = new ArrayDeque<>();
    for(TreeNode<TNode> n = this; n != null; n = n.getParent())
        deque.addFirst(n);
    return deque.stream();
}