package io.kaif.rank;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.function.UnaryOperator;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import io.kaif.util.MoreCollectors;
/**
* immutable node tree structure that support sort children. the value dose not allow null unless
* it is a root node. the root node always with null value and no parent.
*/
@Immutable
public final class SortingNode<T> {
/**
* the builder is not thread-safe, but built node is thread-safe.
*/
@NotThreadSafe
public static class Builder<T> {
private final T value;
private final Builder<T> parentBuilder;
private List<Builder<T>> childBuilders;
public Builder() {
this(null, null);
}
private Builder(T value, Builder<T> parentBuilder) {
this.value = value;
this.parentBuilder = parentBuilder;
}
/**
* add child node, return child node builder
*/
public Builder<T> childNode(T childValue) {
Preconditions.checkNotNull(childValue, "value must not null");
Builder<T> child = new Builder<>(childValue, this);
if (childBuilders == null) {
childBuilders = new LinkedList<>();
}
childBuilders.add(child);
return child;
}
@Override
public String toString() {
return value == null ? "*" : value.toString();
}
/**
* build whole node tree, return root node.
* <p>
* you can invoke #build() everywhere while building, no matter how deep builder is.
*/
public SortingNode<T> build() {
Builder<T> rootBuilder = this;
while (rootBuilder.parentBuilder != null) {
rootBuilder = rootBuilder.parentBuilder;
}
return rootBuilder.deepBuild(null);
}
private SortingNode<T> deepBuild(SortingNode<T> parentNode) {
if (childBuilders == null || childBuilders.isEmpty()) {
return new SortingNode<>(value, parentNode, ImmutableList.of());
}
return new SortingNode<>(value,
parentNode,
childBuilders.stream().map(childBuilder -> childBuilder::deepBuild));
}
public Builder<T> parent() {
return parentBuilder;
}
/**
* append a sibling node, return created sibling builder
* <p>
* this method are not allowed on root builder
*/
public Builder<T> siblingNode(T value) {
return parentBuilder.childNode(value);
}
}
private static final SortingNode<?> EMPTY_ROOT = new SortingNode<>(null,
null,
ImmutableList.of());
@SuppressWarnings("unchecked")
public static <T> SortingNode<T> emptyRoot() {
return (SortingNode<T>) EMPTY_ROOT;
}
@Nullable
private final T value;
@Nullable
private final SortingNode<T> parent;
private final List<SortingNode<T>> children;
/**
* childrenFactory accept `this` node as a argument (its parent), and produce a child node
* <p>
* childrenFactory should not return self or any parent node, since we do not support cyclic tree
*/
private SortingNode(@Nullable T value,
@Nullable SortingNode<T> parent,
Stream<UnaryOperator<SortingNode<T>>> childrenFactory) {
this.value = value;
this.parent = parent;
this.children = childrenFactory.map(f -> f.apply(this))
.collect(MoreCollectors.toImmutableList());
}
private SortingNode(@Nullable T value,
@Nullable SortingNode<T> parent,
ImmutableList<SortingNode<T>> children) {
this.value = value;
this.parent = parent;
this.children = children;
}
@Override
public String toString() {
/*
* do not include parent, it will infinite loop
*/
return "SortingNode{" +
"value=" + value +
", children=" + children +
'}';
}
@Nullable
public T getValue() {
return value;
}
@Nullable
public SortingNode<T> getParent() {
return parent;
}
public boolean hasParent() {
return parent != null;
}
public boolean isRoot() {
return parent == null;
}
public List<SortingNode<T>> getChildren() {
return children;
}
public boolean hasChild() {
return !children.isEmpty();
}
/**
* sort each breath level based on comparator
*
* @return sorted new node.
*/
public SortingNode<T> deepSort(Comparator<SortingNode<T>> comparator) {
if (!hasChild()) {
return this;
}
ImmutableList<SortingNode<T>> sorted = children.stream()
.map(child -> child.deepSort(comparator))
.sorted(comparator)
.collect(MoreCollectors.toImmutableList());
return new SortingNode<>(value, parent, sorted);
}
public String prettyPrint() {
StringBuilder stringBuilder = new StringBuilder();
prettyPrintInto(0, stringBuilder);
return stringBuilder.toString();
}
private void prettyPrintInto(int level, StringBuilder stringBuilder) {
IntStream.range(0, level).forEach(i -> stringBuilder.append(" "));
stringBuilder.append(isRoot() ? "*" : value);
children.forEach(c -> {
stringBuilder.append("\n");
c.prettyPrintInto(level + 1, stringBuilder);
});
}
/**
* traverse self and each children, depth first. the root node is skipped
* (value of root node is always null)
*/
public Stream<T> depthFirst() {
Stream<T> childStream = children.stream().flatMap(SortingNode::depthFirst);
if (value == null) {
return childStream;
} else {
return Stream.concat(Stream.of(value), childStream);
}
}
/**
* traverse self and each children, breath first. the root node is skipped
* (value of root node is always null)
* <p>
* {@link #depthFirst} is faster. prefer it unless you need breathFirst traversal.
*/
public Stream<T> breathFirst() {
return breathFirst(value != null);
}
private Stream<T> breathFirst(boolean includeSelf) {
Stream<T> childStream = children.stream().map(SortingNode::getValue);
Stream<T> all = Stream.concat(childStream,
children.stream().flatMap(c -> c.breathFirst(false)));
if (includeSelf) {
return Stream.concat(Stream.of(value), all);
} else {
return all;
}
}
}