package com.github.btrekkie.tree_list; import java.util.AbstractList; import java.util.ArrayList; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import java.util.NoSuchElementException; import com.github.btrekkie.red_black_node.RedBlackNode; /** * Implements a list using a self-balancing binary search tree augmented by subtree size. The benefit of this compared * to ArrayList or LinkedList is that it supports both decent random access and quickly adding to or removing from the * middle of the list. Operations have the following running times: * * size(): O(1) * get, set, add, remove: O(log N) * addAll: O(log N + P), where P is the number of elements we add * iterator(): O(log N + P + M log N), where P is the number of elements over which we iterate and M is the number of * elements we remove * listIterator: O(log N + P + M log N + R log N), where P is the number of times we iterate over or set an element, M * is the number of elements we add or remove, and R is the number of times we change the direction of iteration * clear(): O(1), excluding garbage collection * subList.clear(): O(log N), excluding garbage collection * Constructor: O(N) * * This class is similar to an Apache Commons Collections class by the same name. I speculate that the Apache class is * faster than this (by a constant factor) for most operations. However, this class's implementations of addAll and * subList.clear() are asymptotically faster than the Apache class's implementations. */ public class TreeList extends AbstractList { /** The dummy leaf node. */ private final TreeListNode leaf = new TreeListNode(null); /** The root node of the tree. */ private TreeListNode root; /** Constructs a new empty TreeList. */ public TreeList() { root = leaf; } /** Constructs a new TreeList containing the specified values, in iteration order. */ public TreeList(Collection values) { root = createTree(values); } /** Returns the root of a perfectly height-balanced tree containing the specified values, in iteration order. */ private TreeListNode createTree(Collection values) { List> nodes = new ArrayList>(values.size()); for (T value : values) { nodes.add(new TreeListNode(value)); } return RedBlackNode.>createTree(nodes, leaf); } /** * Returns the node for get(index). Raises an IndexOutOfBoundsException if "index" is not in the range [0, size()). */ private TreeListNode getNode(int index) { if (index < 0 || index >= root.size) { throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + ")"); } int rank = index; TreeListNode node = root; while (rank != node.left.size) { if (rank < node.left.size) { node = node.left; } else { rank -= node.left.size + 1; node = node.right; } } return node; } @Override public T get(int index) { return getNode(index).value; } @Override public int size() { return root.size; } @Override public boolean isEmpty() { return root == leaf; } @Override public T set(int index, T value) { modCount++; TreeListNode node = getNode(index); T oldValue = node.value; node.value = value; return oldValue; } @Override public void add(int index, T value) { if (index < 0 || index > root.size) { throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + "]"); } modCount++; TreeListNode newNode = new TreeListNode(value); newNode.left = leaf; newNode.right = leaf; if (root.isLeaf()) { root = newNode; newNode.isRed = false; return; } newNode.isRed = true; if (index < root.size) { TreeListNode node = getNode(index); if (node.left.isLeaf()) { node.left = newNode; newNode.parent = node; } else { node = node.predecessor(); node.right = newNode; newNode.parent = node; } } else { TreeListNode node; node = root.max(); node.right = newNode; newNode.parent = node; } root = newNode.fixInsertion(); } @Override public T remove(int index) { TreeListNode node = getNode(index); modCount++; T value = node.value; root = node.remove(); return value; } @Override public boolean addAll(int index, Collection values) { if (index < 0 || index > root.size) { throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + "]"); } modCount++; if (values.isEmpty()) { return false; } else { if (index >= root.size) { root = root.concatenate(createTree(values)); } else { TreeListNode[] split = root.split(getNode(index)); root = split[0].concatenate(createTree(values)).concatenate(split[1]); } return true; } } @Override public boolean addAll(Collection values) { modCount++; if (values.isEmpty()) { return false; } else { root = root.concatenate(createTree(values)); return true; } } @Override protected void removeRange(int startIndex, int endIndex) { if (startIndex != endIndex) { modCount++; TreeListNode last; if (endIndex == root.size) { last = leaf; } else { TreeListNode[] split = root.split(getNode(endIndex)); root = split[0]; last = split[1]; } TreeListNode first = root.split(getNode(startIndex))[0]; root = first.concatenate(last); } } @Override public void clear() { modCount++; root = leaf; } /** The class for TreeList.iterator(). */ private class TreeListIterator implements Iterator { /** The value of TreeList.this.modCount we require to continue iteration without concurrent modification. */ private int modCount = TreeList.this.modCount; /** * The node containing the last element next() returned. This is null if we have yet to call next() or we have * called remove() since the last call to next(). */ private TreeListNode node; /** The node containing next(). This is null if we have reached the end of the list. */ private TreeListNode nextNode; /** Whether we have (successfully) called next(). */ private boolean haveCalledNext; private TreeListIterator() { if (root.isLeaf()) { nextNode = null; } else { nextNode = root.min(); } } @Override public boolean hasNext() { return nextNode != null; } @Override public T next() { if (nextNode == null) { throw new NoSuchElementException("Reached the end of the list"); } else if (TreeList.this.modCount != modCount) { throw new ConcurrentModificationException(); } haveCalledNext = true; node = nextNode; nextNode = nextNode.successor(); return node.value; } @Override public void remove() { if (node == null) { if (!haveCalledNext) { throw new IllegalStateException("Must call next() before calling remove()"); } else { throw new IllegalStateException("Already removed this element"); } } else if (TreeList.this.modCount != modCount) { throw new ConcurrentModificationException(); } root = node.remove(); node = null; TreeList.this.modCount++; modCount = TreeList.this.modCount; } } @Override public Iterator iterator() { return new TreeListIterator(); } /** The class for TreeList.listIterator. */ private class TreeListListIterator implements ListIterator { /** The value of TreeList.this.modCount we require to continue iteration without concurrent modification. */ private int modCount = TreeList.this.modCount; /** The current return value for nextIndex(). */ private int nextIndex; /** The node for next(), or null if hasNext() is false. */ private TreeListNode nextNode; /** The node for previous(), or null if hasPrevious() is false. */ private TreeListNode prevNode; /** Whether we have called next() or previous(). */ private boolean haveCalledNextOrPrevious; /** Whether we (successfully) called next() more recently than previous(). */ private boolean justCalledNext; /** * Whether we have (successfully) called remove() or "add" since the last (successful) call to next() or * previous(). */ private boolean haveModified; /** * Constructs a new TreeListListIterator. * @param index The starting index, as in the "index" argument to listIterator. This method assumes that * 0 <= index < root.size. */ private TreeListListIterator(int index) { nextIndex = index; if (index > 0) { prevNode = getNode(index - 1); nextNode = prevNode.successor(); } else { prevNode = null; if (root.size > 0) { nextNode = root.min(); } else { nextNode = null; } } } @Override public boolean hasNext() { if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } return nextNode != null; } @Override public T next() { if (nextNode == null) { throw new NoSuchElementException("Reached the end of the list"); } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } haveCalledNextOrPrevious = true; justCalledNext = true; haveModified = false; nextIndex++; prevNode = nextNode; nextNode = nextNode.successor(); return prevNode.value; } @Override public int nextIndex() { if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } return nextIndex; } @Override public boolean hasPrevious() { if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } return prevNode != null; } @Override public T previous() { if (prevNode == null) { throw new NoSuchElementException("Reached the beginning of the list"); } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } haveCalledNextOrPrevious = true; justCalledNext = false; haveModified = false; nextIndex--; nextNode = prevNode; prevNode = prevNode.predecessor(); return nextNode.value; } @Override public int previousIndex() { return nextIndex - 1; } @Override public void set(T value) { if (!haveCalledNextOrPrevious) { throw new IllegalStateException("Must call next() or previous() before calling \"set\""); } else if (haveModified) { throw new IllegalStateException("Already modified the list at this position"); } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } if (justCalledNext) { prevNode.value = value; } else { nextNode.value = value; } TreeList.this.modCount++; modCount = TreeList.this.modCount; } @Override public void add(T value) { if (haveModified) { throw new IllegalStateException("Already modified the list at this position"); } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } // Create the new node TreeListNode newNode = new TreeListNode(value);; newNode.left = leaf; newNode.right = leaf; newNode.isRed = true; // Insert newNode. There is guaranteed to be a leaf child of prevNode or nextNode where we can insert it. if (nextNode != null && nextNode.left.isLeaf()) { nextNode.left = newNode; newNode.parent = nextNode; } else if (prevNode != null) { prevNode.right = newNode; newNode.parent = prevNode; } else { root = newNode; } prevNode = newNode; root = newNode.fixInsertion(); nextIndex++; haveModified = true; TreeList.this.modCount++; modCount = TreeList.this.modCount; } @Override public void remove() { if (!haveCalledNextOrPrevious) { throw new IllegalStateException("Must call next() or previous() before calling remove()"); } else if (haveModified) { throw new IllegalStateException("Already modified the list at this position"); } else if (modCount != TreeList.this.modCount) { throw new ConcurrentModificationException(); } if (justCalledNext) { TreeListNode predecessor = prevNode.predecessor(); root = prevNode.remove(); prevNode = predecessor; } else { TreeListNode successor = nextNode.successor(); root = nextNode.remove(); nextNode = successor; } haveModified = true; TreeList.this.modCount++; modCount = TreeList.this.modCount; } } @Override public ListIterator listIterator(int index) { if (index < 0 || index > root.size) { throw new IndexOutOfBoundsException("Index " + index + " is not in the range [0, " + root.size + "]"); } return new TreeListListIterator(index); } }