All Downloads are FREE. Search and download functionalities are using the official Maven repository.

xdean.jex.extra.collection.Tree Maven / Gradle / Ivy

The newest version!
package xdean.jex.extra.collection;

import static xdean.jex.extra.rx2.RxFunctions.rx;
import static xdean.jex.util.function.FunctionAdapter.function;
import static xdean.jex.util.function.Predicates.is;
import static xdean.jex.util.function.Predicates.isEquals;
import static xdean.jex.util.function.Predicates.its;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.google.common.collect.Lists;

import io.reactivex.Flowable;
import xdean.jex.extra.collection.Traverse.Traversable;
import xdean.jex.extra.collection.Traverse.Traverser;

public class Tree implements Traversable> {
  private Tree parent;
  private List> children = new ArrayList<>();
  private T value;

  public Tree(T value) {
    this.value = value;
  }

  public Tree getParent() {
    return parent;
  }

  public List> getChildren() {
    return children;
  }

  public T getValue() {
    return value;
  }

  /**
   * Add the value as child
   *
   * @param value
   * @return the child node
   */
  public Tree add(T value) {
    return add(new Tree<>(value));
  }

  /**
   * Add the node as child
   *
   * @param node
   * @return the child node
   */
  public Tree add(Tree node) {
    node.removeFromParent();
    node.parent = this;
    children.add(node);
    return node;
  }

  /**
   * Remove the first matched node with given value from children
   *
   * @param value
   * @return has removed
   */
  public boolean remove(T value) {
    return getChild(value)
        .map(this::remove)
        .orElse(false);
  }

  /**
   * Remove the node from children
   *
   * @param node
   * @return has removed
   */
  public boolean remove(Tree node) {
    return getChild(node)
        .map(function(n -> n.parent = null))
        .map(children::remove)
        .orElse(false);
  }

  /**
   * Remove this node from its parent
   *
   * @return the old parent
   */
  public Optional> removeFromParent() {
    Tree theParent = this.parent;
    if (parent != null) {
      parent.remove(this);
    }
    return Optional.ofNullable(theParent);
  }

  /**
   * Returns true if there is a child's value is the given value
   *
   * @param value
   * @return
   */
  public boolean hasChild(T value) {
    return getChild(value).isPresent();
  }

  /**
   * Returns true if the node has no children.
   *
   * @return
   */
  public boolean isLeaf() {
    return children.isEmpty();
  }

  /**
   * Get the first matched child with the given value from children
   *
   * @param value
   * @return
   */
  public Optional> getChild(T value) {
    return children.stream().filter(its(n -> n.value, isEquals(value))).findFirst();
  }

  /**
   * Return the given node if it's this node's child
   *
   * @param node
   * @return
   */
  public Optional> getChild(Tree node) {
    return children.stream().filter(is(node)).findFirst();
  }

  /**
   * Get the first matched child with the given value from all of the sub-tree
   *
   * @param value
   * @return
   */
  public Optional> deepChild(T value) {
    return breadthFirstTraversal()
        .filter(rx(its(Tree::getValue, isEquals(value))))
        .map(Optional::of)
        .blockingFirst(Optional.empty());
  }

  /**
   * Get the first matched child with the given value from all of the sub-tree
   *
   * @param node
   * @return
   */
  public Optional> deepChild(Tree node) {
    return breadthFirstTraversal()
        .filter(rx(is(node)))
        .map(Optional::of)
        .blockingFirst(Optional.empty());
  }

  /**
   * Get the first common parent of this node and the given node
   *
   * @param other
   * @return the common parent
   */
  public Optional> commonParent(Tree other) {
    List> myParents = parents().startWith(this).toList().blockingGet();
    return other.parents()
        .startWith(other)
        .filter(myParents::contains)
        .map(Optional::of)
        .blockingFirst(Optional.empty());
  }

  public Optional>> pathTo(Tree node) {
    if (deepChild(node).isPresent()) {
      return Optional.of(
          path(node, this)
              .toList()
              .flattenAsFlowable(Lists::reverse));
    } else if (node.deepChild(this).isPresent()) {
      return Optional.of(path(this, node));
    } else {
      return Optional.empty();
    }
  }

  /**
   * @param downNode
   * @param upNode
   * @return from downNode to upNode's path
   */
  private static  Flowable> path(Tree downNode, Tree upNode) {
    return Flowable.generate(() -> Wrapper.of(downNode), (w, e) -> {
      Tree node = w.get();
      if (node == null) {
        e.onComplete();
      } else {
        e.onNext(node);
        if (node == upNode) {
          w.set(null);
        } else {
          Tree parent = node.getParent();
          w.set(parent);
        }
      }
    });
  }

  /**
   * Swap with the given node. Even they are in different trees.
   *
   * @param node
   */
  public void swap(Tree node) {
    Tree thisParent = this.parent;
    List> thisChildren = new ArrayList<>(this.children);

    this.removeFromParent();
    if (node.parent != null) {
      node.parent.add(this);
    }
    node.children.forEach(this::add);

    node.removeFromParent();
    if (thisParent != null) {
      thisParent.add(node);
    }
    thisChildren.forEach(node::add);
  }

  public void rotateAsParent() {
    removeFromParent().ifPresent(this::add);
  }

  /**
   * Get all of the node's parent
   *
   * @return
   */
  public Flowable> parents() {
    return Flowable.generate(() -> Wrapper.of(this), (n, e) -> {
      Tree parent = n.get().getParent();
      if (parent != null) {
        e.onNext(parent);
        n.set(parent);
      } else {
        e.onComplete();
      }
    });
  }

  @Override
  public Flowable> traverse(Traverser traverser) {
    return traverser.travese(this, Tree::getChildren);
  }

  @Override
  public String toString() {
    return "TreeNode [value=" + value + "]";
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy