package TaiGameCore; /** * This class is a collection of data structures which were necessary for my implementation of Boggle! * * It contains: * 1) SiblingNode, AATreeForSiblings: AA-balancing tree, with no removal feature * 2) AASiblingTreeIterator: a nonrecursive in-order iterator over (1). * 3) A LinkedLyst class is used by both of the above * 4) A DoubleTreeIterator implementation, StringTreeIterator. This allows us to iterate over trees-of-trees. * * Number of imports needed to compile this class: 0. Yay for indie code. */ public class TaiTrees { /******************* * To start: Some marker classes that describe trees ******************/ /** * A tree node with value type 'E'; */ public static interface TaiBSTNode<E> { public TaiBSTNode<E> getLeft(); public TaiBSTNode<E> getRight(); public E getData(); } /** * A BST */ public static interface TaiBST<E> { public BSTIterator<E> iterator(); public TaiBSTNode<E> getNullNode(); } /** * Alright, now this is the hardest of all these classes to understand. * Given the above class (TaiTreeOTrees), this class represents a NODE of that * very special tree. */ public interface TaiTreeOTreesNode<F extends TaiTreeOTreesNode<F>> { public TaiBST<F> getSubTree(); public int height(); public char getData(); public boolean isFlaggedNode(); } /** * Returns an in-order traversor of an arbitrary-node-order tree. Any node of the outer tree is a BST, defining * all of its children. * * This will allow us to iterate Strings out of a Tree with node-values containing only Characters. It seems like a * good idea. */ public static interface DoubleTreeIterator<G, F extends TaiTreeOTreesNode<F>> { public abstract boolean hasNext(); public abstract G next(); } /********** * Now for some actual implementations relevant to the Dictionary Tree we want to make **********/ /** * The node type used in AATreeForSiblings. * Character keys index a basic binary tree node */ public static class SiblingNode<E> implements TaiBSTNode<E> { public static SiblingNode createNullNode() { SiblingNode nullNode = new SiblingNode((char) 0, null); nullNode.left = nullNode.right = nullNode; nullNode.level = 0; return nullNode; } public SiblingNode(char key, E data) { this.key = key; level = 1; this.data = data; } private SiblingNode<E> left, right; public char key; private E data; public int level; public E getData() { return data; } public TaiBSTNode<E> getLeft() { return left; } public TaiBSTNode<E> getRight() { return right; } } /** * A tree made to organize (sort) the siblings in the tree used in GameDictionary. * * Rather than representing the siblings at each level of that tree as an array or a linkedlist, * I chose to store them as a balanced AA tree (Trees of Trees! Oh boy!). * Reason against array: At the low levels of the tree, an array / arraylist for every node wastes memory * Reason against linkedlist: balanced trees are always O(log(n)) for searching, linkedlist is O(n). * * This is a rather crude implementation of an AA tree. Specifically, removal is not implemented. * * Balanced trees should guarantee log(N) lookup time, where N is the number of elements. Note that if you * are using a finite alphabet, this N is bounded... but for my ego lets assume that's not the case ;) */ public static class AATreeForSiblings<E> implements TaiBST<E> { /** * The head of the AA tree */ private SiblingNode<E> root; /** * The skew / split operations rely on leaves being connected bilaterally to the nullnode */ private transient SiblingNode<E> nullNode; private int size = 0; public AATreeForSiblings(SiblingNode<E> nullNode) { this.nullNode = nullNode; root = nullNode; } /** * Null values are NOT ALLOWED. */ public void insert(char key, E e) { if (e == null) { throw new NullPointerException(); } try { root = insert(key, e, root); size++; } catch (DuplicateItemException f) { //tree is unchanged } } private static class DuplicateItemException extends RuntimeException { /** * Duplicate Item Exception. Caught within tree classes, not exposed. */ private static final long serialVersionUID = 4200419223242727579L; }; private SiblingNode<E> insert(char x, E data, SiblingNode<E> t) { if (t == nullNode) { t = new SiblingNode<E>(x, data); t.left = nullNode; t.right = nullNode; } else if (x < t.key) { t.left = insert(x, data, t.left); } else if (x > t.key) { t.right = insert(x, data, t.right); } else { throw new DuplicateItemException(); } t = skew(t); t = split(t); return t; } /** Balancing operations: **/ private SiblingNode<E> skew(SiblingNode<E> t) { if (t.left.level == t.level) { t = rotateWithLeftChild(t); } return t; } private SiblingNode<E> split(SiblingNode<E> t) { if (t.right.right.level == t.level) { t = rotateWithRightChild(t); t.level++; } return t; } private SiblingNode<E> rotateWithLeftChild(SiblingNode<E> k2) { SiblingNode<E> k1 = k2.left; k2.left = k1.right; k1.right = k2; return k1; } private SiblingNode<E> rotateWithRightChild(SiblingNode<E> k1) { SiblingNode<E> k2 = k1.right; k1.right = k2.left; k2.left = k1; return k2; } /** * Returns an iterator that will iterate through the in-order traversal * of this balanced tree. */ public BSTIterator<E> iterator() { return new BSTIterator<E>(root, nullNode); } /** * Binary search. Returns null if no entry exists with the specified key. * (This is one more reason why I can't allow null data values) */ public E get(char key) { SiblingNode<E> cur = root; while (cur != null && cur.data != null) { int c = key - cur.key; if (c == 0) { return cur.data; } if (c < 0) { cur = cur.left; } else { cur = cur.right; } } return null; } public SiblingNode<E> getNullNode() { return nullNode; } public int size() { return size; } } /** * Minimalistic linked list framework. */ private static class LinkedLyst<E> { public LinkedLyst(E value) { this.value = value; } public E value; public LinkedLyst<E> next; } private static <E> LinkedLyst<E> LLpush(LinkedLyst<E> c, E newObj) { LinkedLyst<E> neu = new LinkedLyst<E>(newObj); neu.next = c; return neu; } private static <E> LinkedLyst<E> LLpop(LinkedLyst<E> c) { return c.next; } /** * Traversal node-doubles. These are used within the traversal class, to include a "numPopped" field * along with a clone of every node in the tree. */ private static class BSTItrNodeDouble<T> { private TaiBSTNode<T> value; int numPopped = 0; public BSTItrNodeDouble(TaiBSTNode<T> lh) { value = lh; } } /** * A simple non-recursive implementation of an in-order traversal on a binary tree. */ public static class BSTIterator<T> { private LinkedLyst<BSTItrNodeDouble<T>> current; /** * For iterating over a tree where there is a null-node-value, we need this. */ private TaiBSTNode<T> nullValue; /* public BSTIterator(TaiBSTNode<T> lh){ this(lh,null); } */ public BSTIterator(TaiBSTNode<T> lh, TaiBSTNode<T> nullValue) { this.nullValue = nullValue; current = new LinkedLyst<BSTItrNodeDouble<T>>( new BSTItrNodeDouble<T>(lh)); } public boolean hasNext() { return current != null && current.value.value != nullValue; } /** * Nonrecursive in-order transversal. See pg 620 of our assigned textbook. */ public T next() { if (current == null) { throw new RuntimeException(); } while (true) { //Note: This is not a recursive method! BSTItrNodeDouble<T> cnode = current.value; current = LLpop(current); if (++cnode.numPopped == 2) { //We have visited this node twice now (not 3x!) so visit it. T toRet = cnode.value.getData(); if (cnode.value.getRight() != nullValue) { current = LLpush(current, new BSTItrNodeDouble<T>( cnode.value.getRight())); } return toRet; } current = LLpush(current, cnode); if (cnode.value.getLeft() != nullValue) { current = LLpush(current, new BSTItrNodeDouble<T>( cnode.value.getLeft())); } } } } /** * A DoubleTreeIterator that lets us iterate through the words in our dictionary data structure. * * @author Benjamin */ public static class StringTreeIterator<F extends TaiTreeOTreesNode<F>> implements DoubleTreeIterator<String, F> { /** * Simulated recursion by a stack */ private LinkedLyst<BSTIterator<F>> current; private int wordLen; private char[] curWord; private int maxWordLength; public StringTreeIterator(TaiTreeOTreesNode<F> lh) { maxWordLength = lh.height(); if (maxWordLength != -1) { curWord = new char[maxWordLength]; wordLen = 0; current = new LinkedLyst<BSTIterator<F>>(lh.getSubTree() .iterator()); tryNext(); //Get first element. } else { moreAvailable = false; } } public StringTreeIterator(TaiTreeOTreesNode<F> lh, boolean generateStrings) { } public boolean hasNext() { return moreAvailable; } private String nextReturn; private TaiTreeOTreesNode<F> nextReturnNode; private boolean moreAvailable = true; public void tryNext() { while (current != null) { if (current.value.hasNext()) { TaiTreeOTreesNode<F> nextNode = current.value.next(); TaiBST<F> subTree = nextNode.getSubTree(); current = LLpush(current, subTree.iterator()); curWord[wordLen++] = nextNode.getData(); if (nextNode.isFlaggedNode()) { nextReturnNode = nextNode; nextReturn = new String(curWord, 0, wordLen); return; } } else { current = LLpop(current); wordLen--; } } moreAvailable = false; } public String next() { return nextReturn; } public TaiTreeOTreesNode<F> getCurrentNode() { return nextReturnNode; } //Could be optimized by adding size of subtrees... hmm... public int indexOf(String filename) { int count = 0; int index = -1; while(hasNext()){ if (next().equals(filename)){ index = count; } count++; tryNext(); } return index; } } /** * Some rudimentary tests on the AA tree coded above. */ public static void main(String[] args) { SiblingNode<String> nullNode = new SiblingNode<String>((char) 0, null); nullNode.left = nullNode.right = nullNode; nullNode.level = 0; AATreeForSiblings<String> bh = new AATreeForSiblings<String>(nullNode); String green = "green"; String blue = "blue"; String yellow = "yellow"; bh.insert('A', green); bh.insert('A', green); bh.insert('A', green); bh.insert('A', yellow); bh.insert('F', blue); bh.insert('B', blue); bh.insert('D', green); bh.insert('C', yellow); bh.insert('E', yellow); BSTIterator<String> bi = bh.iterator(); while (bi.hasNext()) { System.out.println(bi.next()); } } }