
org.codefx.libfx.collection.tree.stream.BackwardsDfsTreeIterationStrategy Maven / Gradle / Ivy
Show all versions of LibFX Show documentation
package org.codefx.libfx.collection.tree.stream;
import java.util.Objects;
import java.util.Optional;
import org.codefx.libfx.collection.tree.navigate.TreeNavigator;
/**
* A {@link TreeIterationStrategy} which iterates a tree's nodes with a backwards depth-first search.
*
* This means that given an initial path from the root {@code R} to some node {@code N} , this strategy will start with
* {@code N} and move backwards visiting parents, left siblings and their children (from right to left) until it reaches
* {@code R}. This will enumerate the same nodes as a depth-first search which started in {@code R} and ended in
* {@code N} but in reverse order (containing both {@code N} and {@code R}).
*
* This implementation is only guaranteed to work on trees, i.e. a connected, directed, acyclic graph. Using it on other
* graphs can lead to unexpected behavior including infinite loops.
*
* @param
* the type of elements contained in the tree
*/
final class BackwardsDfsTreeIterationStrategy implements TreeIterationStrategy {
// #begin FIELDS
private final TreeNavigator navigator;
/**
* The path from the root to the last node returned by {@link #goToNextNode()}
*
* The path will also contain at least one node before the first call to {@link #goToNextNode()}. These are the
* nodes specified during construction and the one at the end of the path must be returned by the first call to
* {@code goToNextNode()}. Otherwise the end of the path would never be returned by the strategy. Whether
* {@code goToNextNode()} must return this node or find a new one is indicated by {@link #beforeFirst}.
*
* If the path is empty, no more nodes will be returned.
*/
private final TreePath> path;
/**
* Indicates whether {@link #goToNextNode()} was not already called at least once.
*/
private boolean beforeFirst;
// #end FIELDS
// #begin CONSTRUCTION
/**
* Creates a new backwards depth-first search strategy starting with the specified initial path.
*
* The iteration will begin with the node at the end of the initial path. It will stop when it successfully
* backtracked to the first node in that path, i.e. the (sub-)tree's root. (Remember, this does not happen on a
* straight route from nodes to their parents but via backwards depth-first search.)
*
* The specified path must correspond to the navigator's view on the tree, i.e. each element in the path must be the
* parent (in the tree) of the next one.
*
* See {@link TreePathFactory} for easy ways to create an initial path.
*
* @param navigator
* the navigator used to navigate the tree
* @param initialPath
* the initial path from the root of the (sub-)tree iterated by this strategy; must contain at least one
* element; the last element in the path will be returned by the first call to {@link #goToNextNode()}
*/
public BackwardsDfsTreeIterationStrategy(TreeNavigator navigator, TreePath> initialPath) {
Objects.requireNonNull(navigator, "The argument 'navigator' must not be null.");
Objects.requireNonNull(initialPath, "The argument 'initialPath' must not be null.");
if (initialPath.isEmpty())
throw new IllegalArgumentException("The 'initialPath' must not be empty.");
this.navigator = navigator;
this.path = initialPath;
this.beforeFirst = true;
}
// #end CONSTRUCTION
@Override
public Optional goToNextNode() {
// if the path is empty, iteration ended and no more nodes will be returned
if (path.isEmpty())
return Optional.empty();
// if this is the first call, do not move to the next node
if (beforeFirst)
beforeFirst = false;
else
movePathEndToNextNode();
return path.getEnd().map(TreeNode::getElement);
}
private void movePathEndToNextNode() {
Optional> leftSibling = goToParentAndGetLeftSibling();
if (leftSibling.isPresent())
goToRightmostAncestor(leftSibling.get());
}
private Optional> goToParentAndGetLeftSibling() {
TreeNode node = path.removeEnd();
Optional> parent = path.getEnd();
if (parent.isPresent()) {
int leftSiblingIndex = node.getChildIndex().getAsInt() - 1;
return navigator
.getChild(parent.get().getElement(), leftSiblingIndex)
.map(ls -> SimpleTreeNode.innerNode(ls, leftSiblingIndex));
} else
return Optional.empty();
}
private void goToRightmostAncestor(TreeNode leftSibling) {
Optional> rightmostChild = Optional.of(leftSibling);
while (rightmostChild.isPresent()) {
path.append(rightmostChild.get());
int rightmostChildIndex = navigator.getChildrenCount(rightmostChild.get().getElement()) - 1;
rightmostChild = navigator
.getChild(rightmostChild.get().getElement(), rightmostChildIndex)
.map(child -> SimpleTreeNode.innerNode(child, rightmostChildIndex));
}
}
}