Tree 使用流API对树状结构的惰性BFS遍历

Tree 使用流API对树状结构的惰性BFS遍历,tree,java-8,java-stream,lazy-evaluation,breadth-first-search,Tree,Java 8,Java Stream,Lazy Evaluation,Breadth First Search,假设我想使用流API遍历树状结构的一些节点,类似的问题:。首先想到的是: 抽象类节点{ 儿童抽象集合; 最终流{ 返回Stream.concatStream.this,this.getChildren.Stream.flatMapNode::Stream; } } 上述流实现具有以下特性: 它几乎是惰性的,从某种意义上说,在流创建过程中,只有根节点的直接子节点将被检索,其他内部节点的子节点将根据需要被查询。 它显示了遍历顺序。 现在考虑我有一个大的层次结构,GET子操作非常昂贵,并且我在寻找与谓

假设我想使用流API遍历树状结构的一些节点,类似的问题:。首先想到的是:

抽象类节点{ 儿童抽象集合; 最终流{ 返回Stream.concatStream.this,this.getChildren.Stream.flatMapNode::Stream; } } 上述流实现具有以下特性:

它几乎是惰性的,从某种意义上说,在流创建过程中,只有根节点的直接子节点将被检索,其他内部节点的子节点将根据需要被查询。 它显示了遍历顺序。

现在考虑我有一个大的层次结构,GET子操作非常昂贵,并且我在寻找与谓词匹配的任何节点:

最终节点树=。。。; 最终谓词p=。。。; tree.stream.filterp.findAny; 如何使我的流实现完全懒惰?如果节点本身已经匹配谓词,我不希望查询节点的子节点。我正在寻找Stream.concat的懒惰版本,签名为Stream a,Supplier b。 如何使用流API实现遍历顺序? 您可以使用拆分器库和低级流支持原语。然后可以在节点上提供迭代器,该迭代器只逐个使用节点

return StreamSupport.stream( Spliterators.spliteratorUnknownSize( new Iterator<Node>()
{
    @Override
    public boolean hasNext()
    {
        // to implement
        return ...;
    }

    @Override
    public ContentVersion next()
    {
        // to implement
        return ...;
    }
}, 0 ), false );

有点难看,但这应该适用于Java 8:

public static <N> Stream<N> breadthFirst(N start, Function<? super N, Stream<N>> getChildren) {
    final LinkedList<Stream<N>> generations = new LinkedList<>();
    generations.add(Stream.of(start));
    final Iterator<Stream<N>> genIterator = createIterator(generations::remove, () -> !generations.isEmpty());
    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(genIterator, Spliterator.ORDERED), false)
            .flatMap(Function.identity())
            .distinct() // avoids loops
            .peek(n -> generations.add(getChildren.apply(n)));
}

public static <E> Iterator<E> createIterator(Supplier<E> supplier, BooleanSupplier hasNext) {
    return new Iterator<E>() {
        @Override
        public boolean hasNext() {
            return hasNext.getAsBoolean();
        }
        @Override
        public E next() {
            return supplier.get();
        }
    };
}

这里的想法是,您需要保留对后续代的引用,因此我们创建一个列表以在处理时保存它们。使用Java 9,您可以用Stream.generategenerations::poll.takeWhileObjects::nonNull替换自定义迭代器代码。

不幸的是,我只能回答您的第一个问题。再说一次,flatMap是你的朋友。它使我们能够创建一种稍微不同的concat方法,该方法接受流供应商,而不仅仅是流:

abstract class Node {
    abstract Collection<Node> getChildren();

    Stream<Node> lazyTraverse() {
        return Node.concat(() -> Stream.of(this),
                           () -> getChildren().stream().flatMap(Node::lazyTraverse));
    }

    static Stream<Node> concat(Supplier<Stream<Node>> a, Supplier<Stream<Node>> b) {
        return Stream.of(a, b).flatMap(Supplier::get);
    }
}
一个更好的解决方案是,如果您可以用某种机制代替getChildren,该机制返回一个惰性流,并且可以直接用于原始的树遍历算法中。懒惰的流比缓慢的获取者更合适

关于你的第二个问题:


我不知道是否有BFS遍历算法以优雅的方式使用流API,但我倾向于说“不”,因为BFS通常需要额外的内存来存储所有已访问但尚未遍历的节点。

您是否尝试过实现拆分器?@8472这就是我要做的。只是在寻找一个更简单的解决方案。简而言之,实现是可行的,影响懒惰的不仅仅是concat,而是flatMap的当前实现。您必须仔细考虑是否要修复JRE损坏的部分。如果您想继续,请查看…