diff --git a/.classpath b/.classpath
new file mode 100644
index 000000000..72c2ba61a
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/.project b/.project
new file mode 100644
index 000000000..ec61c803b
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+
+
+ DataStructures
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..7341ab168
--- /dev/null
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/.settings/org.eclipse.jdt.ui.prefs b/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 000000000..a15aefcfd
--- /dev/null
+++ b/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,60 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=false
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.convert_functional_interfaces=false
+sp_cleanup.convert_to_enhanced_for_loop=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.format_source_code=false
+sp_cleanup.format_source_code_changes_only=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.organize_imports=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_casts=false
+sp_cleanup.remove_unnecessary_nls_tags=false
+sp_cleanup.remove_unused_imports=false
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_type_arguments=false
diff --git a/README.md b/README.md
index bc2c15b8e..0883fb752 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,60 @@
# RedBlackNode
-`RedBlackNode` is a Java implementation of red-black trees. By subclassing `RedBlackNode`, clients can add arbitrary data and augmentation information to each node. (self-balancing binary search tree, self-balancing BST, augment, augmented)
+`RedBlackNode` is a Java implementation of red-black trees. By subclassing
+`RedBlackNode`, clients can add arbitrary data and augmentation information to
+each node. (self-balancing binary search tree, self-balancing BST, augment,
+augmented)
+
+# Features
+* Supports min, max, root, predecessor, successor, insert, remove, rotate,
+ split, concatenate, create balanced tree, and compare operations. The running
+ time of each operation has optimal big O bounds.
+* Supports arbitrary augmentation by overriding `augment()`. Examples of
+ augmentation are the number of non-leaf nodes in a subtree and the sum of the
+ values in a subtree.
+* The parent and child links and the color are public fields. This gives
+ clients flexibility. However, it is possible for a client to violate the
+ red-black or BST properties.
+* "Assert is valid" methods allow clients to check for errors in the structure
+ or contents of a red-black tree. This is useful for debugging.
+* As a bonus (a proof of concept and a test case), this includes the `TreeList`
+ class, a `List` implementation backed by a red-black tree augmented by subtree
+ size.
+* Tested in Java 6.0 and 7.0. It might also work in Java 5.0.
+
+# Limitations:
+* Augmentations that depend on information stored in a node's ancestors are not
+ (easily) supported. For example, augmenting each node with the number of
+ nodes in the left subtree is not (easily and efficiently) supported, because
+ in order to perform a right rotation, we would need to use the parent's
+ augmentation information. However, `RedBlackNode` supports augmenting each
+ node with the number of nodes in the subtree, which is basically equivalent.
+* The running time of each operation has optimal big O bounds. However, beyond
+ this, no special effort has been made to optimize performance.
+
+# Example
+
+/** Red-black tree augmented by the sum of the values in the subtree. */
+public class SumNode extends RedBlackNode {
+ public int value;
+ public int sum;
+
+ public SumNode(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean augment() {
+ int newSum = value + left.sum + right.sum;
+ if (newSum == sum) {
+ return false;
+ } else {
+ sum = newSum;
+ return true;
+ }
+ }
+}
+
+
+# Documentation
+For more detailed instructions, check the source code to see the full API and
+Javadoc documentation.
diff --git a/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java b/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java
new file mode 100644
index 000000000..8b5102471
--- /dev/null
+++ b/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderCollection.java
@@ -0,0 +1,40 @@
+package com.github.btrekkie.arbitrary_order_collection;
+
+import java.util.Comparator;
+
+/**
+ * Provides objects ordered in an arbitrary, but consistent fashion, through the createValue() method. To determine the
+ * relative order of two values, use ArbitraryOrderValue.compareTo. We may only compare values on which we have not
+ * called "remove" that were created in the same ArbitraryOrderCollection instance. Note that despite the name,
+ * ArbitraryOrderCollection does not implement Collection.
+ */
+/* We implement an ArbitraryOrderCollection using a red-black tree. We order the nodes arbitrarily.
+ */
+public class ArbitraryOrderCollection {
+ /** The Comparator for ordering ArbitraryOrderNodes. */
+ private static final Comparator NODE_COMPARATOR = new Comparator() {
+ @Override
+ public int compare(ArbitraryOrderNode node1, ArbitraryOrderNode node2) {
+ return 0;
+ }
+ };
+
+ /** The root node of the tree. */
+ private ArbitraryOrderNode root = new ArbitraryOrderNode();
+
+ /** Adds and returns a new value for ordering. */
+ public ArbitraryOrderValue createValue() {
+ ArbitraryOrderNode node = new ArbitraryOrderNode();
+ root = root.insert(node, true, NODE_COMPARATOR);
+ return new ArbitraryOrderValue(node);
+ }
+
+ /**
+ * Removes the specified value from this collection. Assumes we obtained the value by calling createValue() on this
+ * instance of ArbitraryOrderCollection. After calling "remove" on a value, we may no longer compare it to other
+ * values.
+ */
+ public void remove(ArbitraryOrderValue value) {
+ root = value.node.remove();
+ }
+}
diff --git a/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java b/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java
new file mode 100644
index 000000000..b5d28d9be
--- /dev/null
+++ b/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderNode.java
@@ -0,0 +1,8 @@
+package com.github.btrekkie.arbitrary_order_collection;
+
+import com.github.btrekkie.red_black_node.RedBlackNode;
+
+/** A node in an ArbitraryOrderCollection tree. See ArbitraryOrderCollection. */
+class ArbitraryOrderNode extends RedBlackNode {
+
+}
diff --git a/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java b/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java
new file mode 100644
index 000000000..c12b995ad
--- /dev/null
+++ b/src/com/github/btrekkie/arbitrary_order_collection/ArbitraryOrderValue.java
@@ -0,0 +1,19 @@
+package com.github.btrekkie.arbitrary_order_collection;
+
+/**
+ * A value in an ArbitraryOrderCollection. To determine the relative order of two values in the same collection, call
+ * compareTo.
+ */
+public class ArbitraryOrderValue implements Comparable {
+ /** The node that establishes this value's relative position. */
+ final ArbitraryOrderNode node;
+
+ ArbitraryOrderValue(ArbitraryOrderNode node) {
+ this.node = node;
+ }
+
+ @Override
+ public int compareTo(ArbitraryOrderValue other) {
+ return node.compareTo(other.node);
+ }
+}
diff --git a/src/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java b/src/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java
new file mode 100644
index 000000000..9d855d17d
--- /dev/null
+++ b/src/com/github/btrekkie/arbitrary_order_collection/test/ArbitraryOrderCollectionTest.java
@@ -0,0 +1,72 @@
+package com.github.btrekkie.arbitrary_order_collection.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderCollection;
+import com.github.btrekkie.arbitrary_order_collection.ArbitraryOrderValue;
+
+public class ArbitraryOrderCollectionTest {
+ /** Tests ArbitraryOrderCollection. */
+ @Test
+ public void test() {
+ ArbitraryOrderCollection collection = new ArbitraryOrderCollection();
+ List values1 = new ArrayList(5);
+ ArbitraryOrderValue value = collection.createValue();
+ assertEquals(0, value.compareTo(value));
+ values1.add(value);
+ for (int i = 0; i < 4; i++) {
+ values1.add(collection.createValue());
+ }
+ Collections.sort(values1);
+ List values2 = new ArrayList(10);
+ for (int i = 0; i < 10; i++) {
+ value = collection.createValue();
+ values2.add(value);
+ }
+ for (int i = 0; i < 5; i++) {
+ collection.remove(values2.get(2 * i));
+ }
+ assertEquals(0, values1.get(0).compareTo(values1.get(0)));
+ assertTrue(values1.get(0).compareTo(values1.get(1)) < 0);
+ assertTrue(values1.get(1).compareTo(values1.get(0)) > 0);
+ assertTrue(values1.get(4).compareTo(values1.get(2)) > 0);
+ assertTrue(values1.get(0).compareTo(values1.get(4)) < 0);
+
+ collection = new ArbitraryOrderCollection();
+ values1 = new ArrayList(1000);
+ for (int i = 0; i < 1000; i++) {
+ value = collection.createValue();
+ values1.add(value);
+ }
+ for (int i = 0; i < 500; i++) {
+ collection.remove(values1.get(2 * i));
+ }
+ values2 = new ArrayList(500);
+ for (int i = 0; i < 500; i++) {
+ values2.add(values1.get(2 * i + 1));
+ }
+ for (int i = 0; i < 500; i++) {
+ values2.get(0).compareTo(values2.get(i));
+ }
+ Collections.sort(values2);
+ for (int i = 0; i < 500; i++) {
+ collection.createValue();
+ }
+ for (int i = 0; i < 499; i++) {
+ assertTrue(values2.get(i).compareTo(values2.get(i + 1)) < 0);
+ assertTrue(values2.get(i + 1).compareTo(values2.get(i)) > 0);
+ }
+ for (int i = 1; i < 500; i++) {
+ assertEquals(0, values2.get(i).compareTo(values2.get(i)));
+ assertTrue(values2.get(0).compareTo(values2.get(i)) < 0);
+ assertTrue(values2.get(i).compareTo(values2.get(0)) > 0);
+ }
+ }
+}
diff --git a/src/com/github/btrekkie/interval_tree/IntervalTree.java b/src/com/github/btrekkie/interval_tree/IntervalTree.java
new file mode 100644
index 000000000..d5e88624e
--- /dev/null
+++ b/src/com/github/btrekkie/interval_tree/IntervalTree.java
@@ -0,0 +1,60 @@
+package com.github.btrekkie.interval_tree;
+
+/**
+ * An interval tree data structure, which supports adding or removing an interval and finding an arbitrary interval that
+ * contains a specified value.
+ */
+/* The interval tree is ordered in ascending order of the start an interval, with ties broken by the end of the
+ * interval. Each node is augmented with the maximum ending value of an interval in the subtree rooted at the node.
+ */
+public class IntervalTree {
+ /** The root node of the tree. */
+ private IntervalTreeNode root = IntervalTreeNode.LEAF;
+
+ /** Adds the specified interval to this. */
+ public void addInterval(IntervalTreeInterval interval) {
+ root = root.insert(new IntervalTreeNode(interval), true, null);
+ }
+
+ /**
+ * Removes the specified interval from this, if it is present.
+ * @param interval The interval.
+ * @return Whether the interval was present.
+ */
+ public boolean removeInterval(IntervalTreeInterval interval) {
+ IntervalTreeNode node = root;
+ while (!node.isLeaf()) {
+ if (interval.start < node.interval.start) {
+ node = node.left;
+ } else if (interval.start > node.interval.start) {
+ node = node.right;
+ } else if (interval.end < node.interval.end) {
+ node = node.left;
+ } else if (interval.end > node.interval.end) {
+ node = node.right;
+ } else {
+ root = node.remove();
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns an aribtrary IntervalTreeInterval in this that contains the specified value. Returns null if there is no
+ * such interval.
+ */
+ public IntervalTreeInterval findInterval(double value) {
+ IntervalTreeNode node = root;
+ while (!node.isLeaf()) {
+ if (value >= node.interval.start && value <= node.interval.end) {
+ return node.interval;
+ } else if (value <= node.left.maxEnd) {
+ node = node.left;
+ } else {
+ node = node.right;
+ }
+ }
+ return null;
+ }
+}
diff --git a/src/com/github/btrekkie/interval_tree/IntervalTreeInterval.java b/src/com/github/btrekkie/interval_tree/IntervalTreeInterval.java
new file mode 100644
index 000000000..e9f38b9d9
--- /dev/null
+++ b/src/com/github/btrekkie/interval_tree/IntervalTreeInterval.java
@@ -0,0 +1,28 @@
+package com.github.btrekkie.interval_tree;
+
+/**
+ * An inclusive range of values [start, end]. Two intervals are equal if they have the same starting and ending values.
+ */
+public class IntervalTreeInterval {
+ /** The smallest value in the range. */
+ public final double start;
+
+ /** The largest value in the range. */
+ public final double end;
+
+ public IntervalTreeInterval(double start, double end) {
+ if (start > end) {
+ throw new IllegalArgumentException("The end of the range must be at most the start");
+ }
+ this.start = start;
+ this.end = end;
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof IntervalTreeInterval)) {
+ return false;
+ }
+ IntervalTreeInterval interval = (IntervalTreeInterval)obj;
+ return start == interval.start && end == interval.end;
+ }
+}
diff --git a/src/com/github/btrekkie/interval_tree/IntervalTreeNode.java b/src/com/github/btrekkie/interval_tree/IntervalTreeNode.java
new file mode 100644
index 000000000..d75004e77
--- /dev/null
+++ b/src/com/github/btrekkie/interval_tree/IntervalTreeNode.java
@@ -0,0 +1,68 @@
+package com.github.btrekkie.interval_tree;
+
+import com.github.btrekkie.red_black_node.RedBlackNode;
+
+/**
+ * A node in an IntervalTree. See the comments for the implementation of IntervalTree. Its compareTo method orders
+ * nodes as suggested in the comments for the implementation of IntervalTree.
+ */
+class IntervalTreeNode extends RedBlackNode {
+ /** The dummy leaf node. */
+ public static final IntervalTreeNode LEAF = new IntervalTreeNode();
+
+ /** The interval stored in this node. */
+ public IntervalTreeInterval interval;
+
+ /** The maximum ending value of an interval in the subtree rooted at this node. */
+ public double maxEnd;
+
+ public IntervalTreeNode(IntervalTreeInterval interval) {
+ this.interval = interval;
+ maxEnd = interval.end;
+ }
+
+ /** Constructs a new dummy leaf node. */
+ private IntervalTreeNode() {
+ interval = null;
+ maxEnd = Double.NEGATIVE_INFINITY;
+ }
+
+ @Override
+ public boolean augment() {
+ double newMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd));
+ if (newMaxEnd == maxEnd) {
+ return false;
+ } else {
+ maxEnd = newMaxEnd;
+ return true;
+ }
+ }
+
+ @Override
+ public void assertNodeIsValid() {
+ double expectedMaxEnd;
+ if (isLeaf()) {
+ expectedMaxEnd = Double.NEGATIVE_INFINITY;
+ } else {
+ expectedMaxEnd = Math.max(interval.end, Math.max(left.maxEnd, right.maxEnd));
+ }
+ if (maxEnd != expectedMaxEnd) {
+ throw new RuntimeException("The node's maxEnd does not match that of the children");
+ }
+ }
+
+ @Override
+ public int compareTo(IntervalTreeNode other) {
+ if (interval.start != interval.end) {
+ return Double.compare(interval.start, other.interval.start);
+ } else {
+ return Double.compare(interval.end, other.interval.end);
+ }
+ }
+
+ @Override
+ public void assertSubtreeIsValid() {
+ super.assertSubtreeIsValid();
+ assertOrderIsValid(null);
+ }
+}
diff --git a/src/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java b/src/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java
new file mode 100644
index 000000000..cf0dbdfa6
--- /dev/null
+++ b/src/com/github/btrekkie/interval_tree/test/IntervalTreeTest.java
@@ -0,0 +1,48 @@
+package com.github.btrekkie.interval_tree.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import com.github.btrekkie.interval_tree.IntervalTree;
+import com.github.btrekkie.interval_tree.IntervalTreeInterval;
+
+public class IntervalTreeTest {
+ /** Tests IntervalTree. */
+ @Test
+ public void test() {
+ IntervalTree tree = new IntervalTree();
+ assertNull(tree.findInterval(0.5));
+ assertNull(tree.findInterval(-1));
+ tree.addInterval(new IntervalTreeInterval(5, 7));
+ tree.addInterval(new IntervalTreeInterval(42, 48));
+ tree.addInterval(new IntervalTreeInterval(-1, 2));
+ tree.addInterval(new IntervalTreeInterval(6, 12));
+ tree.addInterval(new IntervalTreeInterval(21, 23));
+ assertTrue(tree.removeInterval(new IntervalTreeInterval(-1, 2)));
+ assertFalse(tree.removeInterval(new IntervalTreeInterval(-1, 2)));
+ tree.addInterval(new IntervalTreeInterval(-6, -2));
+ assertEquals(new IntervalTreeInterval(6, 12), tree.findInterval(8));
+ assertNull(tree.findInterval(0));
+ assertEquals(new IntervalTreeInterval(21, 23), tree.findInterval(21));
+ assertEquals(new IntervalTreeInterval(42, 48), tree.findInterval(48));
+ IntervalTreeInterval interval = tree.findInterval(6.5);
+ assertTrue(new IntervalTreeInterval(5, 7).equals(interval) || new IntervalTreeInterval(6, 12).equals(interval));
+
+ tree = new IntervalTree();
+ for (int i = 0; i < 500; i++) {
+ tree.addInterval(new IntervalTreeInterval(2 * i, 2 * i + 1));
+ }
+ for (int i = 0; i < 250; i++) {
+ tree.removeInterval(new IntervalTreeInterval(4 * i + 2, 4 * i + 3));
+ }
+ assertNull(tree.findInterval(123.5));
+ assertEquals(new IntervalTreeInterval(124, 125), tree.findInterval(124.5));
+ assertEquals(new IntervalTreeInterval(776, 777), tree.findInterval(776));
+ assertEquals(new IntervalTreeInterval(0, 1), tree.findInterval(0.5));
+ assertEquals(new IntervalTreeInterval(996, 997), tree.findInterval(997));
+ }
+}
diff --git a/src/com/github/btrekkie/red_black_node/RedBlackNode.java b/src/com/github/btrekkie/red_black_node/RedBlackNode.java
new file mode 100644
index 000000000..8393fafeb
--- /dev/null
+++ b/src/com/github/btrekkie/red_black_node/RedBlackNode.java
@@ -0,0 +1,1224 @@
+package com.github.btrekkie.red_black_node;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * A node in a red-black tree ( https://en.wikipedia.org/wiki/Red%E2%80%93black_tree ). The RedBlackNode class provides
+ * methods for performing various standard operations. The leaf nodes in a tree are dummy nodes colored black that do
+ * not contain any values. It is recommended that all of the leaf nodes in a given tree be the same RedBlackNode
+ * instance, to save space. The root of an empty tree is a leaf node, as opposed to null.
+ *
+ * Subclasses may add arbitrary information to the node. For example, if we were to use a RedBlackNode subclass to
+ * implement a sorted set, the subclass should have a field storing an element in the set. Subclasses can augment the
+ * tree with arbitrary information by overriding augment().
+ *
+ * The internals of the node are exposed publicly, so clients can access or alter a node arbitrarily. This gives
+ * clients flexibility. It is possible for a client to violate the red-black or BST properties.
+ *
+ * @param The type of node in the tree. For example, we might have "class FooNode extends
+ * RedBlackNode>".
+ */
+public abstract class RedBlackNode> implements Comparable {
+ /** A Comparator that compares Comparable elements using their natural order. */
+ private static final Comparator> NATURAL_ORDER = new Comparator>() {
+ @Override
+ public int compare(Comparable value1, Comparable value2) {
+ return value1.compareTo(value2);
+ }
+ };
+
+ /** The parent of this node, if any. "parent" is null if this is a leaf node. */
+ public N parent;
+
+ /** The left child of this node. "left" is null if this is a leaf node. */
+ public N left;
+
+ /** The right child of this node. "right" is null if this is a leaf node. */
+ public N right;
+
+ /** Whether the node is colored red, as opposed to black. */
+ public boolean isRed;
+
+ /**
+ * Sets any augmentation information about the subtree rooted at this node that is stored in this node. For
+ * example, if we augment each node by subtree size (the number of non-leaf nodes in the subtree), this method would
+ * set the size field of this node to be equal to the size field of the left child plus the size field of the right
+ * child plus one.
+ *
+ * "Augmentation information" is information that we can compute about a subtree rooted at some node, preferably
+ * based only on the augmentation information in the node's two children and the information in the node. Examples
+ * of augmentation information are the sum of the values in a subtree and the number of non-leaf nodes in a subtree.
+ * Augmentation information may not depend on the colors of the nodes.
+ *
+ * This method returns whether the augmentation information in any of the ancestors of this node might have been
+ * affected by changes in this subtree since the last call to augment(). In the usual case, where the augmentation
+ * information depends only on the information in this node and the augmentation information in its immediate
+ * children, this is equivalent to whether the augmentation information changed as a result of this call to
+ * augment(). For example, in the case of subtree size, this returns whether the value of the size field prior to
+ * calling augment() differed from the size field of the left child plus the size field of the right child plus one.
+ * False positives are permitted. The return value is unspecified if we have not called augment() on this subtree
+ * before.
+ *
+ * This method may assume that this is not a leaf node. It may not assume that the augmentation information stored
+ * in any of the tree's nodes is correct. However, if the augmentation information stored in all of the node's
+ * descendants is correct, then the augmentation information stored in this node must be correct after calling
+ * augment().
+ */
+ public boolean augment() {
+ return false;
+ }
+
+ /**
+ * Throws a RuntimeException if we detect that this node locally violates any invariants specific to this subclass
+ * of RedBlackNode. For example, if this stores the size of the subtree rooted at this node, this should throw a
+ * RuntimeException if the size field of this is not equal to the size field of the left child plus the size field
+ * of the right child plus one. Note that we may call this on a leaf node.
+ *
+ * assertSubtreeIsValid() calls assertNodeIsValid() on each node, or at least starts to do so until it detects a
+ * problem. assertNodeIsValid() should assume the node is in a tree that satisfies all properties common to all
+ * red-black trees, as assertSubtreeIsValid() is responsible for such checks. assertNodeIsValid() should be
+ * "downward-looking", i.e. it should ignore any information in "parent", and it should be "local", i.e. it should
+ * only check a constant number of descendants. To include "global" checks, such as verifying the BST property
+ * concerning ordering, override assertSubtreeIsValid(). assertOrderIsValid is useful for checking the BST
+ * property.
+ */
+ public void assertNodeIsValid() {
+
+ }
+
+ /** Returns whether this is a leaf node. */
+ public boolean isLeaf() {
+ return left == null;
+ }
+
+ /** Returns the root of the tree that contains this node. */
+ public N root() {
+ @SuppressWarnings("unchecked")
+ N node = (N)this;
+ while (node.parent != null) {
+ node = node.parent;
+ }
+ return node;
+ }
+
+ /** Returns the first node in the subtree rooted at this node, if any. */
+ public N min() {
+ if (isLeaf()) {
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ N node = (N)this;
+ while (!node.left.isLeaf()) {
+ node = node.left;
+ }
+ return node;
+ }
+
+ /** Returns the last node in the subtree rooted at this node, if any. */
+ public N max() {
+ if (isLeaf()) {
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ N node = (N)this;
+ while (!node.right.isLeaf()) {
+ node = node.right;
+ }
+ return node;
+ }
+
+ /** Returns the node immediately before this in the tree that contains this node, if any. */
+ public N predecessor() {
+ if (!left.isLeaf()) {
+ N node;
+ for (node = left; !node.right.isLeaf(); node = node.right);
+ return node;
+ } else if (parent == null) {
+ return null;
+ } else {
+ @SuppressWarnings("unchecked")
+ N node = (N)this;
+ while (node.parent != null && node.parent.left == node) {
+ node = node.parent;
+ }
+ return node.parent;
+ }
+ }
+
+ /** Returns the node immediately after this in the tree that contains this node, if any. */
+ public N successor() {
+ if (!right.isLeaf()) {
+ N node;
+ for (node = right; !node.left.isLeaf(); node = node.left);
+ return node;
+ } else if (parent == null) {
+ return null;
+ } else {
+ @SuppressWarnings("unchecked")
+ N node = (N)this;
+ while (node.parent != null && node.parent.right == node) {
+ node = node.parent;
+ }
+ return node.parent;
+ }
+ }
+
+ /**
+ * Performs a left rotation about this node. This method assumes that !right.isLeaf(). It calls augment() on this
+ * node and on its resulting parent.
+ * @return The return value from calling augment() on the resulting parent.
+ */
+ public boolean rotateLeft() {
+ if (right.isLeaf()) {
+ throw new IllegalArgumentException("The right child is a leaf");
+ }
+ N newParent = right;
+ right = newParent.left;
+ @SuppressWarnings("unchecked")
+ N nThis = (N)this;
+ if (!right.isLeaf()) {
+ right.parent = nThis;
+ }
+ newParent.parent = parent;
+ parent = newParent;
+ newParent.left = nThis;
+ if (newParent.parent != null) {
+ if (newParent.parent.left == this) {
+ newParent.parent.left = newParent;
+ } else {
+ newParent.parent.right = newParent;
+ }
+ }
+ augment();
+ return newParent.augment();
+ }
+
+ /**
+ * Performs a right rotation about this node. This method assumes that !left.isLeaf(). It calls augment() on this
+ * node and on its resulting parent.
+ * @return The return value from calling augment() on the resulting parent.
+ */
+ public boolean rotateRight() {
+ if (left.isLeaf()) {
+ throw new IllegalArgumentException("The left child is a leaf");
+ }
+ N newParent = left;
+ left = newParent.right;
+ @SuppressWarnings("unchecked")
+ N nThis = (N)this;
+ if (!left.isLeaf()) {
+ left.parent = nThis;
+ }
+ newParent.parent = parent;
+ parent = newParent;
+ newParent.right = nThis;
+ if (newParent.parent != null) {
+ if (newParent.parent.left == this) {
+ newParent.parent.left = newParent;
+ } else {
+ newParent.parent.right = newParent;
+ }
+ }
+ augment();
+ return newParent.augment();
+ }
+
+ /**
+ * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements
+ * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be
+ * red. node.isRed must initially be true. The method performs any rotations by calling rotateLeft() and
+ * rotateRight().
+ * @param augment Whether to set the augmentation information for "node" and its ancestors, by calling augment().
+ */
+ public void fixInsertion(boolean augment) {
+ if (!isRed) {
+ throw new IllegalArgumentException("The node must be red");
+ }
+ boolean changed;
+ if (augment) {
+ changed = augment();
+ } else {
+ changed = false;
+ }
+
+ RedBlackNode node = this;
+ while (node.parent != null && node.parent.isRed) {
+ N parent = node.parent;
+ N grandparent = parent.parent;
+ if (grandparent.left.isRed && grandparent.right.isRed) {
+ grandparent.left.isRed = false;
+ grandparent.right.isRed = false;
+ grandparent.isRed = true;
+ if (changed) {
+ changed = parent.augment();
+ if (changed) {
+ changed = grandparent.augment();
+ }
+ }
+ node = grandparent;
+ } else {
+ if (parent.left == node) {
+ if (grandparent.right == parent) {
+ parent.rotateRight();
+ node = parent;
+ parent = node.parent;
+ }
+ } else if (grandparent.left == parent) {
+ parent.rotateLeft();
+ node = parent;
+ parent = node.parent;
+ }
+ if (parent.left == node) {
+ boolean grandparentChanged = grandparent.rotateRight();
+ if (augment) {
+ changed = grandparentChanged;
+ }
+ } else {
+ boolean grandparentChanged = grandparent.rotateLeft();
+ if (augment) {
+ changed = grandparentChanged;
+ }
+ }
+ parent.isRed = false;
+ grandparent.isRed = true;
+ node = parent;
+ break;
+ }
+ }
+
+ if (node.parent == null) {
+ node.isRed = false;
+ }
+ if (changed) {
+ for (node = node.parent; node != null; node = node.parent) {
+ if (!node.augment()) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs red-black insertion fixup. To be more precise, this fixes a tree that satisfies all of the requirements
+ * of red-black trees, except that this may be a red child of a red node, and if this is the root, the root may be
+ * red. node.isRed must initially be true. The method performs any rotations by calling rotateLeft() and
+ * rotateRight().
+ */
+ public void fixInsertion() {
+ fixInsertion(true);
+ }
+
+ /** Returns a Comparator that compares instances of N using their natural order, as in N.compare. */
+ private Comparator naturalOrder() {
+ @SuppressWarnings("unchecked")
+ Comparator comparator = (Comparator)NATURAL_ORDER;
+ return comparator;
+ }
+
+ /**
+ * Inserts the specified node into the tree rooted at this node. Assumes this is the root. We treat newNode as a
+ * solitary node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed
+ * fields.
+ *
+ * If it is not efficient or convenient for a subclass to find the location for a node using a Comparator, then it
+ * should manually add the node to the appropriate location, color it red, and call fixInsertion().
+ *
+ * @param newNode The node to insert.
+ * @param allowDuplicates Whether to insert newNode if there is an equal node in the tree. To check whether we
+ * inserted newNode, check whether newNode.parent is null and the return value differs from newNode.
+ * @param comparator A comparator indicating where to put the node. If this is null, we use the nodes' natural
+ * order, as in N.compare.
+ * @return The root of the resulting tree.
+ */
+ public N insert(N newNode, boolean allowDuplicates, Comparator super N> comparator) {
+ if (parent != null) {
+ throw new IllegalArgumentException("This is not the root of a tree");
+ }
+ @SuppressWarnings("unchecked")
+ N nThis = (N)this;
+ if (isLeaf()) {
+ newNode.isRed = false;
+ newNode.left = nThis;
+ newNode.right = nThis;
+ newNode.parent = null;
+ newNode.augment();
+ return newNode;
+ }
+ if (comparator == null) {
+ comparator = naturalOrder();
+ }
+
+ N node = nThis;
+ int comparison;
+ while (true) {
+ comparison = comparator.compare(newNode, node);
+ if (comparison < 0) {
+ if (!node.left.isLeaf()) {
+ node = node.left;
+ } else {
+ newNode.left = node.left;
+ newNode.right = node.left;
+ node.left = newNode;
+ newNode.parent = node;
+ break;
+ }
+ } else if (comparison > 0 || allowDuplicates) {
+ if (!node.right.isLeaf()) {
+ node = node.right;
+ } else {
+ newNode.left = node.right;
+ newNode.right = node.right;
+ node.right = newNode;
+ newNode.parent = node;
+ break;
+ }
+ } else {
+ newNode.parent = null;
+ return nThis;
+ }
+ }
+ newNode.isRed = true;
+ newNode.fixInsertion();
+ return root();
+ }
+
+ /**
+ * Moves this node to its successor's former position in the tree and vice versa, i.e. sets the "left", "right",
+ * "parent", and isRed fields of each.
+ * @return The node with which we swapped.
+ */
+ private N swapWithSuccessor() {
+ N replacement = successor();
+ boolean oldReplacementIsRed = replacement.isRed;
+ N oldReplacementLeft = replacement.left;
+ N oldReplacementRight = replacement.right;
+ N oldReplacementParent = replacement.parent;
+
+ replacement.isRed = isRed;
+ replacement.left = left;
+ replacement.right = right;
+ replacement.parent = parent;
+ if (parent != null) {
+ if (parent.left == this) {
+ parent.left = replacement;
+ } else {
+ parent.right = replacement;
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ N nThis = (N)this;
+ isRed = oldReplacementIsRed;
+ left = oldReplacementLeft;
+ right = oldReplacementRight;
+ if (oldReplacementParent == this) {
+ parent = replacement;
+ parent.right = nThis;
+ } else {
+ parent = oldReplacementParent;
+ parent.left = nThis;
+ }
+
+ replacement.right.parent = replacement;
+ if (!replacement.left.isLeaf()) {
+ replacement.left.parent = replacement;
+ }
+ if (!right.isLeaf()) {
+ right.parent = nThis;
+ }
+ return replacement;
+ }
+
+ /**
+ * Performs red-black deletion fixup. To be more precise, this fixes a tree that satisfies all of the requirements
+ * of red-black trees, except that all paths from the root to a leaf that pass through the sibling of this node have
+ * one fewer black node than all other root-to-leaf paths.
+ */
+ private void fixSiblingDeletion() {
+ RedBlackNode sibling = this;
+ boolean changed = true;
+ while (true) {
+ N parent = sibling.parent;
+ if (sibling.isRed) {
+ parent.isRed = true;
+ sibling.isRed = false;
+ if (parent.left == sibling) {
+ changed = parent.rotateRight();
+ sibling = parent.left;
+ } else {
+ changed = parent.rotateLeft();
+ sibling = parent.right;
+ }
+ } else if (!sibling.left.isRed && !sibling.right.isRed) {
+ sibling.isRed = true;
+ if (parent.isRed) {
+ parent.isRed = false;
+ break;
+ } else {
+ if (changed) {
+ changed = parent.augment();
+ }
+ N grandparent = parent.parent;
+ if (grandparent == null) {
+ break;
+ } else if (grandparent.left == parent) {
+ sibling = grandparent.right;
+ } else {
+ sibling = grandparent.left;
+ }
+ }
+ } else {
+ if (sibling == parent.left) {
+ if (!sibling.left.isRed) {
+ sibling.rotateLeft();
+ sibling = sibling.parent;
+ }
+ } else if (!sibling.right.isRed) {
+ sibling.rotateRight();
+ sibling = sibling.parent;
+ }
+ sibling.isRed = parent.isRed;
+ parent.isRed = false;
+ if (sibling == parent.left) {
+ sibling.left.isRed = false;
+ changed = parent.rotateRight();
+ } else {
+ sibling.right.isRed = false;
+ changed = parent.rotateLeft();
+ }
+ break;
+ }
+ }
+
+ if (changed) {
+ for (N parent = sibling.parent; parent != null; parent = parent.parent) {
+ if (!parent.augment()) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes this node from the tree that contains it. The effect of this method on the fields of this node is
+ * unspecified. This method is more efficient than remove() if augment() might return false.
+ *
+ * If the node has two children, we begin by moving the node's successor to its former position, by changing its
+ * "left", "right", "parent", and "isBlack" fields.
+ */
+ public void removeWithoutGettingRoot() {
+ N replacement;
+ if (left.isLeaf() || right.isLeaf()) {
+ replacement = null;
+ } else {
+ replacement = swapWithSuccessor();
+ }
+
+ N child;
+ if (!left.isLeaf()) {
+ child = left;
+ } else if (!right.isLeaf()) {
+ child = right;
+ } else {
+ child = null;
+ }
+
+ if (child != null) {
+ child.parent = parent;
+ if (parent != null) {
+ if (parent.left == this) {
+ parent.left = child;
+ } else {
+ parent.right = child;
+ }
+ }
+ parent = null;
+ child.isRed = false;
+ if (child.parent != null) {
+ N parent;
+ for (parent = child.parent; parent != null; parent = parent.parent) {
+ if (!parent.augment()) {
+ break;
+ }
+ }
+ }
+ } else if (parent != null) {
+ N leaf = left;
+ N parent = this.parent;
+ N sibling;
+ if (parent.left == this) {
+ parent.left = leaf;
+ sibling = parent.right;
+ } else {
+ parent.right = leaf;
+ sibling = parent.left;
+ }
+ this.parent = null;
+ if (!isRed) {
+ RedBlackNode siblingNode = sibling;
+ siblingNode.fixSiblingDeletion();
+ } else {
+ while (parent != null) {
+ if (!parent.augment()) {
+ break;
+ }
+ parent = parent.parent;
+ }
+ }
+ }
+
+ if (replacement != null) {
+ replacement.augment();
+ for (N parent = replacement.parent; parent != null; parent = parent.parent) {
+ if (!parent.augment()) {
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes this node from the tree that contains it. The effect of this method on the fields of this node is
+ * unspecified.
+ *
+ * If the node has two children, we begin by moving the node's successor to its former position, by changing its
+ * "left", "right", "parent", and "isBlack" fields.
+ *
+ * @return The root of the resulting tree.
+ */
+ public N remove() {
+ // Find an arbitrary non-leaf node in the tree other than this node
+ N node;
+ if (parent != null) {
+ node = parent;
+ } else if (!left.isLeaf()) {
+ node = left;
+ } else if (!right.isLeaf()) {
+ node = right;
+ } else {
+ return left;
+ }
+
+ removeWithoutGettingRoot();
+ return node.root();
+ }
+
+ /**
+ * Returns the root of a perfectly height-balanced subtree containing the next "size" nodes from "iterator", in
+ * iteration order. This method is responsible for setting the "left", "right", "parent", and isRed fields of the
+ * nodes, and calling augment() as appropriate. It ignores the initial values of the "left", "right", "parent", and
+ * isRed fields.
+ * @param iterator The nodes.
+ * @param size The number of nodes.
+ * @param height The "height" of the subtree's root node above the deepest leaf in the tree that contains it. Since
+ * insertion fixup is slow if there are too many red nodes and deleteion fixup is slow if there are too few red
+ * nodes, we compromise and have red nodes at every fourth level. We color a node red iff its "height" is equal
+ * to 1 mod 4.
+ * @return The root of the subtree.
+ */
+ private static > N createTree(
+ Iterator extends N> iterator, int size, int height, N leaf) {
+ if (size == 0) {
+ return leaf;
+ } else {
+ N left = createTree(iterator, (size - 1) / 2, height - 1, leaf);
+ N node = iterator.next();
+ N right = createTree(iterator, size / 2, height - 1, leaf);
+ node.isRed = height % 4 == 1;
+ node.left = left;
+ node.right = right;
+ if (!left.isLeaf()) {
+ left.parent = node;
+ }
+ if (!right.isLeaf()) {
+ right.parent = node;
+ }
+ node.augment();
+ return node;
+ }
+ }
+
+ /**
+ * Returns the root of a perfectly height-balanced tree containing the specified nodes, in iteration order. This
+ * method is responsible for setting the "left", "right", "parent", and isRed fields of the nodes, and calling
+ * augment() as appropriate. It ignores the initial values of the "left", "right", "parent", and isRed fields.
+ * @param nodes The nodes.
+ * @param leaf The leaf node.
+ * @return The root of the tree.
+ */
+ public static > N createTree(Collection extends N> nodes, N leaf) {
+ int size = nodes.size();
+ if (size == 0) {
+ return leaf;
+ }
+ int height = 0;
+ for (int subtreeSize = size; subtreeSize > 0; subtreeSize /= 2) {
+ height++;
+ }
+ N node = createTree(nodes.iterator(), size, height, leaf);
+ node.isRed = false;
+ return node;
+ }
+
+ /**
+ * Concatenates to the end of the tree rooted at this node. To be precise, given that all of the nodes in this
+ * precede the node "pivot", which precedes all of the nodes in "last", this returns the root of a tree containing
+ * all of these nodes. This method destroys the trees rooted at "this" and "last". We treat "pivot" as a solitary
+ * node that does not belong to any tree, and we ignore its initial "parent", "left", "right", and isRed fields.
+ * This method assumes that this node and "last" are the roots of their respective trees.
+ *
+ * This method takes O(log N) time. It is more efficient than inserting "pivot" and then calling concatenate(last).
+ * It is considerably more efficient than inserting "pivot" and all of the nodes in "last".
+ */
+ public N concatenate(N last, N pivot) {
+ // If the black height of "first", where first = this, is less than or equal to that of "last", starting at the
+ // root of "last", we keep going left until we reach a black node whose black height is equal to that of
+ // "first". Then, we make "pivot" the parent of that node and of "first", coloring it red, and perform
+ // insertion fixup on the pivot. If the black height of "first" is greater than that of "last", we do the mirror
+ // image of the above.
+
+ if (parent != null) {
+ throw new IllegalArgumentException("This is not the root of a tree");
+ }
+ if (last.parent != null) {
+ throw new IllegalArgumentException("\"last\" is not the root of a tree");
+ }
+
+ // Compute the black height of the trees
+ int firstBlackHeight = 0;
+ @SuppressWarnings("unchecked")
+ N first = (N)this;
+ for (N node = first; node != null; node = node.right) {
+ if (!node.isRed) {
+ firstBlackHeight++;
+ }
+ }
+ int lastBlackHeight = 0;
+ for (N node = last; node != null; node = node.right) {
+ if (!node.isRed) {
+ lastBlackHeight++;
+ }
+ }
+
+ // Identify the children and parent of pivot
+ N firstChild = first;
+ N lastChild = last;
+ N parent;
+ if (firstBlackHeight <= lastBlackHeight) {
+ parent = null;
+ while (lastBlackHeight > firstBlackHeight) {
+ if (!lastChild.isRed) {
+ lastBlackHeight--;
+ }
+ parent = lastChild;
+ lastChild = lastChild.left;
+ }
+ if (lastChild.isRed) {
+ parent = lastChild;
+ lastChild = lastChild.left;
+ }
+ } else {
+ parent = null;
+ while (firstBlackHeight > lastBlackHeight) {
+ if (!firstChild.isRed) {
+ firstBlackHeight--;
+ }
+ parent = firstChild;
+ firstChild = firstChild.right;
+ }
+ if (firstChild.isRed) {
+ parent = firstChild;
+ firstChild = firstChild.right;
+ }
+ }
+
+ // Add "pivot" to the tree
+ pivot.isRed = true;
+ pivot.parent = parent;
+ if (parent != null) {
+ if (parent.left == lastChild) {
+ parent.left = pivot;
+ } else {
+ parent.right = pivot;
+ }
+ }
+ pivot.left = firstChild;
+ if (!firstChild.isLeaf()) {
+ firstChild.parent = pivot;
+ }
+ pivot.right = lastChild;
+ if (!lastChild.isLeaf()) {
+ lastChild.parent = pivot;
+ }
+
+ // Perform insertion fixup
+ pivot.fixInsertion();
+
+ return pivot.root();
+ }
+
+ /**
+ * Concatenates the tree rooted at "last" to the end of the tree rooted at this node. To be precise, given that all
+ * of the nodes in this precede all of the nodes in "last", this returns the root of a tree containing all of these
+ * nodes. This method destroys the trees rooted at "this" and "last". It assumes that this node and "last" are the
+ * roots of their respective trees. This method takes O(log N) time. It is considerably more efficient than
+ * inserting all of the nodes in "last".
+ */
+ public N concatenate(N last) {
+ if (isLeaf()) {
+ return last;
+ } else if (parent != null) {
+ throw new IllegalArgumentException("This is not the root of a tree");
+ } else if (last.isLeaf()) {
+ @SuppressWarnings("unchecked")
+ N nThis = (N)this;
+ return nThis;
+ } else {
+ N node = last.min();
+ last = node.remove();
+ return concatenate(last, node);
+ }
+ }
+
+ /**
+ * Splits the tree rooted at this node into two trees, so that the first element of the return value is the root of
+ * a tree consisting of the nodes that were before the specified node, and the second element of the return value is
+ * the root of a tree consisting of the nodes that were equal to or after the specified node. This method assumes
+ * that this node is the root. It takes O(log N) time. It is considerably more efficient than removing all of the
+ * elements after splitNode and then creating a new tree from those nodes.
+ * @return An array consisting of the resulting trees.
+ */
+ public N[] split(N splitNode) {
+ // To split the tree, we accumulate a pre-split tree and a post-split tree. We walk down the tree toward the
+ // position where we are splitting. Whenever we go left, we concatenate the right subtree with the post-split
+ // tree, and whenever we go right, we concatenate the pre-split tree with the left subtree. We use the
+ // concatenation algorithm described in concatenate(Object, Object). For the pivot, we use the last node where
+ // we went left in the case of a left move, and the last node where we went right in the case of a right move.
+ //
+ // The method uses the following variables:
+ //
+ // node: The current node in our walk down the tree.
+ // first: A node on the right spine of the pre-split tree. At the beginning of each iteration, it is the black
+ // node with the same black height as "node". If the pre-split tree is empty, this is null instead.
+ // firstParent: The parent of "first". If the pre-split tree is empty, this is null. Otherwise, this is the
+ // same as first.parent, unless first.isLeaf().
+ // firstPivot: The node where we last went right, i.e. the next node to use as a pivot when concatenating with
+ // the pre-split tree.
+ // advanceFirst: Whether to set "first" to be its next black descendant at the end of the loop.
+ // last, lastParent, lastPivot, advanceFirst: Analogous to "first", firstParent, firstPivot, and advanceFirst,
+ // but for the post-split tree.
+ if (parent != null) {
+ throw new IllegalArgumentException("This is not the root of a tree");
+ }
+
+ // Create an array containing the path from the root to splitNode
+ int depth = 1;
+ N parent;
+ for (parent = splitNode; parent.parent != null; parent = parent.parent) {
+ depth++;
+ }
+ if (parent != this) {
+ throw new IllegalArgumentException("The split node does not belong to this tree");
+ }
+ @SuppressWarnings("unchecked")
+ N[] path = (N[])Array.newInstance(getClass(), depth);
+ for (parent = splitNode; parent != null; parent = parent.parent) {
+ depth--;
+ path[depth] = parent;
+ }
+
+ @SuppressWarnings("unchecked")
+ N node = (N)this;
+ N first = null;
+ N firstParent = null;
+ N last = null;
+ N lastParent = null;
+ N firstPivot = null;
+ N lastPivot = null;
+ while (!node.isLeaf()) {
+ boolean advanceFirst = !node.isRed && firstPivot != null;
+ boolean advanceLast = !node.isRed && lastPivot != null;
+ if ((depth + 1 < path.length && path[depth + 1] == node.left) || depth + 1 == path.length) {
+ // Left move
+ if (lastPivot == null) {
+ // The post-split tree is empty
+ last = node.right;
+ last.parent = null;
+ if (last.isRed) {
+ last.isRed = false;
+ lastParent = last;
+ last = last.left;
+ }
+ } else {
+ // Concatenate node.right and the post-split tree
+ if (node.right.isRed) {
+ node.right.isRed = false;
+ } else if (!node.isRed) {
+ lastParent = last;
+ last = last.left;
+ if (last.isRed) {
+ lastParent = last;
+ last = last.left;
+ }
+ advanceLast = false;
+ }
+ lastPivot.isRed = true;
+ lastPivot.parent = lastParent;
+ if (lastParent != null) {
+ lastParent.left = lastPivot;
+ }
+ lastPivot.left = node.right;
+ if (!lastPivot.left.isLeaf()) {
+ lastPivot.left.parent = lastPivot;
+ }
+ lastPivot.right = last;
+ if (!last.isLeaf()) {
+ last.parent = lastPivot;
+ }
+ last = lastPivot.left;
+ lastParent = lastPivot;
+ lastPivot.fixInsertion(false);
+ }
+ lastPivot = node;
+ node = node.left;
+ } else {
+ // Right move
+ if (firstPivot == null) {
+ // The pre-split tree is empty
+ first = node.left;
+ first.parent = null;
+ if (first.isRed) {
+ first.isRed = false;
+ firstParent = first;
+ first = first.right;
+ }
+ } else {
+ // Concatenate the post-split tree and node.left
+ if (node.left.isRed) {
+ node.left.isRed = false;
+ } else if (!node.isRed) {
+ firstParent = first;
+ first = first.right;
+ if (first.isRed) {
+ firstParent = first;
+ first = first.right;
+ }
+ advanceFirst = false;
+ }
+ firstPivot.isRed = true;
+ firstPivot.parent = firstParent;
+ if (firstParent != null) {
+ firstParent.right = firstPivot;
+ }
+ firstPivot.right = node.left;
+ if (!firstPivot.right.isLeaf()) {
+ firstPivot.right.parent = firstPivot;
+ }
+ firstPivot.left = first;
+ if (!first.isLeaf()) {
+ first.parent = firstPivot;
+ }
+ first = firstPivot.right;
+ firstParent = firstPivot;
+ firstPivot.fixInsertion(false);
+ }
+ firstPivot = node;
+ node = node.right;
+ }
+
+ depth++;
+
+ // Update "first" and "last" to be the nodes at the proper black height
+ if (advanceFirst) {
+ firstParent = first;
+ first = first.right;
+ if (first.isRed) {
+ firstParent = first;
+ first = first.right;
+ }
+ }
+ if (advanceLast) {
+ lastParent = last;
+ last = last.left;
+ if (last.isRed) {
+ lastParent = last;
+ last = last.left;
+ }
+ }
+ }
+
+ // Add firstPivot to the pre-split tree
+ N leaf = node;
+ if (first == null) {
+ first = leaf;
+ } else {
+ firstPivot.isRed = true;
+ firstPivot.parent = firstParent;
+ if (firstParent != null) {
+ firstParent.right = firstPivot;
+ }
+ firstPivot.left = leaf;
+ firstPivot.right = leaf;
+ firstPivot.fixInsertion(false);
+ for (first = firstPivot; first.parent != null; first = first.parent) {
+ first.augment();
+ }
+ first.augment();
+ }
+
+ // Add lastPivot to the post-split tree
+ if (last == null) {
+ last = leaf;
+ } else {
+ lastPivot.isRed = true;
+ lastPivot.parent = lastParent;
+ if (lastParent != null) {
+ lastParent.left = lastPivot;
+ }
+ lastPivot.left = leaf;
+ lastPivot.right = leaf;
+ lastPivot.fixInsertion(false);
+ for (last = lastPivot; last.parent != null; last = last.parent) {
+ last.augment();
+ }
+ last.augment();
+ }
+
+ @SuppressWarnings("unchecked")
+ N[] result = (N[])Array.newInstance(getClass(), 2);
+ result[0] = first;
+ result[1] = last;
+ return result;
+ }
+
+ /**
+ * Returns an integer comparing the position of this node in the tree that contains it with that of "other".
+ * Returns a negative number if this is earlier, a positive number if this is later, and 0 if this is at the same
+ * position. Assumes that this is in the same tree as "other".
+ *
+ * The base class's implementation takes O(log N) time. If a RedBlackNode subclass stores a value used to order the
+ * nodes, then it could override compareTo to compare the nodes' values, which would take O(1) time.
+ */
+ public int compareTo(N other) {
+ // The algorithm operates as follows: compare the depth of this node to that of "other". If the depth of
+ // "other" is greater, keep moving up from "other" until we find the ancestor at the same depth. Then, keep
+ // moving up from "this" and from that node until we reach the lowest common ancestor. The node that arrived
+ // from the left child of the common ancestor is earlier. The algorithm is analogous if the depth of "other" is
+ // not greater.
+ if (this == other) {
+ return 0;
+ }
+
+ // Compute the depth of each node
+ int depth = 0;
+ RedBlackNode parent;
+ for (parent = this; parent.parent != null; parent = parent.parent) {
+ depth++;
+ }
+ int otherDepth = 0;
+ N otherParent;
+ for (otherParent = other; otherParent.parent != null; otherParent = otherParent.parent) {
+ otherDepth++;
+ }
+
+ // Go up to nodes of the same depth
+ if (depth < otherDepth) {
+ otherParent = other;
+ for (int i = otherDepth - 1; i > depth; i--) {
+ otherParent = otherParent.parent;
+ }
+ if (otherParent.parent != this) {
+ otherParent = otherParent.parent;
+ } else if (left == otherParent) {
+ return 1;
+ } else {
+ return -1;
+ }
+ parent = this;
+ } else if (depth > otherDepth) {
+ parent = this;
+ for (int i = depth - 1; i > otherDepth; i--) {
+ parent = parent.parent;
+ }
+ if (parent.parent != other) {
+ parent = parent.parent;
+ } else if (other.left == parent) {
+ return -1;
+ } else {
+ return 1;
+ }
+ otherParent = other;
+ } else {
+ parent = this;
+ otherParent = other;
+ }
+
+ // Keep going up until we reach the lowest common ancestor
+ while (parent.parent != otherParent.parent) {
+ parent = parent.parent;
+ otherParent = otherParent.parent;
+ }
+ if (parent.parent == null) {
+ throw new IllegalArgumentException("The nodes do not belong to the same tree");
+ }
+ if (parent.parent.left == parent) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ /** Throws a RuntimeException if the RedBlackNode fields of this are not correct for a leaf node. */
+ private void assertIsValidLeaf() {
+ if (left != null || right != null || parent != null || isRed) {
+ throw new RuntimeException("A leaf node's \"left\", \"right\", \"parent\", or isRed field is incorrect");
+ }
+ }
+
+ /**
+ * Throws a RuntimeException if this is a repeated node other than a leaf node or the subtree rooted at this node
+ * does not satisfy the red-black properties, excluding the requirement that the root be black.
+ * @param blackHeight The required number of black nodes in each path from this to a leaf node, including this and
+ * the leaf node.
+ * @param visited The nodes we have reached thus far, other than leaf nodes. This method adds the non-leaf nodes in
+ * the subtree rooted at this node to "visited".
+ */
+ private void assertSubtreeIsValidRedBlack(int blackHeight, Set> visited) {
+ @SuppressWarnings("unchecked")
+ N nThis = (N)this;
+ if (left == null || right == null) {
+ assertIsValidLeaf();
+ if (blackHeight != 1) {
+ throw new RuntimeException("Not all root-to-leaf paths have the same number of black nodes");
+ }
+ return;
+ } else if (!visited.add(new Reference(nThis))) {
+ throw new RuntimeException("The tree contains a repeated non-leaf node");
+ } else {
+ int childBlackHeight;
+ if (isRed) {
+ if (!left.isLeaf() && left.isRed) {
+ throw new RuntimeException("A red node has a red child");
+ }
+ if (!right.isLeaf() && right.isRed) {
+ throw new RuntimeException("A red node has a red child");
+ }
+ childBlackHeight = blackHeight;
+ } else if (blackHeight == 0) {
+ throw new RuntimeException("Not all root-to-leaf paths have the same number of black nodes");
+ } else {
+ childBlackHeight = blackHeight - 1;
+ }
+
+ if (!left.isLeaf() && left.parent != this) {
+ throw new RuntimeException("left.parent != this");
+ }
+ if (!right.isLeaf() && right.parent != this) {
+ throw new RuntimeException("right.parent != this");
+ }
+ RedBlackNode leftNode = left;
+ RedBlackNode rightNode = right;
+ leftNode.assertSubtreeIsValidRedBlack(childBlackHeight, visited);
+ rightNode.assertSubtreeIsValidRedBlack(childBlackHeight, visited);
+ }
+ }
+
+ /** Calls assertNodeIsValid() on every node in the subtree rooted at this node. */
+ private void assertNodesAreValid() {
+ assertNodeIsValid();
+ if (left != null) {
+ RedBlackNode leftNode = left;
+ RedBlackNode rightNode = right;
+ leftNode.assertNodesAreValid();
+ rightNode.assertNodesAreValid();
+ }
+ }
+
+ /**
+ * Throws a RuntimeException if the subtree rooted at this node is not a valid red-black tree, e.g. if a red node
+ * has a red child or it contains a non-leaf node "node" for which node.left.parent != node. (If parent != null,
+ * it's okay if isRed is true.) This method is useful for debugging. See also
+ * assertSubtreeIsValid().
+ */
+ public void assertSubtreeIsValidRedBlack() {
+ if (isLeaf()) {
+ assertIsValidLeaf();
+ } else {
+ if (parent == null && isRed) {
+ throw new RuntimeException("The root is red");
+ }
+
+ // Compute the black height of the tree
+ Set> nodes = new HashSet>();
+ int blackHeight = 0;
+ @SuppressWarnings("unchecked")
+ N node = (N)this;
+ while (node != null) {
+ if (!nodes.add(new Reference(node))) {
+ throw new RuntimeException("The tree contains a repeated non-leaf node");
+ }
+ if (!node.isRed) {
+ blackHeight++;
+ }
+ node = node.left;
+ }
+
+ assertSubtreeIsValidRedBlack(blackHeight, new HashSet>());
+ }
+ }
+
+ /**
+ * Throws a RuntimeException if we detect a problem with the subtree rooted at this node, such as a red child of a
+ * red node or a non-leaf descendant "node" for which node.left.parent != node. This method is useful for
+ * debugging. RedBlackNode subclasses may want to override assertSubtreeIsValid() to call assertOrderIsValid.
+ */
+ public void assertSubtreeIsValid() {
+ assertSubtreeIsValidRedBlack();
+ assertNodesAreValid();
+ }
+
+ /**
+ * Throws a RuntimeException if the nodes in the subtree rooted at this node are not in the specified order or they
+ * do not lie in the specified range. Assumes that the subtree rooted at this node is a valid binary tree, i.e. it
+ * has no repeated nodes other than leaf nodes.
+ * @param comparator A comparator indicating how the nodes should be ordered.
+ * @param start The lower limit for nodes in the subtree, if any.
+ * @param end The upper limit for nodes in the subtree, if any.
+ */
+ private void assertOrderIsValid(Comparator super N> comparator, N start, N end) {
+ if (!isLeaf()) {
+ @SuppressWarnings("unchecked")
+ N nThis = (N)this;
+ if (start != null && comparator.compare(nThis, start) < 0) {
+ throw new RuntimeException("The nodes are not ordered correctly");
+ }
+ if (end != null && comparator.compare(nThis, end) > 0) {
+ throw new RuntimeException("The nodes are not ordered correctly");
+ }
+ RedBlackNode leftNode = left;
+ RedBlackNode rightNode = right;
+ leftNode.assertOrderIsValid(comparator, start, nThis);
+ rightNode.assertOrderIsValid(comparator, nThis, end);
+ }
+ }
+
+ /**
+ * Throws a RuntimeException if the nodes in the subtree rooted at this node are not in the specified order.
+ * Assumes that this is a valid binary tree, i.e. there are no repeated nodes other than leaf nodes. This method is
+ * useful for debugging. RedBlackNode subclasses may want to override assertSubtreeIsValid() to call
+ * assertOrderIsValid.
+ * @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes'
+ * natural order, as in N.compare.
+ */
+ public void assertOrderIsValid(Comparator super N> comparator) {
+ if (comparator == null) {
+ comparator = naturalOrder();
+ }
+ assertOrderIsValid(comparator, null, null);
+ }
+}
diff --git a/src/com/github/btrekkie/red_black_node/Reference.java b/src/com/github/btrekkie/red_black_node/Reference.java
new file mode 100644
index 000000000..843053820
--- /dev/null
+++ b/src/com/github/btrekkie/red_black_node/Reference.java
@@ -0,0 +1,28 @@
+package com.github.btrekkie.red_black_node;
+
+/**
+ * Wraps a value using reference equality. In other words, two references are equal only if their values are the same
+ * object instance, as in ==.
+ * @param The type of value.
+ */
+class Reference {
+ /** The value this wraps. */
+ private final T value;
+
+ public Reference(T value) {
+ this.value = value;
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Reference)) {
+ return false;
+ }
+ Reference> reference = (Reference>)obj;
+ return value == reference.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return System.identityHashCode(value);
+ }
+}
diff --git a/src/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java b/src/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java
new file mode 100644
index 000000000..6059745aa
--- /dev/null
+++ b/src/com/github/btrekkie/red_black_node/test/RedBlackNodeTest.java
@@ -0,0 +1,171 @@
+package com.github.btrekkie.red_black_node.test;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Comparator;
+
+import org.junit.Test;
+
+/**
+ * Tests RedBlackNode. Most of the testing for RedBlackNode takes place in TreeListTest, IntervalTreeTest, and
+ * ArbitraryOrderCollectionTest, which test realistic use cases of RedBlackNode. TreeListTest tests most of the
+ * RedBlackNode methods, while IntervalTreeTest tests non-structural augmentation and the "insert" method,
+ * ArbitraryOrderCollectionTest tests compareTo, and RedBlackNodeTest tests assertSubtreeIsValid() and
+ * assertOrderIsValid.
+ */
+public class RedBlackNodeTest {
+ /**
+ * Returns whether the subtree rooted at the specified node is valid, as in TestRedBlackNode.assertSubtreeIsValid().
+ */
+ private boolean isSubtreeValid(TestRedBlackNode node) {
+ try {
+ node.assertSubtreeIsValid();
+ return true;
+ } catch (RuntimeException exception) {
+ return false;
+ }
+ }
+
+ /**
+ * Returns whether the nodes in the subtree rooted at the specified node are ordered correctly, as in
+ * TestRedBlackNode.assertOrderIsValid.
+ * @param comparator A comparator indicating how the nodes should be ordered. If this is null, we use the nodes'
+ * natural ordering, as in TestRedBlackNode.compare.
+ */
+ private boolean isOrderValid(TestRedBlackNode node, Comparator comparator) {
+ try {
+ node.assertOrderIsValid(null);
+ return true;
+ } catch (RuntimeException exception) {
+ return false;
+ }
+ }
+
+ /** Tests RedBlackNode.assertSubtreeIsValid() and RedBlackNode.assertOrderIsValid. */
+ @Test
+ public void testAssertIsValid() {
+ // Create a perfectly balanced tree of height 3
+ TestRedBlackNode node0 = new TestRedBlackNode(0);
+ TestRedBlackNode node1 = new TestRedBlackNode(1);
+ TestRedBlackNode node2 = new TestRedBlackNode(2);
+ TestRedBlackNode node3 = new TestRedBlackNode(3);
+ TestRedBlackNode node4 = new TestRedBlackNode(4);
+ TestRedBlackNode node5 = new TestRedBlackNode(5);
+ TestRedBlackNode node6 = new TestRedBlackNode(6);
+ node0.parent = node1;
+ node0.left = TestRedBlackNode.LEAF;
+ node0.right = TestRedBlackNode.LEAF;
+ node1.parent = node3;
+ node1.left = node0;
+ node1.right = node2;
+ node1.isRed = true;
+ node2.parent = node1;
+ node2.left = TestRedBlackNode.LEAF;
+ node2.right = TestRedBlackNode.LEAF;
+ node3.left = node1;
+ node3.right = node5;
+ node4.parent = node5;
+ node4.left = TestRedBlackNode.LEAF;
+ node4.right = TestRedBlackNode.LEAF;
+ node5.parent = node3;
+ node5.left = node4;
+ node5.right = node6;
+ node5.isRed = true;
+ node6.parent = node5;
+ node6.left = TestRedBlackNode.LEAF;
+ node6.right = TestRedBlackNode.LEAF;
+
+ node3.left = node3;
+ node3.right = node3;
+ node3.parent = node3;
+ assertFalse(isSubtreeValid(node3));
+ node3.left = node1;
+ node3.right = node5;
+ node3.parent = null;
+
+ node0.parent = node3;
+ assertFalse(isSubtreeValid(node3));
+ node0.parent = node1;
+
+ node1.right = node0;
+ assertFalse(isSubtreeValid(node3));
+ node1.right = node2;
+
+ node5.isRed = false;
+ assertFalse(isSubtreeValid(node3));
+ assertTrue(isSubtreeValid(node5));
+ node5.isRed = true;
+
+ node3.isRed = true;
+ assertFalse(isSubtreeValid(node3));
+ assertTrue(isSubtreeValid(node5));
+ node3.isRed = false;
+
+ node0.isRed = true;
+ node2.isRed = true;
+ node4.isRed = true;
+ node6.isRed = true;
+ assertFalse(isSubtreeValid(node3));
+ node0.isRed = false;
+ node2.isRed = false;
+ node4.isRed = false;
+ node6.isRed = false;
+
+ TestRedBlackNode.LEAF.isRed = true;
+ assertFalse(isSubtreeValid(node3));
+ TestRedBlackNode.LEAF.isRed = false;
+
+ TestRedBlackNode.LEAF.isValid = false;
+ assertFalse(isSubtreeValid(node3));
+ assertFalse(isSubtreeValid(TestRedBlackNode.LEAF));
+ TestRedBlackNode.LEAF.isValid = true;
+
+ node1.isValid = false;
+ assertFalse(isSubtreeValid(node3));
+ node1.isValid = true;
+
+ node3.value = 2;
+ node2.value = 3;
+ assertFalse(isOrderValid(node3, null));
+ assertFalse(
+ isOrderValid(node3, new Comparator() {
+ @Override
+ public int compare(TestRedBlackNode node1, TestRedBlackNode node2) {
+ return node1.value - node2.value;
+ }
+ }));
+ node3.value = 3;
+ node2.value = 2;
+
+ node2.value = 4;
+ node4.value = 2;
+ assertFalse(isOrderValid(node3, null));
+ node2.value = 2;
+ node4.value = 4;
+
+ node0.value = 1;
+ node1.value = 0;
+ assertFalse(isOrderValid(node3, null));
+ node0.value = 0;
+ node1.value = 1;
+
+ // Do all of the assertions for which the tree is supposed to be valid at the end, to make sure we didn't make a
+ // mistake undoing any of the modifications
+ assertTrue(isSubtreeValid(node3));
+ assertTrue(isSubtreeValid(node1));
+ assertTrue(isSubtreeValid(node0));
+ assertTrue(isSubtreeValid(TestRedBlackNode.LEAF));
+ assertTrue(isOrderValid(node3, null));
+ assertTrue(isOrderValid(node1, null));
+ assertTrue(isOrderValid(node0, null));
+ assertTrue(isOrderValid(TestRedBlackNode.LEAF, null));
+ assertTrue(
+ isOrderValid(node3, new Comparator() {
+ @Override
+ public int compare(TestRedBlackNode node1, TestRedBlackNode node2) {
+ return node1.value - node2.value;
+ }
+ }));
+ }
+}
diff --git a/src/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java b/src/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java
new file mode 100644
index 000000000..8e3b78cd8
--- /dev/null
+++ b/src/com/github/btrekkie/red_black_node/test/TestRedBlackNode.java
@@ -0,0 +1,36 @@
+package com.github.btrekkie.red_black_node.test;
+
+import com.github.btrekkie.red_black_node.RedBlackNode;
+
+/** A RedBlackNode for RedBlackNodeTest. */
+class TestRedBlackNode extends RedBlackNode {
+ /** The dummy leaf node. */
+ public static final TestRedBlackNode LEAF = new TestRedBlackNode();
+
+ /** The value stored in this node. "value" is unspecified if this is a leaf node. */
+ public int value;
+
+ /** Whether this node is considered valid, as in assertNodeIsValid(). */
+ public boolean isValid = true;
+
+ public TestRedBlackNode(int value) {
+ this.value = value;
+ }
+
+ /** Constructs a new dummy leaf node. */
+ private TestRedBlackNode() {
+
+ }
+
+ @Override
+ public void assertNodeIsValid() {
+ if (!isValid) {
+ throw new RuntimeException("isValid is false");
+ }
+ }
+
+ @Override
+ public int compareTo(TestRedBlackNode other) {
+ return value - other.value;
+ }
+}
diff --git a/src/com/github/btrekkie/tree_list/TreeList.java b/src/com/github/btrekkie/tree_list/TreeList.java
new file mode 100644
index 000000000..b5de0ccbc
--- /dev/null
+++ b/src/com/github/btrekkie/tree_list/TreeList.java
@@ -0,0 +1,461 @@
+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 extends T> values) {
+ root = createTree(values);
+ }
+
+ /** Returns the root of a perfectly height-balanced tree containing the specified values, in iteration order. */
+ private TreeListNode createTree(Collection extends T> 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;
+ }
+
+ newNode.fixInsertion();
+ while (root.parent != null) {
+ root = root.parent;
+ }
+ }
+
+ @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 extends T> 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 extends T> 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;
+ 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);
+ }
+}
diff --git a/src/com/github/btrekkie/tree_list/TreeListNode.java b/src/com/github/btrekkie/tree_list/TreeListNode.java
new file mode 100644
index 000000000..a16637e81
--- /dev/null
+++ b/src/com/github/btrekkie/tree_list/TreeListNode.java
@@ -0,0 +1,40 @@
+package com.github.btrekkie.tree_list;
+
+import com.github.btrekkie.red_black_node.RedBlackNode;
+
+/** A node in a TreeList. See the comments for TreeList. */
+class TreeListNode extends RedBlackNode> {
+ /** The element stored in the node. The value is unspecified if this is a leaf node. */
+ public T value;
+
+ /** The number of elements in the subtree rooted at this node, not counting leaf nodes, as in TreeList.leaf. */
+ public int size;
+
+ public TreeListNode(T value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean augment() {
+ int newSize = left.size + right.size + 1;
+ if (newSize == size) {
+ return false;
+ } else {
+ size = newSize;
+ return true;
+ }
+ }
+
+ @Override
+ public void assertNodeIsValid() {
+ int expectedSize;
+ if (isLeaf()) {
+ expectedSize = 0;
+ } else {
+ expectedSize = left.size + right.size + 1;
+ }
+ if (size != expectedSize) {
+ throw new RuntimeException("The node's size does not match that of the children");
+ }
+ }
+}
diff --git a/src/com/github/btrekkie/tree_list/test/TreeListTest.java b/src/com/github/btrekkie/tree_list/test/TreeListTest.java
new file mode 100644
index 000000000..f59a86f0b
--- /dev/null
+++ b/src/com/github/btrekkie/tree_list/test/TreeListTest.java
@@ -0,0 +1,551 @@
+package com.github.btrekkie.tree_list.test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.NoSuchElementException;
+
+import org.junit.Test;
+
+import com.github.btrekkie.tree_list.TreeList;
+
+public class TreeListTest {
+ /** Tests TreeList.add. */
+ @Test
+ public void testAdd() {
+ List list = new TreeList();
+ assertEquals(Collections.emptyList(), list);
+ assertEquals(0, list.size());
+ assertTrue(list.isEmpty());
+
+ list.add(4);
+ list.add(17);
+ list.add(-4);
+ list.add(null);
+ assertEquals(Arrays.asList(4, 17, -4, null), list);
+ assertEquals(4, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(-4, list.get(2).intValue());
+
+ boolean threwException;
+ try {
+ list.get(-3);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ list.get(9);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ list.add(-3, 5);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ list.add(9, 10);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+
+ list.add(1, 6);
+ list.add(0, -1);
+ list.add(6, 42);
+ assertEquals(Arrays.asList(-1, 4, 6, 17, -4, null, 42), list);
+ assertEquals(7, list.size());
+ assertEquals(6, list.get(2).intValue());
+ assertEquals(null, list.get(5));
+
+ list = new TreeList();
+ for (int i = 0; i < 200; i++) {
+ list.add(i + 300);
+ }
+ for (int i = 0; i < 300; i++) {
+ list.add(i, i);
+ }
+ for (int i = 499; i >= 0; i--) {
+ list.add(500, i + 500);
+ }
+ List expected = new ArrayList(1000);
+ for (int i = 0; i < 1000; i++) {
+ expected.add(i);
+ }
+ assertEquals(expected, list);
+ assertEquals(1000, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(777, list.get(777).intValue());
+ assertEquals(123, list.get(123).intValue());
+ assertEquals(0, list.get(0).intValue());
+ assertEquals(999, list.get(999).intValue());
+ }
+
+ /** Tests TreeList.remove. */
+ @Test
+ public void testRemove() {
+ List list = new TreeList();
+ list.add(17);
+ list.add(-5);
+ list.add(null);
+ list.add(0);
+ list.add(1, 16);
+ assertEquals(null, list.remove(3));
+ assertEquals(17, list.remove(0).intValue());
+ assertEquals(0, list.remove(2).intValue());
+ assertEquals(Arrays.asList(16, -5), list);
+ assertEquals(2, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(16, list.get(0).intValue());
+ assertEquals(-5, list.get(1).intValue());
+
+ boolean threwException;
+ try {
+ list.remove(-3);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ list.remove(9);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+
+ list = new TreeList();
+ for (int i = 0; i < 1000; i++) {
+ list.add(i);
+ }
+ for (int i = 0; i < 250; i++) {
+ assertEquals(2 * i + 501, list.remove(501).intValue());
+ assertEquals(2 * i + 1, list.remove(i + 1).intValue());
+ }
+ List expected = new ArrayList(500);
+ for (int i = 0; i < 500; i++) {
+ expected.add(2 * i);
+ }
+ assertEquals(expected, list);
+ assertEquals(500, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(0, list.get(0).intValue());
+ assertEquals(998, list.get(499).intValue());
+ assertEquals(84, list.get(42).intValue());
+ assertEquals(602, list.get(301).intValue());
+ }
+
+ /** Tests TreeList.set.*/
+ @Test
+ public void testSet() {
+ List list = new TreeList();
+ list.addAll(Arrays.asList(5, 17, 42, -6, null, 3, null));
+ list.set(3, 12);
+ list.set(0, 6);
+ list.set(6, 88);
+ boolean threwException;
+ try {
+ list.set(7, 2);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ list.set(-1, 4);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ assertEquals(Arrays.asList(6, 17, 42, 12, null, 3, 88), list);
+ assertEquals(7, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(42, list.get(2).intValue());
+ assertEquals(6, list.get(0).intValue());
+ assertEquals(88, list.get(6).intValue());
+
+ list = new TreeList();
+ for (int i = 0; i < 1000; i++) {
+ list.add(999 - i);
+ }
+ for (int i = 0; i < 500; i++) {
+ list.set(i, i);
+ list.set(i + 500, i + 500);
+ }
+ List expected = new ArrayList(1000);
+ for (int i = 0; i < 1000; i++) {
+ expected.add(i);
+ }
+ assertEquals(expected, list);
+ assertEquals(1000, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(123, list.get(123).intValue());
+ assertEquals(777, list.get(777).intValue());
+ assertEquals(0, list.get(0).intValue());
+ assertEquals(999, list.get(999).intValue());
+ }
+
+ /** Tests TreeList.addAll. */
+ @Test
+ public void testAddAll() {
+ List list = new TreeList();
+ list.add(3);
+ list.add(42);
+ list.add(16);
+ list.addAll(0, Arrays.asList(15, 4, -1));
+ list.addAll(2, Arrays.asList(6, 14));
+ list.addAll(1, Collections.emptyList());
+ list.addAll(8, Arrays.asList(null, 7));
+ list.addAll(Arrays.asList(-5, 5));
+ assertEquals(Arrays.asList(15, 4, 6, 14, -1, 3, 42, 16, null, 7, -5, 5), list);
+ assertEquals(12, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(14, list.get(3).intValue());
+ assertEquals(15, list.get(0).intValue());
+ assertEquals(5, list.get(11).intValue());
+
+ list = new TreeList();
+ List list2 = new ArrayList(400);
+ for (int i = 0; i < 200; i++) {
+ list2.add(i + 100);
+ }
+ for (int i = 0; i < 200; i++) {
+ list2.add(i + 700);
+ }
+ list.addAll(list2);
+ list2.clear();
+ for (int i = 0; i < 100; i++) {
+ list2.add(i);
+ }
+ list.addAll(0, list2);
+ list2.clear();
+ for (int i = 0; i < 400; i++) {
+ list2.add(i + 300);
+ }
+ list.addAll(300, list2);
+ list2.clear();
+ for (int i = 0; i < 100; i++) {
+ list2.add(i + 900);
+ }
+ list.addAll(900, list2);
+ List expected = new ArrayList(1000);
+ for (int i = 0; i < 1000; i++) {
+ expected.add(i);
+ }
+ assertEquals(expected, list);
+ assertEquals(1000, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(123, list.get(123).intValue());
+ assertEquals(777, list.get(777).intValue());
+ assertEquals(0, list.get(0).intValue());
+ assertEquals(999, list.get(999).intValue());
+ }
+
+ /** Tests TreeList.subList.clear(). */
+ @Test
+ public void testClearSubList() {
+ List list = new TreeList();
+ list.addAll(Arrays.asList(6, 42, -3, 15, 7, 99, 6, 12));
+ list.subList(2, 4).clear();
+ list.subList(0, 1).clear();
+ list.subList(4, 4).clear();
+ list.subList(3, 5).clear();
+ assertEquals(Arrays.asList(42, 7, 99), list);
+ assertEquals(3, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(42, list.get(0).intValue());
+ assertEquals(7, list.get(1).intValue());
+ assertEquals(99, list.get(2).intValue());
+
+ list = new TreeList();
+ for (int i = 0; i < 200; i++) {
+ list.add(-1);
+ }
+ for (int i = 0; i < 500; i++) {
+ list.add(i);
+ }
+ for (int i = 0; i < 400; i++) {
+ list.add(-1);
+ }
+ for (int i = 0; i < 500; i++) {
+ list.add(i + 500);
+ }
+ for (int i = 0; i < 600; i++) {
+ list.add(-1);
+ }
+ list.subList(1600, 2200).clear();
+ list.subList(777, 777).clear();
+ list.subList(700, 1100).clear();
+ list.subList(0, 200).clear();
+ List expected = new ArrayList(1000);
+ for (int i = 0; i < 1000; i++) {
+ expected.add(i);
+ }
+ assertEquals(expected, list);
+ assertEquals(1000, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(123, list.get(123).intValue());
+ assertEquals(777, list.get(777).intValue());
+ assertEquals(0, list.get(0).intValue());
+ assertEquals(999, list.get(999).intValue());
+
+ list = new TreeList();
+ list.addAll(Arrays.asList(-3, null, 8, 14, 9, 42));
+ list.subList(0, 6).clear();
+ assertEquals(Collections.emptyList(), list);
+ assertEquals(0, list.size());
+ assertTrue(list.isEmpty());
+ }
+
+ /** Tests TreeList(Collection). */
+ @Test
+ public void testConstructor() {
+ List list = new TreeList(Arrays.asList(1, 5, 42, -6, null, 3));
+ assertEquals(Arrays.asList(1, 5, 42, -6, null, 3), list);
+ assertEquals(6, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(42, list.get(2).intValue());
+ assertEquals(1, list.get(0).intValue());
+ assertEquals(3, list.get(5).intValue());
+
+ List expected = new ArrayList(1000);
+ for (int i = 0; i < 1000; i++) {
+ expected.add(i);
+ }
+ list = new TreeList(expected);
+ assertEquals(expected, list);
+ assertEquals(1000, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(123, list.get(123).intValue());
+ assertEquals(777, list.get(777).intValue());
+ assertEquals(0, list.get(0).intValue());
+ assertEquals(999, list.get(999).intValue());
+ }
+
+ /** Tests TreeList.clear(). */
+ @Test
+ public void testClear() {
+ List list = new TreeList();
+ list.addAll(Arrays.asList(7, 16, 5, 42));
+ list.clear();
+ assertEquals(Collections.emptyList(), list);
+ assertEquals(0, list.size());
+ assertTrue(list.isEmpty());
+
+ for (int i = 0; i < 1000; i++) {
+ list.add(i);
+ }
+ list.clear();
+ assertEquals(Collections.emptyList(), list);
+ assertEquals(0, list.size());
+ assertTrue(list.isEmpty());
+ }
+
+ /** Tests TreeList.iterator(). */
+ @Test
+ public void testIterator() {
+ List list = new TreeList();
+ Iterator iterator = list.iterator();
+ assertFalse(iterator.hasNext());
+ boolean threwException;
+ try {
+ iterator.next();
+ threwException = false;
+ } catch (NoSuchElementException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+
+ list = new TreeList(Arrays.asList(42, 16, null, 7, 8, 3, 12));
+ iterator = list.iterator();
+ try {
+ iterator.remove();
+ threwException = false;
+ } catch (IllegalStateException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ assertTrue(iterator.hasNext());
+ assertEquals(42, iterator.next().intValue());
+ assertEquals(16, iterator.next().intValue());
+ assertEquals(null, iterator.next());
+ assertTrue(iterator.hasNext());
+ assertEquals(7, iterator.next().intValue());
+ iterator.remove();
+ try {
+ iterator.remove();
+ threwException = false;
+ } catch (IllegalStateException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ assertTrue(iterator.hasNext());
+ assertTrue(iterator.hasNext());
+ assertEquals(8, iterator.next().intValue());
+ assertTrue(iterator.hasNext());
+ assertEquals(3, iterator.next().intValue());
+ assertTrue(iterator.hasNext());
+ assertEquals(12, iterator.next().intValue());
+ assertFalse(iterator.hasNext());
+ iterator.remove();
+ assertFalse(iterator.hasNext());
+ try {
+ iterator.next();
+ threwException = false;
+ } catch (NoSuchElementException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ assertEquals(Arrays.asList(42, 16, null, 8, 3), list);
+
+ list = new TreeList();
+ for (int i = 0; i < 1000; i++) {
+ list.add(i);
+ }
+ iterator = list.iterator();
+ for (int i = 0; i < 500; i++) {
+ assertTrue(iterator.hasNext());
+ assertEquals(2 * i, iterator.next().intValue());
+ assertTrue(iterator.hasNext());
+ assertEquals(2 * i + 1, iterator.next().intValue());
+ iterator.remove();
+ }
+ assertFalse(iterator.hasNext());
+ List expected = new ArrayList(500);
+ for (int i = 0; i < 500; i++) {
+ expected.add(2 * i);
+ }
+ assertEquals(expected, list);
+ }
+
+ /** Tests TreeList.listIterator. */
+ @Test
+ public void testListIterator() {
+ List list = new TreeList();
+ list.addAll(Arrays.asList(7, 16, 42, -3, 12, 25, 8, 9));
+ boolean threwException;
+ try {
+ list.listIterator(-1);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ list.listIterator(18);
+ threwException = false;
+ } catch (IndexOutOfBoundsException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ ListIterator iterator = list.listIterator(1);
+ assertTrue(iterator.hasNext());
+ assertTrue(iterator.hasPrevious());
+ assertEquals(16, iterator.next().intValue());
+ iterator.add(6);
+ try {
+ iterator.set(-1);
+ threwException = false;
+ } catch (IllegalStateException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ iterator.add(9);
+ threwException = false;
+ } catch (IllegalStateException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ try {
+ iterator.remove();
+ threwException = false;
+ } catch (IllegalStateException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ assertEquals(6, iterator.previous().intValue());
+ assertEquals(6, iterator.next().intValue());
+ assertEquals(2, iterator.previousIndex());
+ assertEquals(3, iterator.nextIndex());
+ assertEquals(42, iterator.next().intValue());
+ assertEquals(-3, iterator.next().intValue());
+ assertEquals(12, iterator.next().intValue());
+ iterator.remove();
+ assertEquals(-3, iterator.previous().intValue());
+ iterator.remove();
+ assertEquals(25, iterator.next().intValue());
+ iterator.set(14);
+ assertEquals(8, iterator.next().intValue());
+ assertEquals(9, iterator.next().intValue());
+ assertFalse(iterator.hasNext());
+ try {
+ iterator.next();
+ threwException = false;
+ } catch (NoSuchElementException exception) {
+ threwException = true;
+ }
+ assertTrue(threwException);
+ assertEquals(9, iterator.previous().intValue());
+ iterator.set(10);
+ assertEquals(Arrays.asList(7, 16, 6, 42, 14, 8, 10), list);
+ assertEquals(7, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(42, list.get(3).intValue());
+ assertEquals(7, list.get(0).intValue());
+ assertEquals(10, list.get(6).intValue());
+
+ list = new TreeList();
+ for (int i = 0; i < 1000; i++) {
+ list.add(-1);
+ }
+ iterator = list.listIterator();
+ for (int i = 0; i < 500; i++) {
+ iterator.add(i);
+ iterator.next();
+ }
+ for (int i = 0; i < 500; i++) {
+ iterator.previous();
+ iterator.remove();
+ iterator.previous();
+ }
+ iterator = list.listIterator(500);
+ for (int i = 0; i < 250; i++) {
+ iterator.next();
+ iterator.set(2 * i + 500);
+ iterator.next();
+ }
+ for (int i = 0; i < 250; i++) {
+ iterator.previous();
+ iterator.set(999 - 2 * i);
+ iterator.previous();
+ }
+ List expected = new ArrayList(1000);
+ for (int i = 0; i < 1000; i++) {
+ expected.add(i);
+ }
+ assertEquals(expected, list);
+ assertEquals(1000, list.size());
+ assertFalse(list.isEmpty());
+ assertEquals(123, list.get(123).intValue());
+ assertEquals(777, list.get(777).intValue());
+ assertEquals(0, list.get(0).intValue());
+ assertEquals(999, list.get(999).intValue());
+ }
+}