
fj.data.TreeZipper Maven / Gradle / Ivy
package fj.data;
import fj.*;
import fj.function.Booleans;
import java.util.Iterator;
import static fj.Equal.p3Equal;
import static fj.Equal.p4Equal;
import static fj.Equal.streamEqual;
import static fj.Equal.treeEqual;
import static fj.Function.compose;
import static fj.Function.curry;
import static fj.Function.flip;
import static fj.Function.uncurryF2;
import static fj.Show.p3Show;
import static fj.Show.p4Show;
import static fj.Show.streamShow;
import static fj.Show.treeShow;
import static fj.data.Option.none;
import static fj.data.Option.some;
import static fj.data.Stream.nil;
import static fj.data.Stream.unfold;
import static fj.data.Tree.node;
import static fj.data.Tree.unfoldTree;
/**
* Provides a zipper structure for rose trees, which is a Tree supplied with a location within that tree.
* Provides navigation, insertion, deletion, and memorization of visited locations within a tree.
*/
public final class TreeZipper implements Iterable> {
/**
* Returns an iterator of all the positions of this TreeZipper. Exists for use with the foreach syntax.
*
* @return An iterator of all the positions of this TreeZipper.
*/
public Iterator> iterator() {
return positions().toTree().iterator();
}
private final Tree tree;
private final Stream> lefts;
private final Stream> rights;
private final Stream>, A, Stream>>> parents;
private TreeZipper(final Tree tree,
final Stream> lefts,
final Stream> rights,
final Stream>, A, Stream>>> parents) {
this.tree = tree;
this.lefts = lefts;
this.rights = rights;
this.parents = parents;
}
@Override
public final boolean equals(Object other) {
return Equal.equals0(TreeZipper.class, this, other, () -> Equal.treeZipperEqual(Equal.anyEqual()));
}
@Override
public final int hashCode() {
return Hash.treeZipperHash(Hash.anyHash()).hash(this);
}
/**
* Creates a new tree zipper given a currently selected tree, a forest on the left, a forest on the right,
* and a stream of parent contexts.
*
* @param tree The currently selected tree.
* @param lefts The selected tree's left siblings, closest first.
* @param rights The selected tree's right siblings, closest first.
* @param parents The parent of the selected tree, and the parent's siblings.
* @return A new zipper with the given tree selected, and the given forests on the left and right.
*/
public static TreeZipper treeZipper(final Tree tree,
final Stream> lefts,
final Stream> rights,
final Stream>, A, Stream>>> parents) {
return new TreeZipper<>(tree, lefts, rights, parents);
}
/**
* First-class constructor for tree zippers.
*
* @return A function that returns a new tree zipper, given a selected tree, left and right siblings,
* and a parent context.
*/
public static
F, F>, F>, F>, A, Stream>>>, TreeZipper>>>>
treeZipper() {
return curry(
TreeZipper::treeZipper);
}
/**
* Returns the product-4 representation of this zipper.
*
* @return the product-4 representation of this zipper.
*/
public P4, Stream>, Stream>, Stream>, A, Stream>>>> p() {
return P.p(tree, lefts, rights, parents);
}
/**
* A first-class function that returns the product-4 representation of a given zipper.
*
* @return a function that converts a given zipper to its product-4 representation.
*/
public static
F, P4, Stream>, Stream>, Stream>, A, Stream>>>>> p_() {
return TreeZipper::p;
}
/**
* An Equal instance for tree zippers.
*
* @param e An Equal instance for tree elements.
* @return An Equal instance for tree zippers.
*/
public static Equal> eq(final Equal e) {
return p4Equal(
treeEqual(e),
streamEqual(treeEqual(e)),
streamEqual(treeEqual(e)),
streamEqual(p3Equal(streamEqual(treeEqual(e)), e, streamEqual(treeEqual(e))))).contramap(TreeZipper.p_());
}
/**
* A Show instance for tree zippers.
*
* @param s A Show instance for tree elements.
* @return A Show instance for tree zippers.
*/
public static Show> show(final Show s) {
return p4Show(
treeShow(s),
streamShow(treeShow(s)),
streamShow(treeShow(s)),
streamShow(p3Show(streamShow(treeShow(s)), s, streamShow(treeShow(s))))).contramap(TreeZipper.p_());
}
private static Stream> combChildren(final Stream> ls,
final Tree t,
final Stream> rs) {
return ls.foldLeft(compose(flip(Stream.cons()), P.p1()), Stream.cons(t, P.p(rs)));
}
/**
* Navigates to the parent of the current location.
*
* @return A new tree zipper focused on the parent node of the current node,
* or none if the current node is the root node.
*/
public Option> parent() {
if (parents.isEmpty())
return none();
else {
final P3>, A, Stream>> p = parents.head();
return some(treeZipper(node(p._2(), combChildren(lefts, tree, rights)), p._1(), p._3(), parents.tail()._1()));
}
}
/**
* Navigates to the top-most parent of the current location.
*
* @return A new tree zipper focused on the top-most parent of the current node.
*/
public TreeZipper root() {
return parent().option(this, TreeZipper.root_());
}
/**
* A first-class version of the root function.
*
* @return A function that returns a new tree-zipper focused on the root of the given tree zipper's tree.
*/
public static F, TreeZipper> root_() {
return TreeZipper::root;
}
/**
* Navigates to the left sibling of the current location.
*
* @return A new tree zipper focused on the left sibling of the current node,
* or none if there are no siblings on the left.
*/
public Option> left() {
return lefts.isEmpty() ? Option.none()
: some(treeZipper(lefts.head(), lefts.tail()._1(), rights.cons(tree), parents));
}
/**
* Navigates to the right sibling of the current location.
*
* @return A new tree zipper focused on the right sibling of the current node,
* or none if there are no siblings on the right.
*/
public Option> right() {
return rights.isEmpty() ? Option.none()
: some(treeZipper(rights.head(), lefts.cons(tree), rights.tail()._1(), parents));
}
/**
* Navigtes to the first child of the current location.
*
* @return A new tree zipper focused on the first child of the current node, or none if the node has no children.
*/
public Option> firstChild() {
final Stream> ts = tree.subForest()._1();
return ts.isEmpty() ? Option.none()
: some(treeZipper(ts.head(), Stream.nil(), ts.tail()._1(), downParents()));
}
/**
* Navigtes to the last child of the current location.
*
* @return A new tree zipper focused on the last child of the current node, or none if the node has no children.
*/
public Option> lastChild() {
final Stream> ts = tree.subForest()._1().reverse();
return ts.isEmpty() ? Option.none()
: some(treeZipper(ts.head(), ts.tail()._1(), Stream.nil(), downParents()));
}
/**
* Navigates to the given child of the current location, starting at index 0.
*
* @param n The index of the child to which to navigate.
* @return An optional tree zipper focused on the child node at the given index, or none if there is no such child.
*/
public Option> getChild(final int n) {
Option> r = none();
for (final P2>, Stream>> lr
: splitChildren(Stream.nil(), tree.subForest()._1(), n)) {
r = some(treeZipper(lr._1().head(), lr._1().tail()._1(), lr._2(), downParents()));
}
return r;
}
/**
* Navigates to the first child of the current location, that satisfies the given predicate.
*
* @param p A predicate to be satisfied by the child node.
* @return An optional tree zipper focused on the first child node that satisfies the given predicate,
* or none if there is no such child.
*/
public Option> findChild(final F, Boolean> p) {
Option> r = none();
final F2>, Stream>, Option>, Tree, Stream>>>> split =
new F2>, Stream>, Option>, Tree, Stream>>>>() {
public Option>, Tree, Stream>>> f(final Stream> acc,
final Stream> xs) {
return xs.isNotEmpty()
? p.f(xs.head()) ? some(P.p(acc, xs.head(), xs.tail()._1()))
: f(acc.cons(xs.head()), xs.tail()._1())
: Option.none();
}
};
Stream> subforest = tree.subForest()._1();
if (subforest.isNotEmpty()) {
for (final P3>, Tree, Stream>> ltr
: split.f(Stream.nil(), subforest)) {
r = some(treeZipper(ltr._2(), ltr._1(), ltr._3(), downParents()));
}
}
return r;
}
private Stream>, A, Stream>>> downParents() {
return parents.cons(P.p(lefts, tree.root(), rights));
}
private static Option, Stream>> splitChildren(final Stream acc,
final Stream xs,
final int n) {
return n == 0 ? some(P.p(acc, xs))
: xs.isNotEmpty() ? splitChildren(acc.cons(xs.head()), xs.tail()._1(), n - 1)
: Option.none();
}
private static Stream>, A, Stream>>> lp3nil() {
return nil();
}
/**
* Creates a new tree zipper focused on the root of the given tree.
*
* @param t A tree over which to create a new zipper.
* @return a new tree zipper focused on the root of the given tree.
*/
public static TreeZipper fromTree(final Tree t) {
return treeZipper(t, Stream.nil(), Stream.nil(), TreeZipper.lp3nil());
}
/**
* Creates a new tree zipper focused on the first element of the given forest.
*
* @param ts A forest over which to create a new zipper.
* @return a new tree zipper focused on the first element of the given forest.
*/
public static Option> fromForest(final Stream> ts) {
return ts.isNotEmpty()
? some(treeZipper(ts.head(), Stream.nil(), ts.tail()._1(), TreeZipper.lp3nil()))
: Option.none();
}
/**
* Returns the tree containing this location.
*
* @return the tree containing this location.
*/
public Tree toTree() {
return root().tree;
}
/**
* Returns the forest containing this location.
*
* @return the forest containing this location.
*/
public Stream> toForest() {
final TreeZipper r = root();
return combChildren(r.lefts, r.tree, r.rights);
}
/**
* Returns the tree at the currently focused node.
*
* @return the tree at the currently focused node.
*/
public Tree focus() {
return tree;
}
/**
* Returns the left siblings of the currently focused node.
*
* @return the left siblings of the currently focused node.
*/
public Stream> lefts() {
return lefts;
}
/**
* Returns the right siblings of the currently focused node.
*
* @return the right siblings of the currently focused node.
*/
public Stream> rights() {
return rights;
}
/**
* Returns the parents of the currently focused node.
*
* @return the parents of the currently focused node.
*/
public Stream>, A, Stream>>> parents() {
return parents;
}
/**
* Indicates whether the current node is at the top of the tree.
*
* @return true if the current node is the root of the tree, otherwise false.
*/
public boolean isRoot() {
return parents.isEmpty();
}
/**
* Indicates whether the current node is the leftmost tree in the current forest.
*
* @return true if the current node has no left siblings, otherwise false.
*/
public boolean isFirst() {
return lefts.isEmpty();
}
/**
* Indicates whether the current node is the rightmost tree in the current forest.
*
* @return true if the current node has no siblings on its right, otherwise false.
*/
public boolean isLast() {
return rights.isEmpty();
}
/**
* Indicates whether the current node is at the bottom of the tree.
*
* @return true if the current node has no child nodes, otherwise false.
*/
public boolean isLeaf() {
return tree.subForest()._1().isEmpty();
}
/**
* Indicates whether the current node is a child node of another node.
*
* @return true if the current node has a parent node, otherwise false.
*/
public boolean isChild() {
return !isRoot();
}
/**
* Indicates whether the current node has any child nodes.
*
* @return true if the current node has child nodes, otherwise false.
*/
public boolean hasChildren() {
return !isLeaf();
}
/**
* Replaces the current node with the given tree.
*
* @param t A tree with which to replace the current node.
* @return A new tree zipper in which the focused node is replaced with the given tree.
*/
public TreeZipper setTree(final Tree t) {
return treeZipper(t, lefts, rights, parents);
}
/**
* Modifies the current node with the given function.
*
* @param f A function with which to modify the current tree.
* @return A new tree zipper in which the focused node has been transformed by the given function.
*/
public TreeZipper modifyTree(final F, Tree> f) {
return setTree(f.f(tree));
}
/**
* Modifies the label at the current node with the given function.
*
* @param f A function with which to transform the current node's label.
* @return A new tree zipper with the focused node's label transformed by the given function.
*/
public TreeZipper modifyLabel(final F f) {
return setLabel(f.f(getLabel()));
}
/**
* Replaces the label of the current node with the given value.
*
* @param v The new value for the node's label.
* @return A new tree zipper with the focused node's label replaced by the given value.
*/
public TreeZipper setLabel(final A v) {
return modifyTree(t -> Tree.node(v, t.subForest()));
}
/**
* Returns the label at the current node.
*
* @return the label at the current node.
*/
public A getLabel() {
return tree.root();
}
/**
* Inserts a tree to the left of the current position. The inserted tree becomes the current tree.
*
* @param t A tree to insert to the left of the current position.
* @return A new tree zipper with the given tree in focus and the current tree on the right.
*/
public TreeZipper insertLeft(final Tree t) {
return treeZipper(t, lefts, rights.cons(tree), parents);
}
/**
* Inserts a tree to the right of the current position. The inserted tree becomes the current tree.
*
* @param t A tree to insert to the right of the current position.
* @return A new tree zipper with the given tree in focus and the current tree on the left.
*/
public TreeZipper insertRight(final Tree t) {
return treeZipper(t, lefts.cons(tree), rights, parents);
}
/**
* Inserts a tree as the first child of the current node. The inserted tree becomes the current tree.
*
* @param t A tree to insert.
* @return A new tree zipper with the given tree in focus, as the first child of the current node.
*/
public TreeZipper insertDownFirst(final Tree t) {
return treeZipper(t, Stream.nil(), tree.subForest()._1(), downParents());
}
/**
* Inserts a tree as the last child of the current node. The inserted tree becomes the current tree.
*
* @param t A tree to insert.
* @return A new tree zipper with the given tree in focus, as the last child of the current node.
*/
public TreeZipper insertDownLast(final Tree t) {
return treeZipper(t, tree.subForest()._1().reverse(), Stream.nil(), downParents());
}
/**
* Inserts a tree at the specified location in the current node's stream of children. The inserted tree
* becomes the current node.
*
* @param n The index at which to insert the given tree, starting at 0.
* @param t A tree to insert.
* @return A new tree zipper with the given tree in focus, at the specified index in the current node's stream
* of children, or None if the current node has fewer than n
children.
*/
public Option> insertDownAt(final int n, final Tree t) {
Option> r = none();
for (final P2>, Stream>> lr
: splitChildren(Stream.nil(), tree.subForest()._1(), n)) {
r = some(treeZipper(t, lr._1(), lr._2(), downParents()));
}
return r;
}
/**
* Removes the current node from the tree. The new position becomes the right sibling, or the left sibling
* if the current node has no right siblings, or the parent node if the current node has no siblings.
*
* @return A new tree zipper with the current node removed.
*/
public Option> delete() {
Option> r = none();
if (rights.isNotEmpty())
r = some(treeZipper(rights.head(), lefts, rights.tail()._1(), parents));
else if (lefts.isNotEmpty())
r = some(treeZipper(lefts.head(), lefts.tail()._1(), rights, parents));
else for (final TreeZipper loc : parent())
r = some(loc.modifyTree(t -> node(t.root(), Stream.nil())));
return r;
}
/**
* Zips the nodes in this zipper with a boolean that indicates whether that node has focus.
* All of the booleans will be false, except for the focused node.
*
* @return A new zipper of pairs, with each node of this zipper paired with a boolean that is true if that
* node has focus, and false otherwise.
*/
public TreeZipper> zipWithFocus() {
final F> f = flip(P.p2()).f(false);
return map(f).modifyLabel(P2.map2_(Booleans.not));
}
/**
* Maps the given function across this zipper (covariant functor pattern).
*
* @param f A function to map across this zipper.
* @return A new zipper with the given function applied to the label of every node.
*/
public TreeZipper map(final F f) {
final F, Tree> g = Tree.fmap_().f(f);
final F>, Stream>> h = Stream., Tree>map_().f(g);
return treeZipper(tree.fmap(f), lefts.map(g), rights.map(g), parents.map(
p -> p.map1(h).map2(f).map3(h)));
}
/**
* First-class conversion of a Tree to the corresponding tree zipper.
*
* @return A function that takes a tree to its tree zipper representation.
*/
public static F, TreeZipper> fromTree() {
return TreeZipper::fromTree;
}
/**
* A first-class version of the left() function.
*
* @return A function that focuses the given tree zipper on its left sibling.
*/
public static F, Option>> left_() {
return TreeZipper::left;
}
/**
* A first-class version of the right() function.
*
* @return A function that focuses the given tree zipper on its right sibling.
*/
public static F, Option>> right_() {
return TreeZipper::right;
}
/**
* Returns a zipper over the tree of all possible permutations of this tree zipper (comonad pattern).
* This tree zipper becomes the focused node of the new zipper.
*
* @return A tree zipper over the tree of all possible permutations of this tree zipper.
*/
public TreeZipper> positions() {
final Tree> t = unfoldTree(TreeZipper.dwn()).f(this);
final Stream>> l = uf(TreeZipper.left_());
final Stream>> r = uf(TreeZipper.right_());
final Stream>>, TreeZipper, Stream>>>> p = unfold(
o -> {
Option>>, TreeZipper, Stream>>>,
Option>>> r1 = none();
for (final TreeZipper z : o) {
r1 = some(P.p(P.p(z.uf(TreeZipper.left_()), z, z.uf(TreeZipper.right_())), z.parent()));
}
return r1;
}, parent());
return treeZipper(t, l, r, p);
}
private Stream>> uf(final F, Option>> f) {
return unfold(
o -> {
Option>, Option>>> r = none();
for (final TreeZipper c : o) {
r = some(P.p(unfoldTree(TreeZipper.dwn()).f(c), f.f(c)));
}
return r;
}, f.f(this));
}
private static F, P2, P1>>>> dwn() {
F
© 2015 - 2025 Weber Informatics LLC | Privacy Policy