
com.github.kdvolder.tttree.TTTree Maven / Gradle / Ivy
Show all versions of two-three-tree Show documentation
package com.github.kdvolder.tttree;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.Stack;
import com.github.kdvolder.util.Assert;
import com.google.common.collect.Iterators;
public abstract class TTTree, V> implements Iterable>{
////////////////////////////////////
// public api
////////////////////////////////////
@SuppressWarnings("unchecked")
public static , V> TTTree empty() {
return EMPTY_TREE;
}
public abstract TTTree put(K k, V v);
public final V get(K k) {
Leaf e = getEntry(k);
if (e!=null) {
return e.getValue();
}
return null;
}
public abstract TTTree remove(K k);
public boolean isEmpty() {
return false; // good default because most nodes aren't empty.
}
@Override
public Iterator> iterator() {
if (isEmpty()) {
Collections.emptyIterator();
}
return new TTTreeIterator(this);
}
public Set keySet() {
return new AbstractSet() {
private Integer size;
@SuppressWarnings("unchecked")
@Override
public boolean contains(Object o) {
if (o instanceof Comparable) {
return TTTree.this.containsKey((K)o);
}
return false;
}
@Override
public Iterator iterator() {
Iterator> entries = TTTree.this.iterator();
return Iterators.transform(entries, Map.Entry::getKey);
}
@Override
public int size() {
if (size==null) {
this.size = TTTree.this.size();
}
return size;
}
};
}
public final boolean containsKey(K key) {
return getEntry(key)!=null;
}
abstract Leaf getEntry(K key);
/**
* Counts the elements in the {@link TTTree}.
*
* WARNING: not cached... so O(n) operation.
*/
abstract int size();
/**
* For debugging. Dump tree structure in indented format onto sysout
*/
public void dump() {
dump(0);
}
/////////////////////////////////////
// implementation
/////////////////////////////////////
//Some possible optimization to consider in future:
// 1) Can we avoid storing/computing 'depth' in each node?
// It is nice to have the depth for now to:
// - execute some asserts to make sure the code doesn't break the 'equal depths' invariant
// - easily determine if a tree has grown.
// However it should not be necessary to track the depth. All that would be needed is for a
// internal 'put/remove' operation to somehow return a boolean alongside the resulting tree to
// indicate whether the new tree's depth has changed.
// 2) Remove 'redundant' internal keys
// See the method TTTreeTest.checkForRedundantInternalKeys
@Override
public abstract String toString();
protected static final TTTree,?>[] NO_CHILDREN = {};
abstract void dump(int indent);
abstract TTTree[] getChildren();
abstract int depth();
void print(int indent, Object msg) {
for (int i = 0; i < indent; i++) {
System.out.print(" ");
}
System.out.println(msg);
}
@SuppressWarnings({"rawtypes", "unchecked"})
private static final TTTree EMPTY_TREE = new TTTree() {
@Override public TTTree put(Comparable k, Object v) { return leaf(k, v); }
@Override Leaf getEntry(Comparable key) { return null; }
@Override public String toString() { return "EMPTY"; }
@Override public boolean isEmpty() { return true; }
@Override TTTree[] getChildren() { return NO_CHILDREN; }
@Override int depth() { return 0; }
@Override int size() { return 0; }
@Override void dump(int indent) {print(indent, this);}
@Override public TTTree remove(Comparable k) {return this; }
@Override public void accept(TTTreeVisitor visitor) { visitor.visit_empty(); }
};
/**
* Create a LEAF node which contains a single key -> value pair.
*/
private static , V> TTTree leaf(K k, V v) {
return new Leaf(k, v);
}
private static class Leaf, V> extends TTTree implements Map.Entry {
private final K k;
private final V v;
Leaf(K k, V v) {
super();
this.k = k;
this.v = v;
}
@Override
Leaf getEntry(K key) {
if (key.equals(k)) {
return this;
}
return null;
}
@Override
public TTTree put(K ik, V iv) {
int compare = ik.compareTo(k);
TTTree newLeaf = leaf(ik, iv);
if (compare==0) {
// ik == k
return newLeaf;
} else if (compare<0) {
// ik < k
return new Node2<>(newLeaf, ik, this);
} else {
// ik > k
return new Node2<>(this, k, newLeaf);
}
}
@Override
public TTTree remove(K fk) {
if (fk.equals(k)) {
return empty();
}
return this;
}
@Override
protected int depth() {
return 1;
}
@Override
void dump(int indent) {
print(indent, k + " = " +v);
}
@Override
public K getKey() {
return k;
}
@Override
public V getValue() {
return v;
}
@Override
public V setValue(V value) {
throw new UnsupportedOperationException("setValue");
}
@SuppressWarnings("unchecked")
@Override
TTTree[] getChildren() {
return (TTTree[]) NO_CHILDREN;
}
@Override
int size() {
return 1;
}
@Override
public String toString() {
return "["+k+" = "+v+"]";
}
@Override
public void accept(TTTreeVisitor visitor) {
visitor.visit_leaf(k, v);
}
};
private static class Node2, V> extends TTTree {
private final TTTree l;
private final K k;
private final TTTree r;
private int depth;
Node2(TTTree l, K k, TTTree r) {
Assert.isLegalState(l.depth()==r.depth());
this.depth = l.depth()+1;
this.l = l;
this.k = k;
this.r = r;
}
@Override
public TTTree put(K ik, V iv) {
int c = ik.compareTo(k);
if (c<=0) {
//ik <= k
TTTree new_l = l.put(ik, iv);
if (new_l.depth()>l.depth()) {
//Since the tree has just grown its root must be Node2
K lk = ((Node2)new_l).k;
TTTree ll = ((Node2)new_l).l;
TTTree lr = ((Node2)new_l).r;
return new Node3<>(ll, lk, lr, k, r);
} else {
//Tree remained same size
return new Node2<>(new_l,k,r);
}
} else {
//ik > k
TTTree new_r = r.put(ik, iv);
if (new_r.depth()>r.depth()) {
//Since the tree has just grown its root must be Node2
K rk = ((Node2)new_r).k;
TTTree rl = ((Node2)new_r).l;
TTTree rr = ((Node2)new_r).r;
return new Node3<>(l, k, rl, rk, rr);
} else {
//Tree remained same size
return new Node2<>(l,k,new_r);
}
}
}
@Override
public TTTree remove(K fk) {
int c = fk.compareTo(k);
if (c<=0) {
// fk <= k
TTTree l = this.l.remove(fk);
if (this.l==l) {
return this; //Avoid needless copying if tree is unchanged
} else if (this.l.depth()==l.depth()) {
return new Node2<>(l, k, r);
} else {
//l.depth shrunk
if (r instanceof Node2) {
Node2 r = (Node2) this.r;
return new Node3<>(l, k, r.l, r.k, r.r);
} else if (r instanceof Node3) {
Node3 r = (Node3) this.r;
return new Node2<>(
new Node2<>(l, k, r.l),
r.k1,
new Node2<>(r.m, r.k2, r.r)
);
} else {
Assert.isLegalState(r instanceof Leaf);
Assert.isLegalState(l.isEmpty());
return r;
}
}
} else {
// fk > k
TTTree r = this.r.remove(fk);
if (this.r==r) {
return this; //Avoid needless copying if tree is unchanged
} else if (this.r.depth()==r.depth()) {
return new Node2<>(l, k, r);
} else {
//r.depth shrunk
if (l instanceof Node2) {
Node2 l = (Node2) this.l;
return new Node3<>(l.l, l.k, l.r, k, r);
} else if (l instanceof Node3) {
Node3 l = (Node3) this.l;
return new Node2<>(
new Node2<>(l.l, l.k1, l.m),
l.k2,
new Node2<>(l.r, k, r)
);
} else {
Assert.isLegalState(l instanceof Leaf);
Assert.isLegalState(r.isEmpty());
return l;
}
}
}
}
@Override
public Leaf getEntry(K fk) {
int c = fk.compareTo(k);
if (c<=0) {
// fk <= k
return l.getEntry(fk);
} else {
// fk > k
return r.getEntry(fk);
}
}
@Override
protected int depth() {
return depth;
}
@Override
void dump(int indent) {
l.dump(indent+1);
print(indent, k);
r.dump(indent+1);
}
@SuppressWarnings("unchecked")
@Override
TTTree[] getChildren() {
return new TTTree[] {l, r};
}
@Override
int size() {
return l.size() + r.size();
}
@Override
public String toString() {
return "Node2["+depth+"]("+k+")";
}
@Override
public void accept(TTTreeVisitor visitor) {
visitor.visit_2node(l, k, r);
}
}
private static class Node3, V> extends TTTree {
private int depth;
final TTTree l;
final K k1;
final TTTree m;
final K k2;
final TTTree r;
public Node3(TTTree l, K k1, TTTree m, K k2, TTTree r) {
Assert.isLegalState(l.depth()==m.depth());
Assert.isLegalState(l.depth()==r.depth());
this.depth = l.depth()+1;
this.l = l;
this.k1 = k1;
this.m = m;
this.k2 = k2;
this.r = r;
}
@Override
public TTTree put(K k, V v) {
int c = k.compareTo(k1);
if (c<=0) {
//k <= k1
final TTTree l = this.l.put(k, v);
if (l.depth()>this.l.depth()) {
//The tree has just grown
//split ourself into a new Node2.
return new Node2<>(
l,
k1,
new Node2<>(m, k2, r)
);
} else {
//Tree remained same size
return new Node3<>(l, k1, m, k2, r);
}
} else {
// k1 < k
c = k.compareTo(k2);
if (c<=0) {
//k1 < k <= k2
final TTTree m = this.m.put(k, v);
if (m.depth()>this.m.depth()) {
//Since the tree has just grown its root *must* be Node2
K mk = ((Node2)m).k;
TTTree ml = ((Node2)m).l;
TTTree mr = ((Node2)m).r;
return new Node2<>(
new Node2<>(l, k1, ml),
mk,
new Node2<>(mr, k2, r)
);
} else {
//Tree remained same size
return new Node3<>(l, k1, m, k2, r);
}
} else {
//k2 < k
final TTTree r = this.r.put(k, v);
if (r.depth()>this.r.depth()) {
//The tree has just grown
//split ourself into a new Node2.
return new Node2<>(
new Node2<>(l, k1, m),
k2,
r
);
} else {
//Tree remained same size
return new Node3<>(l, k1, m, k2, r);
}
}
}
}
@Override
public TTTree remove(K fk) {
int c = fk.compareTo(k1);
if (c<=0) {
//fk <= k1
TTTree l = this.l.remove(fk);
if (l==this.l) {
return this;
} else if (l.depth()==this.l.depth()) {
return new Node3<>(l, k1 ,m, k2, r);
} else {
//shrunk l
if (l.isEmpty()) {
return new Node2<>(m, k2, r);
} else if (m instanceof Node2) {
Node2 m = (Node2) this.m;
return new Node2<>(
new Node3<>(l, k1, m.l, m.k, m.r),
k2,
r
);
} else { //m instanceof Node3
Node3 m = (Node3) this.m;
return new Node3<>(
new Node2<>(l, k1, m.l),
m.k1,
new Node2<>(m.m, m.k2, m.r),
k2,
r
);
}
}
} else {
//k1 < fk
c = fk.compareTo(k2);
if (c<=0) {
//k1 < fk <= k2
TTTree m = this.m.remove(fk);
if (m==this.m) {
return this;
} else if (m.depth()==this.m.depth()) {
return new Node3<>(l, k1, m, k2, r);
} else {
//shrunk
if (m.isEmpty()) {
return new Node2<>(l, k1, r);
} else if (l instanceof Node2) {
Node2 l = (Node2) this.l;
return new Node2<>(
new Node3<>(l.l, l.k, l.r, k1, m),
k2,
r
);
} else { //l instanceof Node3
Node3 l = (Node3) this.l;
return new Node3<>(
new Node2<>(l.l, l.k1, l.m),
l.k2,
new Node2<>(l.r, k1, m),
k2,
r
);
}
}
} else {
//k2 < fk
TTTree r = this.r.remove(fk);
if (r==this.r) {
return this;
} else if (r.depth()==this.r.depth()) {
return new Node3<>(l, k1, m, k2, r);
} else {
//shrunk
if (r.isEmpty()) {
return new Node2<>(l, k1, m);
} else if (m instanceof Node2) {
Node2 m = (Node2)this.m;
return new Node2<>(
l,
k1,
new Node3<>(m.l, m.k, m.r, k2, r)
);
} else { //m instanceof Node3
Node3 m = (Node3) this.m;
return new Node3<>(
l,
k1,
new Node2<>(m.l, m.k1, m.m),
m.k2,
new Node2<>(m.r, k2, r)
);
}
}
}
}
}
@Override
public Leaf getEntry(K k) {
int c = k.compareTo(k1);
if (c<=0) {
//k <= k1
return l.getEntry(k);
} else {
// k1 < k
c = k.compareTo(k2);
if (c<=0) {
//k1 < k <= k2
return m.getEntry(k);
} else {
//k2 < k
return r.getEntry(k);
}
}
}
@Override
protected int depth() {
return depth;
}
@Override
void dump(int indent) {
l.dump(indent+1);
print(indent, k1);
m.dump(indent+1);
print(indent, k2);
r.dump(indent+1);
}
@SuppressWarnings("unchecked")
@Override
TTTree[] getChildren() {
return new TTTree[] {l, m, r};
}
@Override
int size() {
return l.size() + m.size() + r.size();
}
@Override
public String toString() {
return "Node3["+depth+"]("+k1+", "+k2+")";
}
@Override
public void accept(TTTreeVisitor visitor) {
visitor.visit_3node(l, k1, m, k2, r);
}
}
private class TTTreeIterator implements Iterator> {
Stack> stack = new Stack<>();
TTTreeIterator(TTTree tree) {
if (!tree.isEmpty())
stack.push(tree);
}
@Override
public boolean hasNext() {
return !stack.isEmpty();
}
@Override
public Entry next() {
while(!stack.isEmpty()) {
TTTree node = stack.pop();
if (node instanceof Leaf) {
return (Leaf)node;
} else {
TTTree[] children = node.getChildren();
for (int i = children.length-1; i >= 0; i--) {
TTTree c = children[i];
if (!c.isEmpty()) {
stack.push(children[i]);
}
}
}
}
throw new NoSuchElementException();
}
}
/**
* Useful to perform various traversals / analysis on the tree.
*/
public abstract void accept(TTTreeVisitor visitor);
}