Tree 使用流API对树状结构的惰性BFS遍历
假设我想使用流API遍历树状结构的一些节点,类似的问题:。首先想到的是: 抽象类节点{ 儿童抽象集合; 最终流{ 返回Stream.concatStream.this,this.getChildren.Stream.flatMapNode::Stream; } } 上述流实现具有以下特性: 它几乎是惰性的,从某种意义上说,在流创建过程中,只有根节点的直接子节点将被检索,其他内部节点的子节点将根据需要被查询。 它显示了遍历顺序。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子操作非常昂贵,并且我在寻找与谓
现在考虑我有一个大的层次结构,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损坏的部分。如果您想继续,请查看…