Lazy BFS使用Stream API遍历树状结构

时间:2016-05-27 13:57:27

标签: tree java-8 java-stream lazy-evaluation breadth-first-search

考虑我想使用Stream API遍历树状结构的某些节点(类似问题:[1][2][3])。想到的第一个实现将是:

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

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

上述stream()实施具有以下功能:

  • 它几乎是懒惰的&#34;,从某种意义上说,在创建流时只会检索根节点的直接子节点(必要时将查询其他内部节点的子节点)。
  • 它展示了DFS遍历顺序。

现在考虑我有一个很大的层次结构,getChildren()操作成本很高,而且我正在寻找匹配某个谓词的任何 Node

final Node tree = ...;
final Predicate<Node> p = ...;
tree.stream().filter(p).findAny();
  1. 如何让stream()实施&#34;完全懒惰&#34;?如果节点本身已经与谓词匹配,我不希望查询节点的子节点。我正在寻找一个Stream.concat()的懒惰版本,签名为(Stream<T> a, Supplier<Stream<T>> b)
  2. 如何实现BFS遍历顺序(使用Stream API)?

3 个答案:

答案 0 :(得分:2)

很遗憾,我只能回答您的第一个问题。同样,flatMap是您的朋友在这里。它使我们能够创建略有不同的concat方法,该方法接受流Supplier而不是仅流:

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,而该机制返回一个惰性Stream<Node>,并且可以直接在原始树遍历算法中使用。惰性流比慢速吸收器更合适。

关于第二个问题的一些单词:

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

答案 1 :(得分:1)

您可以使用rdf:Statement库和低级流支持原语。然后,您可以在节点上提供Spliterators,它只会逐个使用节点。

Iterator

答案 2 :(得分:1)

有点难看,但这应该适用于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.generate(generations::poll).takeWhile(Objects::nonNull)替换自定义迭代器代码。