package org.codefx.libfx.collection.tree.stream; import java.util.Iterator; import java.util.Spliterator; import java.util.Spliterators; import java.util.stream.Stream; import java.util.stream.StreamSupport; import org.codefx.libfx.collection.tree.navigate.TreeNavigator; /** * Creates streams of nodes. * <p> * Unless otherwise noted all streams are {@link Spliterator#ORDERED ordered} and sequential. They are generated lazily, * i.e. the {@link TreeNavigator} is only used to find nodes which are known to be processed by the stream. This is * implies that short-circuiting operation (like {@link Stream#limit(long) limit}) will lead to the evaluation of less * nodes. * <p> * The streams are only defined on trees, i.e. connected, directed, acyclic graphs. Creating them on other graphs can * lead to unexpected behavior including infinite streams. */ public class TreeStreams { // #begin DFS // forward /** * Returns a stream which enumerates nodes in the (sub-)tree rooted in the specified root in the order of a <a * href="https://en.wikipedia.org/wiki/Depth-first_search">depth-first search</a>. * <p> * It is not necessary for the specified node to be the tree's actual root. It will be treated as the root of a * subtree and only this subtree will be streamed. * * @param <N> * the type of nodes contained in the tree * @param navigator * the navigator used to navigate the tree * @param root * the root node for the searched (sub-)tree * @return a stream of nodes */ public static <N> Stream<N> dfsFromRoot(TreeNavigator<N> navigator, N root) { TreePath<TreeNode<N>> initialPath = TreePathFactory.createWithSingleNode(root); TreeIterationStrategy<N> strategy = new DfsTreeIterationStrategy<N>(navigator, initialPath); return byStrategy(strategy); } /** * Returns a stream which enumerates nodes in a tree in the order of a <a * href="https://en.wikipedia.org/wiki/Depth-first_search">depth-first search</a> which starts in the specified * node. * <p> * While the search will start in the specified node it will eventually backtrack above it, i.e. the search is not * limited to the subtree rooted in the node. This is equivalent to starting a full depth-first search in the tree's * root but ignoring all encountered nodes until the specified start node is found. * * @param <N> * the type of nodes contained in the tree * @param navigator * the navigator used to navigate the tree * @param startNode * the node in which the search starts * @return a stream of nodes */ public static <N> Stream<N> dfsFromWithin(TreeNavigator<N> navigator, N startNode) { TreePath<TreeNode<N>> initialPath = TreePathFactory.createFromRootToNode(navigator, startNode); TreeIterationStrategy<N> strategy = new DfsTreeIterationStrategy<N>(navigator, initialPath); return byStrategy(strategy); } /** * Returns a stream which enumerates nodes in the (sub-)tree rooted in the specified root in the order of a <a * href="https://en.wikipedia.org/wiki/Depth-first_search">depth-first search</a> which starts in the specified * start node. * <p> * This is a combination of {@link #dfsFromRoot(TreeNavigator, Object) dfsFromRoot} and * {@link #dfsFromWithin(TreeNavigator, Object) dfsFromWithin}. Only the (sub-)tree rooted in the specified root is * searched (i.e. the search will never backtrack above it) but the root actually startes in the specified start * node. * * @param <N> * the type of nodes contained in the tree * @param navigator * the navigator used to navigate the tree * @param root * the root node for the searched (sub-)tree * @param startNode * the node in which the search starts * @return a stream of nodes */ public static <N> Stream<N> dfsFromWithin(TreeNavigator<N> navigator, N root, N startNode) { TreePath<TreeNode<N>> initialPath = TreePathFactory.createFromNodeToDescendant(navigator, root, startNode); TreeIterationStrategy<N> strategy = new DfsTreeIterationStrategy<N>(navigator, initialPath); return byStrategy(strategy); } // backward /** * Returns a stream which enumerates nodes in a tree in the order of a <em>backwards</em> <a * href="https://en.wikipedia.org/wiki/Depth-first_search">depth-first search</a> which starts in the specified * node. * <p> * The stream enumerates the nodes which would be encountered by starting a depth-first search in the tree's root * and stopping at the specified node, but in reverse order. * * @param <N> * the type of nodes contained in the tree * @param navigator * the navigator used to navigate the tree * @param startNode * the node in which the search starts * @return a stream of nodes */ public static <N> Stream<N> backwardDfs(TreeNavigator<N> navigator, N startNode) { TreePath<TreeNode<N>> initialPath = TreePathFactory.createFromRootToNode(navigator, startNode); TreeIterationStrategy<N> strategy = new DfsTreeIterationStrategy<N>(navigator, initialPath); return byStrategy(strategy); } /** * Returns a stream which enumerates nodes in the (sub-)tree rooted in the specified root in the order of a * <em>backwards</em> <a href="https://en.wikipedia.org/wiki/Depth-first_search">depth-first search</a> which starts * in the specified node. * <p> * The stream enumerates the nodes which would be encountered by starting a depth-first search in the specified root * and stopping at the specified node, but in reverse order. * * @param <N> * the type of nodes contained in the tree * @param navigator * the navigator used to navigate the tree * @param root * the root node for the searched (sub-)tree * @param startNode * the node in which the search starts * @return a stream of nodes */ public static <N> Stream<N> backwardDfsToRoot(TreeNavigator<N> navigator, N root, N startNode) { TreePath<TreeNode<N>> initialPath = TreePathFactory.createFromNodeToDescendant(navigator, root, startNode); TreeIterationStrategy<N> strategy = new DfsTreeIterationStrategy<N>(navigator, initialPath); return byStrategy(strategy); } // #end DFS /** * Returns a stream which enumerates a tree's nodes according to the specified {@link TreeIterationStrategy}. * * @param <N> * the type of nodes contained in the tree * @param strategy * the strategy used to enumerate the tree's nodes * @return a stream of nodes */ public static <N> Stream<N> byStrategy(TreeIterationStrategy<N> strategy) { return byStrategy(strategy, Spliterator.NONNULL | Spliterator.ORDERED, false); } /** * Returns a stream which enumerates a tree's nodes according to the specified {@link TreeIterationStrategy} and * stream characteristics. * * @param <N> * the type of nodes contained in the tree * @param strategy * the strategy used to enumerate the tree's nodes * @param characteristics * the characteristics of the {@link Spliterator} used to create the stream * @param parallel * if true then the returned stream is a parallel stream; if false the returned stream is a sequential * stream. * @return a stream of nodes */ public static <N> Stream<N> byStrategy(TreeIterationStrategy<N> strategy, int characteristics, boolean parallel) { Iterator<N> iterator = new TreeIterator<N>(strategy); Spliterator<N> spliterator = Spliterators.spliteratorUnknownSize(iterator, characteristics); return StreamSupport.stream(spliterator, parallel); } }