【问题标题】:Create depth stream on Tree structure在树结构上创建深度流
【发布时间】:2016-04-15 09:26:36
【问题描述】:

所以,我有一个由树节点组成的基本树结构,这些树节点链接到具有父和子引用的其他树节点。我想创建一个方法,该方法返回一个从叶节点流向根节点或从根流向叶的 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

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

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 应该实现或扩展一些接口/类吗?让我知道是否有任何不清楚的地方。

谢谢!

【问题讨论】:

    标签: java tree java-8 java-stream


    【解决方案1】:

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

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

    如果你绝对必须有一个流,让你的消费者要么收集一个 List 中的节点然后流那个,或者将消费者连接到迭代器的 next() 方法(使用一些代码使其正常运行)和使用 StreamSupport 将其转换为流。

    【讨论】:

    • 这不是必须的,但会非常好。我的另一个想法是将过滤谓词、映射函数和消费者传递给迭代方法,我将获得部分流式函数,并且不会创建任何对象。但这对我来说似乎有点难看。将节点收集到一个列表并进行流式传输,我认为这与我的解决方案非常相似的对象创建方式,实际上可能会更好一些。
    【解决方案2】:

    我不同意您对性能的担忧,但是,您的代码仍有简化的空间,这可能会解决您的一些担忧作为副作用。

    您犯了从Iterator 实现开始的常见错误,很可能是因为Iterator 接口陈旧且广为人知。但是实现它很麻烦,而且如果你只是直接实现Spliterator,就不需要将Iterator 包装在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 方法,因为它很容易实现。

    对于从根到叶的流,临时存储似乎是不可避免的,但如果是的话,根本不要在低级编码上浪费时间,只需使用存储内置的流能力:

    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();
    }
    

    【讨论】:

    • 谢谢!好的指点。正如您所说,由于它更熟悉,因此倾向于诉诸迭代器。 Spliterator 方法看起来确实更干净。将尝试一下并更深入地了解新的 java 8 内容。关于性能。该代码对延迟至关重要,我们尽量不要过多地驱动 GC 以避免延迟异常值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多