summaryrefslogtreecommitdiff
path: root/src/main/java/bjc/data/Tree.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/bjc/data/Tree.java')
-rw-r--r--src/main/java/bjc/data/Tree.java606
1 files changed, 223 insertions, 383 deletions
diff --git a/src/main/java/bjc/data/Tree.java b/src/main/java/bjc/data/Tree.java
index d0cfe3d..3e16e02 100644
--- a/src/main/java/bjc/data/Tree.java
+++ b/src/main/java/bjc/data/Tree.java
@@ -6,441 +6,281 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
-import bjc.funcdata.FunctionalList;
-import bjc.funcdata.IList;
+import bjc.funcdata.ListEx;
import bjc.funcdata.bst.TreeLinearizationMethod;
/**
- * A node in a homogeneous tree.
+ * A node in a homogeneous tree with a unlimited amount of children.
*
* @author ben
*
* @param <ContainedType>
- * The type contained in the tree.
+ * The type of data contained in the tree nodes.
+ *
*/
-public class Tree<ContainedType> implements ITree<ContainedType> {
- /* The data/label for this node. */
- private ContainedType data;
-
- /* The children of this node. */
- private IList<ITree<ContainedType>> children;
-
- /* Whether this node has children. */
- /*
- * @NOTE Why have both this boolean and childCount? Why not just do a childCount
- * == 0 whenever you'd check hasChildren?
- *
- * - Because hasChildren is set once and not reset, and really what it
- * indicates is that children has been allocated.
+public interface Tree<ContainedType> {
+ /**
+ * Append a child to this node.
+ *
+ * @param child
+ * The child to append to this node.
*/
- private boolean hasChildren;
- /* The number of children this node has. */
- private int childCount = 0;
-
- /* The ID of this node. */
- private int ID;
- /* The next ID to assign to a node. */
- private static int nextID = 0;
+ void addChild(Tree<ContainedType> child);
/**
- * Create a new leaf node in a tree.
+ * Append a child to this node.
+ *
+ * @param child
+ * The child to append to this node.
*/
- public Tree() {
- this(null);
- }
+ void addChild(ContainedType child);
/**
- * Create a new leaf node in a tree.
+ * Prepend a child to this node.
*
- * @param leaf
- * The data to store as a leaf node.
+ * @param child
+ * The child to prepend to this node.
*/
- public Tree(final ContainedType leaf) {
- data = leaf;
-
- hasChildren = false;
-
- ID = nextID++;
- }
+ void prependChild(Tree<ContainedType> child);
/**
- * Create a new tree node with the specified children.
+ * Collapse a tree into a single version.
+ *
+ * @param <NewType>
+ * The intermediate type being folded.
+ *
+ * @param <ReturnedType>
+ * The type that is the end result.
+ *
+ * @param leafTransform
+ * The function to use to convert leaf values.
+ *
+ * @param nodeCollapser
+ * The function to use to convert internal nodes and
+ * their children.
*
- * @param leaf
- * The data to hold in this node.
+ * @param resultTransformer
+ * The function to use to convert a state to the
+ * returned version.
*
- * @param childrn
- * A list of children for this node.
+ * @return The final transformed state.
*/
- public Tree(final ContainedType leaf, final IList<ITree<ContainedType>> childrn) {
- this(leaf);
+ <NewType, ReturnedType> ReturnedType collapse(
+ Function<ContainedType, NewType> leafTransform,
+ BiFunction<ContainedType, ListEx<NewType>, NewType> nodeCollapser,
+ Function<NewType, ReturnedType> resultTransformer);
- hasChildren = true;
-
- childCount = childrn.getSize();
-
- children = childrn;
- }
+ /**
+ * Execute a given action for each of this tree's children.
+ *
+ * @param action
+ * The action to execute for each child.
+ */
+ void doForChildren(Consumer<Tree<ContainedType>> action);
/**
- * Create a new tree node with the specified children.
+ * Expand the nodes of a tree into trees, and then merge the contents of those
+ * trees into a single tree.
*
- * @param leaf
- * The data to hold in this node.
+ * @param mapper
+ * The function to use to map values into trees.
*
- * @param childrn
- * A list of children for this node.
+ * @return A tree, with some nodes expanded into trees.
*/
- @SafeVarargs
- public Tree(final ContainedType leaf, final ITree<ContainedType>... childrn) {
- this(leaf);
-
- hasChildren = true;
-
- childCount = 0;
-
- children = new FunctionalList<>();
-
- for (final ITree<ContainedType> child : childrn) {
- children.add(child);
-
- childCount++;
- }
- }
-
- @Override
- public void addChild(final ContainedType child) {
- addChild(new Tree<>(child));
- }
-
- @Override
- public void addChild(final ITree<ContainedType> child) {
- if (hasChildren == false) {
- hasChildren = true;
-
- children = new FunctionalList<>();
- }
-
- childCount++;
-
- children.add(child);
- }
-
- @Override
- public void prependChild(final ITree<ContainedType> child) {
- if (hasChildren == false) {
- hasChildren = true;
-
- children = new FunctionalList<>();
- }
-
- childCount++;
-
- children.prepend(child);
- }
-
- @Override
- public void doForChildren(final Consumer<ITree<ContainedType>> action) {
- if (childCount > 0) children.forEach(action);
- }
-
- @Override
- public int getChildrenCount() {
- return childCount;
- }
-
- @Override
- public int revFind(final Predicate<ITree<ContainedType>> childPred) {
- if (childCount == 0) return -1;
-
- for (int i = childCount - 1; i >= 0; i--) {
- if (childPred.test(getChild(i))) return i;
- }
-
- return -1;
- }
-
- @Override
- public void traverse(final TreeLinearizationMethod linearizationMethod,
- final Consumer<ContainedType> action) {
- if (hasChildren) {
- switch (linearizationMethod) {
- case INORDER:
- if (childCount != 2) {
- final String msg = "Can only do in-order traversal for binary trees.";
-
- throw new IllegalArgumentException(msg);
- }
-
- children.getByIndex(0).traverse(linearizationMethod, action);
+ default Tree<ContainedType>
+ flatMapTree(final Function<ContainedType, Tree<ContainedType>> mapper) {
+ return topDownTransform(dat -> TopDownTransformResult.PUSHDOWN, node -> {
+ if (node.getChildrenCount() > 0) {
+ final Tree<ContainedType> parent = node.transformHead(mapper);
- action.accept(data);
-
- children.getByIndex(1).traverse(linearizationMethod, action);
- break;
- case POSTORDER:
- children.forEach(child -> child.traverse(linearizationMethod, action));
-
- action.accept(data);
- break;
- case PREORDER:
- action.accept(data);
-
- children.forEach(child -> child.traverse(linearizationMethod, action));
- break;
- default:
- break;
+ node.doForChildren(parent::addChild);
+ return parent;
}
- } else {
- action.accept(data);
- }
- }
-
- @Override
- public <NewType, ReturnedType> ReturnedType collapse(
- final Function<ContainedType, NewType> leafTransform,
- final BiFunction<ContainedType, IList<NewType>, NewType> nodeCollapser,
- final Function<NewType, ReturnedType> resultTransformer) {
- return resultTransformer.apply(internalCollapse(leafTransform, nodeCollapser));
- }
-
- @Override
- public ITree<ContainedType>
- flatMapTree(final Function<ContainedType, ITree<ContainedType>> mapper) {
- if (hasChildren) {
- final ITree<ContainedType> flatMappedData = mapper.apply(data);
-
- final IList<ITree<ContainedType>> mappedChildren
- = children.map(child -> child.flatMapTree(mapper));
-
- mappedChildren.forEach(flatMappedData::addChild);
-
- return flatMappedData;
- }
- return mapper.apply(data);
+ return node.transformHead(mapper);
+ });
}
- /*
- * Do a collapse of this tree.
+ /**
+ * Get the specified child of this tree.
+ *
+ * @param childNo
+ * The number of the child to get.
+ *
+ * @return The specified child of this tree.
*/
-
- private <NewType> NewType internalCollapse(
- final Function<ContainedType, NewType> leafTransform,
- final BiFunction<ContainedType, IList<NewType>, NewType> nodeCollapser) {
- if (hasChildren) {
- final IList<NewType> collapsedChildren = children.map(child -> {
- final NewType collapsed = child.collapse(leafTransform, nodeCollapser,
- subTreeVal -> subTreeVal);
-
- return collapsed;
- });
-
- return nodeCollapser.apply(data, collapsedChildren);
- }
-
- return leafTransform.apply(data);
- }
-
- private void internalToString(final StringBuilder builder, final int indentLevel,
- final boolean initial) {
- if (!initial) {
- for (int i = 0; i < indentLevel; i++) builder.append(">\t");
- }
-
- builder.append("Node #");
- builder.append(ID);
- builder.append(": ");
- builder.append(data == null ? "(null)" : data.toString());
- builder.append("\n");
-
- if (hasChildren) {
- children.forEach(child -> {
- if (child instanceof Tree<?>) {
- final Tree<ContainedType> kid = (Tree<ContainedType>) child;
-
- kid.internalToString(builder, indentLevel + 1, false);
- } else {
- for (int i = 0; i < indentLevel + 1; i++) {
- builder.append(">\t");
- }
-
- builder.append("Unknown node of type ");
- builder.append(child.getClass().getName());
- builder.append("\n");
- }
- });
- }
+ default Tree<ContainedType> getChild(final int childNo) {
+ return transformChild(childNo, child -> child);
}
- @Override
- public <MappedType> ITree<MappedType> rebuildTree(
- final Function<ContainedType, MappedType> leafTransformer,
- final Function<ContainedType, MappedType> operatorTransformer) {
- if (hasChildren) {
- final IList<ITree<MappedType>> mappedChildren =
- children.map(child ->
- child.rebuildTree(leafTransformer, operatorTransformer));
-
- final MappedType mapData = operatorTransformer.apply(data);
- return new Tree<>(mapData, mappedChildren);
- }
-
- return new Tree<>(leafTransformer.apply(data));
- }
+ /**
+ * Get a count of the number of direct children this node has.
+ *
+ * @return The number of direct children this node has.
+ */
+ int getChildrenCount();
- @Override
- public void selectiveTransform(final Predicate<ContainedType> nodePicker,
- final UnaryOperator<ContainedType> transformer) {
- if (hasChildren) {
- children.forEach(child -> child.selectiveTransform(nodePicker, transformer));
- } else {
- data = transformer.apply(data);
- }
+ /**
+ * Get a count of the number of direct children this node has.
+ *
+ * @return The number of direct children this node has.
+ */
+ default int size() {
+ return getChildrenCount();
}
- @Override
- public ITree<ContainedType> topDownTransform(
- final Function<ContainedType, TopDownTransformResult> transformPicker,
- final UnaryOperator<ITree<ContainedType>> transformer) {
- final TopDownTransformResult transformResult = transformPicker.apply(data);
-
- switch (transformResult) {
- case PASSTHROUGH:
- ITree<ContainedType> result = new Tree<>(data);
-
- if (hasChildren) {
- children.forEach(child -> {
- final ITree<ContainedType> kid
- = child.topDownTransform(transformPicker, transformer);
-
- result.addChild(kid);
- });
- }
-
- return result;
- case SKIP:
- return this;
- case TRANSFORM:
- return transformer.apply(this);
- case RTRANSFORM:
- return transformer.apply(this).topDownTransform(transformPicker, transformer);
- case PUSHDOWN:
- result = new Tree<>(data);
-
- if (hasChildren) {
- children.forEach(child -> {
- final ITree<ContainedType> kid
- = child.topDownTransform(transformPicker, transformer);
-
- result.addChild(kid);
- });
- }
-
- return transformer.apply(result);
- case PULLUP:
- final ITree<ContainedType> intermediateResult = transformer.apply(this);
-
- result = new Tree<>(intermediateResult.getHead());
-
- intermediateResult.doForChildren(child -> {
- final ITree<ContainedType> kid
- = child.topDownTransform(transformPicker, transformer);
-
- result.addChild(kid);
- });
-
- return result;
- default:
- final String msg = String.format("Recieved unknown transform result type %s",
- transformResult);
-
- throw new IllegalArgumentException(msg);
- }
+ /**
+ * Get the data stored in this node.
+ *
+ * @return The data stored in this node.
+ */
+ default ContainedType getHead() {
+ return transformHead(head -> head);
}
- @Override
- public <TransformedType> TransformedType transformChild(final int childNo,
- final Function<ITree<ContainedType>, TransformedType> transformer) {
- if (childNo < 0 || childNo > childCount - 1) {
- final String msg = String.format("Child index #%d is invalid", childNo);
-
- throw new IllegalArgumentException(msg);
- }
-
- final ITree<ContainedType> selectedKid = children.getByIndex(childNo);
+ /**
+ * Rebuild the tree with the same structure, but different nodes.
+ *
+ * @param <MappedType>
+ * The type of the new tree.
+ *
+ * @param leafTransformer
+ * The function to use to transform leaf tokens.
+ *
+ * @param internalTransformer
+ * The function to use to transform internal tokens.
+ *
+ * @return The tree, with the nodes changed.
+ */
+ <MappedType> Tree<MappedType> rebuildTree(
+ Function<ContainedType, MappedType> leafTransformer,
+ Function<ContainedType, MappedType> internalTransformer);
- return transformer.apply(selectedKid);
- }
+ /**
+ * Transform some of the nodes in this tree.
+ *
+ * @param nodePicker
+ * The predicate to use to pick nodes to transform.
+ *
+ * @param transformer
+ * The function to use to transform picked nodes.
+ */
+ void selectiveTransform(Predicate<ContainedType> nodePicker,
+ UnaryOperator<ContainedType> transformer);
- @Override
- public ITree<ContainedType> getChild(final int childNo) {
- if (childNo < 0 || childNo > childCount - 1) {
- final String msg = String.format("Child index #%d is invalid", childNo);
+ /**
+ * Do a top-down transform of the tree.
+ *
+ * @param transformPicker
+ * The function to use to pick how to progress.
+ *
+ * @param transformer
+ * The function used to transform picked subtrees.
+ *
+ * @return The tree with the transform applied to picked subtrees.
+ */
+ Tree<ContainedType> topDownTransform(
+ Function<ContainedType, TopDownTransformResult> transformPicker,
+ UnaryOperator<Tree<ContainedType>> transformer);
- throw new IllegalArgumentException(msg);
- }
- return children.getByIndex(childNo);
- }
+ /**
+ * Transform one of this nodes children.
+ *
+ * @param <TransformedType>
+ * The type of the transformed value.
+ *
+ * @param childNo
+ * The number of the child to transform.
+ *
+ * @param transformer
+ * The function to use to transform the value.
+ *
+ * @return The transformed value.
+ *
+ * @throws IllegalArgumentException
+ * if the childNo is out of bounds (0 &lt;=
+ * childNo &lt;= childCount()).
+ */
+ <TransformedType> TransformedType transformChild(int childNo,
+ Function<Tree<ContainedType>, TransformedType> transformer);
- @Override
- public <TransformedType> TransformedType
- transformHead(final Function<ContainedType, TransformedType> transformer) {
- return transformer.apply(data);
- }
+ /**
+ * Transform the value that is the head of this node.
+ *
+ * @param <TransformedType>
+ * The type of the transformed value.
+ *
+ * @param transformer
+ * The function to use to transform the value.
+ *
+ * @return The transformed value.
+ */
+ <TransformedType> TransformedType
+ transformHead(Function<ContainedType, TransformedType> transformer);
- @Override
- public void setHead(ContainedType dat) {
- this.data = dat;
+ /**
+ * Transform the tree into a tree with a different type of token.
+ *
+ * @param <MappedType>
+ * The type of the new tree.
+ *
+ * @param transformer
+ * The function to use to transform tokens.
+ *
+ * @return A tree with the token types transformed.
+ */
+ default <MappedType> Tree<MappedType>
+ transformTree(final Function<ContainedType, MappedType> transformer) {
+ return rebuildTree(transformer, transformer);
}
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
-
- result = prime * result + childCount;
- result = prime * result + (children == null ? 0 : children.hashCode());
- result = prime * result + (data == null ? 0 : data.hashCode());
-
- return result;
- }
+ /**
+ * Perform an action on each part of the tree.
+ *
+ * @param linearizationMethod
+ * The way to traverse the tree.
+ *
+ * @param action
+ * The action to perform on each tree node.
+ */
+ void traverse(TreeLinearizationMethod linearizationMethod,
+ Consumer<ContainedType> action);
- @Override
- public String toString() {
- final StringBuilder builder = new StringBuilder();
+ /**
+ * Find the farthest to right child that satisfies the given predicate.
+ *
+ * @param childPred
+ * The predicate to satisfy.
+ *
+ * @return The index of the right-most child that satisfies the predicate, or -1
+ * if one doesn't exist.
+ */
+ int revFind(Predicate<Tree<ContainedType>> childPred);
- internalToString(builder, 1, true);
+ /**
+ * Check if this tree contains any nodes that satisfy the predicate.
+ *
+ * @param pred
+ * The predicate to look for.
+ *
+ * @return Whether or not any items satisfied the predicate.
+ */
+ default boolean containsMatching(Predicate<ContainedType> pred) {
+ Toggle<Boolean> tog = new OneWayToggle<>(false, true);
- /* Delete a trailing nl. */
- builder.deleteCharAt(builder.length() - 1);
+ traverse(TreeLinearizationMethod.POSTORDER, val -> {
+ if (pred.test(val)) tog.get();
+ });
- return builder.toString();
+ return tog.get();
}
- @Override
- public boolean equals(final Object obj) {
- if (this == obj) return true;
- if (obj == null) return false;
- if (!(obj instanceof Tree<?>)) return false;
-
- final Tree<?> other = (Tree<?>) obj;
-
- if (data == null) {
- if (other.data != null) return false;
- } else if (!data.equals(other.data)) {
- return false;
- }
-
- if (childCount != other.childCount) return false;
-
- if (children == null) {
- if (other.children != null) return false;
- } else if (!children.equals(other.children)) {
- return false;
- }
-
- return true;
- }
+ /**
+ * Set the head of the tree.
+ *
+ * @param dat
+ * The value to set as the head of the tree.
+ */
+ void setHead(ContainedType dat);
}