//******************************
// Written by Peter Golde
// Copyright (c) 2004-2005, Wintellect
//
// Use and restribution of this code is subject to the license agreement
// contained in the file "License.txt" accompanying this file.
//******************************
using System;
using System.Collections.Generic;
using System.Collections;
using System.Diagnostics;
// CONSIDER: provide more efficient implementation of CopyTo.
namespace Wintellect.PowerCollections
{
///
/// BigList<T> provides a list of items, in order, with indices of the items ranging from 0 to one less
/// than the count of items in the collection. BigList<T> is optimized for efficient operations on large (>100 items)
/// lists, especially for insertions, deletions, copies, and concatinations.
///
///
/// BigList<T> class is similar in functionality to the standard List<T> class. Both classes
/// provide a collection that stores an set of items in order, with indices of the items ranging from 0 to one less
/// than the count of items in the collection. Both classes provide the ability to add and remove items from any index,
/// and the get or set the item at any index.
/// BigList<T> differs significantly from List<T> in the performance of various operations,
/// especially when the lists become large (several hundred items or more). With List<T>, inserting or removing
/// elements from anywhere in a large list except the end is very inefficient -- every item after the point of inserting
/// or deletion has to be moved in the list. The BigList<T> class, however, allows for fast insertions
/// and deletions anywhere in the list. Furthermore, BigList<T> allows copies of a list, sub-parts
/// of a list, and concatinations of two lists to be very fast. When a copy is made of part or all of a BigList,
/// two lists shared storage for the parts of the lists that are the same. Only when one of the lists is changed is additional
/// memory allocated to store the distinct parts of the lists.
/// Of course, there is a small price to pay for this extra flexibility. Although still quite efficient, using an
/// index to get or change one element of a BigList, while still reasonably efficient, is significantly slower than using
/// a plain List. Because of this, if you want to process every element of a BigList, using a foreach loop is a lot
/// more efficient than using a for loop and indexing the list.
/// In general, use a List when the only operations you are using are Add (to the end), foreach,
/// or indexing, or you are very sure the list will always remain small (less than 100 items). For large (>100 items) lists
/// that do insertions, removals, copies, concatinations, or sub-ranges, BigList will be more efficient than List.
/// In almost all cases, BigList is more efficient and easier to use than LinkedList.
///
/// The type of items to store in the BigList.
[Serializable]
public class BigList: ListBase, ICloneable
{
const uint MAXITEMS = int.MaxValue - 1; // maximum number of items in a BigList.
#if DEBUG
const int MAXLEAF = 8; // Maximum number of elements in a leaf node -- small for debugging purposes.
#else
const int MAXLEAF = 120; // Maximum number of elements in a leaf node.
#endif
const int BALANCEFACTOR = 6; // how far the root must be in depth from fully balanced to invoke the rebalance operation (min 3).
// The fibonacci numbers. Used in the rebalancing algorithm. Final MaxValue makes sure we don't go off the end.
static readonly int[] FIBONACCI = {1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,
4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040,
1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986,
102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, int.MaxValue};
const int MAXFIB = 44; // maximum index in the above, not counting the final MaxValue.
// If null, the BigList is empty. If non-null, the list has at least one item.
private Node root;
// Holds the change stamp for the collection.
private int changeStamp;
///
/// Must be called whenever there is a structural change in the tree. Causes
/// changeStamp to be changed, which causes any in-progress enumerations
/// to throw exceptions.
///
private void StopEnumerations()
{
++changeStamp;
}
///
/// Checks the given stamp against the current change stamp. If different, the
/// collection has changed during enumeration and an InvalidOperationException
/// must be thrown
///
/// changeStamp at the start of the enumeration.
private void CheckEnumerationStamp(int startStamp)
{
if (startStamp != changeStamp) {
throw new InvalidOperationException(Strings.ChangeDuringEnumeration);
}
}
///
/// Creates a new BigList. The BigList is initially empty.
///
/// Creating a empty BigList takes constant time and consumes a very small amount of memory.
public BigList()
{
root = null;
}
///
/// Creates a new BigList initialized with the items from , in order.
///
/// Initializing the tree list with the elements of collection takes time O(N), where N is the number of
/// items in .
/// The collection used to initialize the BigList.
/// is null.
public BigList(IEnumerable collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
root = NodeFromEnumerable(collection);
CheckBalance();
}
///
/// Creates a new BigList initialized with a given number of copies of the items from , in order.
///
/// Initializing the tree list with the elements of collection takes time O(N + log K), where N is the number of
/// items in , and K is the number of copies.
/// Number of copies of the collection to use.
/// The collection used to initialize the BigList.
/// is negative.
/// is null.
public BigList(IEnumerable collection, int copies)
{
if (collection == null)
throw new ArgumentNullException("collection");
root = NCopiesOfNode(copies, NodeFromEnumerable(collection));
CheckBalance();
}
///
/// Creates a new BigList that is a copy of .
///
/// Copying a BigList takes constant time, and little
/// additional memory, since the storage for the items of the two lists is shared. However, changing
/// either list will take additional time and memory. Portions of the list are copied when they are changed.
/// The BigList to copy.
/// is null.
public BigList(BigList list)
{
if (list == null)
throw new ArgumentNullException("list");
if (list.root == null)
root = null;
else {
list.root.MarkShared();
root = list.root;
}
}
///
/// Creates a new BigList that is several copies of .
///
/// Creating K copies of a BigList takes time O(log K), and O(log K)
/// additional memory, since the storage for the items of the two lists is shared. However, changing
/// either list will take additional time and memory. Portions of the list are copied when they are changed.
/// Number of copies of the collection to use.
/// The BigList to copy.
/// is null.
public BigList(BigList list, int copies)
{
if (list == null)
throw new ArgumentNullException("list");
if (list.root == null)
root = null;
else {
list.root.MarkShared();
root = NCopiesOfNode(copies, list.root);
}
}
///
/// Creates a new BigList from the indicated Node.
///
/// Node that becomes the new root. If null, the new BigList is empty.
private BigList(Node node)
{
this.root = node;
CheckBalance();
}
///
/// Gets the number of items stored in the BigList. The indices of the items
/// range from 0 to Count-1.
///
/// Getting the number of items in the BigList takes constant time.
/// The number of items in the BigList.
public sealed override int Count
{
get
{
if (root == null)
return 0;
else
return root.Count;
}
}
///
/// Gets or sets an item in the list, by index.
///
/// Gettingor setting an item takes time O(log N), where N is the number of items
/// in the list.
/// To process each of the items in the list, using GetEnumerator() or a foreach loop is more efficient
/// that accessing each of the elements by index.
/// The index of the item to get or set. The first item in the list
/// has index 0, the last item has index Count-1.
/// The value of the item at the given index.
/// is less than zero or
/// greater than or equal to Count.
public sealed override T this[int index]
{
get
{
// This could just be a simple call to GetAt on the root.
// It is recoded as an interative algorithm for performance.
if (root == null || index < 0 || index >= root.Count)
throw new ArgumentOutOfRangeException("index");
Node current = root;
ConcatNode curConcat = current as ConcatNode;
while (curConcat != null) {
int leftCount = curConcat.left.Count;
if (index < leftCount)
current = curConcat.left;
else {
current = curConcat.right;
index -= leftCount;
}
curConcat = current as ConcatNode;
}
LeafNode curLeaf = (LeafNode)current;
return curLeaf.items[index];
}
set
{
// This could just be a simple call to SetAtInPlace on the root.
// It is recoded as an interative algorithm for performance.
if (root == null || index < 0 || index >= root.Count)
throw new ArgumentOutOfRangeException("index");
// Like List, we stop enumerations after a set operation. This could be made
// to not happen, but it would be complex, because set operations on a shared node
// could change the node.
StopEnumerations();
if (root.Shared)
root = root.SetAt(index, value);
Node current = root;
ConcatNode curConcat = current as ConcatNode;
while (curConcat != null) {
int leftCount = curConcat.left.Count;
if (index < leftCount) {
current = curConcat.left;
if (current.Shared) {
curConcat.left = current.SetAt(index, value);
return;
}
}
else {
current = curConcat.right;
index -= leftCount;
if (current.Shared) {
curConcat.right = current.SetAt(index, value);
return;
}
}
curConcat = current as ConcatNode;
}
LeafNode curLeaf = (LeafNode)current;
curLeaf.items[index] = value;
}
}
///
/// Removes all of the items from the BigList.
///
/// Clearing a BigList takes constant time.
public sealed override void Clear()
{
StopEnumerations();
root = null;
}
///
/// Inserts a new item at the given index in the BigList. All items at indexes
/// equal to or greater than move up one index.
///
/// The amount of time to insert an item is O(log N), no matter where
/// in the list the insertion occurs. Inserting an item at the beginning or end of the
/// list is O(N).
///
/// The index to insert the item at. After the
/// insertion, the inserted item is located at this index. The
/// first item has index 0.
/// The item to insert at the given index.
/// is
/// less than zero or greater than Count.
public sealed override void Insert(int index, T item)
{
StopEnumerations();
if ((uint)Count + 1 > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
if (index <= 0 || index >= Count) {
if (index == 0)
AddToFront(item);
else if (index == Count)
Add(item);
else
throw new ArgumentOutOfRangeException("index");
}
else {
if (root == null)
root = new LeafNode(item);
else {
Node newRoot = root.InsertInPlace(index, item);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
}
///
/// Inserts a collection of items at the given index in the BigList. All items at indexes
/// equal to or greater than increase their indices
/// by the number of items inserted.
///
/// The amount of time to insert an arbitrary collection in the BigList is O(M + log N),
/// where M is the number of items inserted, and N is the number of items in the list.
///
/// The index to insert the collection at. After the
/// insertion, the first item of the inserted collection is located at this index. The
/// first item has index 0.
/// The collection of items to insert at the given index.
/// is
/// less than zero or greater than Count.
/// is null.
public void InsertRange(int index, IEnumerable collection)
{
StopEnumerations();
if (collection == null)
throw new ArgumentNullException("collection");
if (index <= 0 || index >= Count) {
if (index == 0)
AddRangeToFront(collection);
else if (index == Count)
AddRange(collection);
else
throw new ArgumentOutOfRangeException("index");
}
else {
Node node = NodeFromEnumerable(collection);
if (node == null)
return;
else if (root == null)
root = node;
else {
if ((uint)Count + (uint)node.Count > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
Node newRoot = root.InsertInPlace(index, node, true);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
}
///
/// Inserts a BigList of items at the given index in the BigList. All items at indexes
/// equal to or greater than increase their indices
/// by the number of items inserted.
///
/// The amount of time to insert another BigList is O(log N),
/// where N is the number of items in the list, regardless of the number of items in the
/// inserted list. Storage is shared between the two lists until one of them is changed.
///
/// The index to insert the collection at. After the
/// insertion, the first item of the inserted collection is located at this index. The
/// first item has index 0.
/// The BigList of items to insert at the given index.
/// is
/// less than zero or greater than Count.
/// is null.
public void InsertRange(int index, BigList list)
{
StopEnumerations();
if (list == null)
throw new ArgumentNullException("list");
if ((uint)Count + (uint)list.Count > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
if (index <= 0 || index >= Count) {
if (index == 0)
AddRangeToFront(list);
else if (index == Count)
AddRange(list);
else
throw new ArgumentOutOfRangeException("index");
}
else {
if (list.Count == 0)
return;
if (root == null) {
list.root.MarkShared();
root = list.root;
}
else {
if (list.root == root)
root.MarkShared(); // make sure inserting into itself works.
Node newRoot = root.InsertInPlace(index, list.root, false);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
}
///
/// Removes the item at the given index in the BigList. All items at indexes
/// greater than move down one index.
///
/// The amount of time to delete an item in the BigList is O(log N),
/// where N is the number of items in the list.
///
/// The index in the list to remove the item at. The
/// first item in the list has index 0.
/// is
/// less than zero or greater than or equal to Count.
public sealed override void RemoveAt(int index)
{
RemoveRange(index, 1);
}
///
/// Removes a range of items at the given index in the Deque. All items at indexes
/// greater than move down indices
/// in the Deque.
///
/// The amount of time to delete items in the Deque is proportional
/// to the distance of index from the closest end of the Deque, plus :
/// O(count + Min(, Count - 1 - )).
///
/// The index in the list to remove the range at. The
/// first item in the list has index 0.
/// The number of items to remove.
/// is
/// less than zero or greater than or equal to Count, or is less than zero
/// or too large.
public void RemoveRange(int index, int count)
{
if (count == 0)
return; // nothing to do.
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException("index");
if (count < 0 || count > Count - index)
throw new ArgumentOutOfRangeException("count");
StopEnumerations();
Node newRoot = root.RemoveRangeInPlace(index, index + count - 1);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
///
/// Adds an item to the end of the BigList. The indices of all existing items
/// in the Deque are unchanged.
///
/// Adding an item takes, on average, constant time.
/// The item to add.
public sealed override void Add(T item)
{
if ((uint)Count + 1 > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
StopEnumerations();
if (root == null)
root = new LeafNode(item);
else {
Node newRoot = root.AppendInPlace(item);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
///
/// Adds an item to the beginning of the BigList. The indices of all existing items
/// in the Deque are increased by one, and the new item has index zero.
///
/// Adding an item takes, on average, constant time.
/// The item to add.
public void AddToFront(T item)
{
if ((uint)Count + 1 > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
StopEnumerations();
if (root == null)
root = new LeafNode(item);
else {
Node newRoot = root.PrependInPlace(item);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
///
/// Adds a collection of items to the end of BigList. The indices of all existing items
/// are unchanged. The last item in the added collection becomes the
/// last item in the BigList.
///
/// This method takes time O(M + log N), where M is the number of items in the
/// , and N is the size of the BigList.
/// The collection of items to add.
/// is null.
public void AddRange(IEnumerable collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
StopEnumerations();
Node node = NodeFromEnumerable(collection);
if (node == null)
return;
else if (root == null) {
root = node;
CheckBalance();
}
else {
if ((uint)Count + (uint)node.count > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
Node newRoot = root.AppendInPlace(node, true);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
///
/// Adds a collection of items to the front of BigList. The indices of all existing items
/// in the are increased by the number of items in .
/// The first item in the added collection becomes the first item in the BigList.
///
/// This method takes time O(M + log N), where M is the number of items in the
/// , and N is the size of the BigList.
/// The collection of items to add.
/// is null.
public void AddRangeToFront(IEnumerable collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
StopEnumerations();
Node node = NodeFromEnumerable(collection);
if (node == null)
return;
else if (root == null) {
root = node;
CheckBalance();
}
else {
if ((uint)Count + (uint)node.Count > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
Node newRoot = root.PrependInPlace(node, true);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
///
/// Creates a new BigList that is a copy of this list.
///
/// Copying a BigList takes constant time, and little
/// additional memory, since the storage for the items of the two lists is shared. However, changing
/// either list will take additional time and memory. Portions of the list are copied when they are changed.
/// A copy of the current list
public BigList Clone()
{
if (root == null)
return new BigList();
else {
root.MarkShared();
return new BigList(root);
}
}
///
/// Creates a new BigList that is a copy of this list.
///
/// Copying a BigList takes constant time, and little
/// additional memory, since the storage for the items of the two lists is shared. However, changing
/// either list will take additional time and memory. Portions of the list are copied when they are changed.
/// A copy of the current list
object ICloneable.Clone()
{
return Clone();
}
///
/// Makes a deep clone of this BigList. A new BigList is created with a clone of
/// each element of this set, by calling ICloneable.Clone on each element. If T is
/// a value type, then this method is the same as Clone.
///
/// If T is a reference type, it must implement
/// ICloneable. Otherwise, an InvalidOperationException is thrown.
/// If T is a reference type, cloning the list takes time approximate O(N), where N is the number of items in the list.
/// The cloned set.
/// T is a reference type that does not implement ICloneable.
public BigList CloneContents()
{
if (root == null)
return new BigList();
else {
bool itemIsValueType;
if (!Util.IsCloneableType(typeof(T), out itemIsValueType))
throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, typeof(T).FullName));
if (itemIsValueType)
return Clone();
// Create a new list by converting each item in this list via cloning.
return new BigList(Algorithms.Convert(this, delegate(T item) {
if (item == null)
return default(T); // Really null, because we know T is a reference type
else
return (T)(((ICloneable)item).Clone());
}));
}
}
///
/// Adds a BigList of items to the end of BigList. The indices of all existing items
/// are unchanged. The last item in becomes the
/// last item in this list. The added list is unchanged.
///
/// This method takes, on average, constant time, regardless of the size
/// of either list. Although conceptually all of the items in are
/// copied, storage is shared between the two lists until changes are made to the
/// shared sections.
/// The list of items to add.
/// is null.
public void AddRange(BigList list)
{
if (list == null)
throw new ArgumentNullException("list");
if ((uint)Count + (uint)list.Count > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
if (list.Count == 0)
return;
StopEnumerations();
if (root == null) {
list.root.MarkShared();
root = list.root;
}
else {
Node newRoot = root.AppendInPlace(list.root, false);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
///
/// Adds a BigList of items to the front of BigList. The indices of all existing items
/// are increased by the number of items in . The first item in
/// becomes the first item in this list. The added list is unchanged.
///
/// This method takes, on average, constant time, regardless of the size
/// of either list. Although conceptually all of the items in are
/// copied, storage is shared between the two lists until changes are made to the
/// shared sections.
/// The list of items to add.
/// is null.
public void AddRangeToFront(BigList list)
{
if (list == null)
throw new ArgumentNullException("list");
if ((uint)Count + (uint)list.Count > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
if (list.Count == 0)
return;
StopEnumerations();
if (root == null) {
list.root.MarkShared();
root = list.root;
}
else {
Node newRoot = root.PrependInPlace(list.root, false);
if (newRoot != root) {
root = newRoot;
CheckBalance();
}
}
}
///
/// Concatenates two lists together to create a new list. Both lists being concatenated
/// are unchanged. The resulting list contains all the items in , followed
/// by all the items in .
///
/// This method takes, on average, constant time, regardless of the size
/// of either list. Although conceptually all of the items in both lists are
/// copied, storage is shared until changes are made to the
/// shared sections.
/// The first list to concatenate.
/// The second list to concatenate.
/// or is null.
public static BigList operator +(BigList first, BigList second)
{
if (first == null)
throw new ArgumentNullException("first");
if (second == null)
throw new ArgumentNullException("second");
if ((uint)first.Count + (uint)second.Count > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
if (first.Count == 0)
return second.Clone();
else if (second.Count == 0)
return first.Clone();
else {
BigList result = new BigList(first.root.Append(second.root, false));
result.CheckBalance();
return result;
}
}
///
/// Creates a new list that contains a subrange of elements from this list. The
/// current list is unchanged.
///
/// This method takes take O(log N), where N is the size of the current list. Although
/// the sub-range is conceptually copied, storage is shared between the two lists until a change
/// is made to the shared items.
/// If a view of a sub-range is desired, instead of a copy, use the
/// more efficient method, which provides a view onto a sub-range of items.
/// The starting index of the sub-range.
/// The number of items in the sub-range. If this is zero,
/// the returned list is empty.
/// A new list with the items that start at .
public BigList GetRange(int index, int count)
{
if (count == 0)
return new BigList();
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException("index");
if (count < 0 || count > Count - index)
throw new ArgumentOutOfRangeException("count");
return new BigList(root.Subrange(index, index + count - 1));
}
///
/// Returns a view onto a sub-range of this list. Items are not copied; the
/// returned IList<T> is simply a different view onto the same underlying items. Changes to this list
/// are reflected in the view, and vice versa. Insertions and deletions in the view change the size of the
/// view, but insertions and deletions in the underlying list do not.
///
///
/// If a copy of the sub-range is desired, use the method instead.
/// This method can be used to apply an algorithm to a portion of a list. For example:
/// Algorithms.ReverseInPlace(list.Range(3, 6))
/// will reverse the 6 items beginning at index 3.
/// The starting index of the view.
/// The number of items in the view.
/// A list that is a view onto the given sub-list.
/// or is negative.
/// + is greater than the
/// size of this list.
public sealed override IList Range(int index, int count)
{
if (index < 0 || index > this.Count || (index == this.Count && count != 0))
throw new ArgumentOutOfRangeException("index");
if (count < 0 || count > this.Count || count + index > this.Count)
throw new ArgumentOutOfRangeException("count");
return new BigListRange(this, index, count);
}
///
/// Enumerates a range of the items in the list, in order. The item at
/// is enumerated first, then the next item at index 1, and so on. At most
/// items are enumerated.
///
/// Enumerating all of the items in the list take time O(N), where
/// N is the number of items being enumerated. Using GetEnumerator() or foreach
/// is much more efficient than accessing all items by index.
/// Index to start enumerating at.
/// Max number of items to enumerate.
/// An IEnumerator<T> that enumerates all the
/// items in the given range.
private IEnumerator GetEnumerator(int start, int maxItems)
{
// We could use a recursive enumerator here, but an explicit stack
// is a lot more efficient, and efficiency matters here.
int startStamp = changeStamp; // to detect changes during enumeration.
if (root != null && maxItems > 0) {
ConcatNode[] stack = new ConcatNode[root.Depth];
bool[] leftStack = new bool[root.Depth];
int stackPtr = 0, startIndex = 0;
Node current = root;
LeafNode currentLeaf;
ConcatNode currentConcat;
if (start != 0) {
// Set current to the node containing start, and set startIndex to
// the index within that node.
if (start < 0 || start >= root.Count)
throw new ArgumentOutOfRangeException("start");
currentConcat = current as ConcatNode;
startIndex = start;
while (currentConcat != null) {
stack[stackPtr] = currentConcat;
int leftCount = currentConcat.left.Count;
if (startIndex < leftCount) {
leftStack[stackPtr] = true;
current = currentConcat.left;
}
else {
leftStack[stackPtr] = false;
current = currentConcat.right;
startIndex -= leftCount;
}
++stackPtr;
currentConcat = current as ConcatNode;
}
}
for (; ; ) {
// If not already at a leaf, walk to the left to find a leaf node.
while ((currentConcat = current as ConcatNode) != null) {
stack[stackPtr] = currentConcat;
leftStack[stackPtr] = true;
++stackPtr;
current = currentConcat.left;
}
// Iterate the leaf.
currentLeaf = (LeafNode)current;
int limit = currentLeaf.Count;
if (limit > startIndex + maxItems)
limit = startIndex + maxItems;
for (int i = startIndex; i < limit; ++i) {
yield return currentLeaf.items[i];
CheckEnumerationStamp(startStamp);
}
// Update the number of items to interate.
maxItems -= limit - startIndex;
if (maxItems <= 0)
yield break; // Done!
// From now on, start enumerating at 0.
startIndex = 0;
// Go back up the stack until we find a place to the right
// we didn't just come from.
for (; ; ) {
ConcatNode parent;
if (stackPtr == 0)
yield break; // iteration is complete.
parent = stack[--stackPtr];
if (leftStack[stackPtr]) {
leftStack[stackPtr] = false;
++stackPtr;
current = parent.right;
break;
}
current = parent;
// And keep going up...
}
// current is now a new node we need to visit. Loop around to get it.
}
}
}
///
/// Enumerates all of the items in the list, in order. The item at index 0
/// is enumerated first, then the item at index 1, and so on. Usually, the
/// foreach statement is used to call this method implicitly.
///
/// Enumerating all of the items in the list take time O(N), where
/// N is the number of items in the list. Using GetEnumerator() or foreach
/// is much more efficient than accessing all items by index.
/// An IEnumerator<T> that enumerates all the
/// items in the list.
public sealed override IEnumerator GetEnumerator()
{
return GetEnumerator(0, int.MaxValue);
}
///
/// Given an IEnumerable<T>, create a new Node with all of the
/// items in the enumerable. Returns null if the enumerable has no items.
///
/// The collection to copy.
/// Returns a Node, not shared or with any shared children,
/// with the items from the collection. If the collection was empty,
/// null is returned.
private Node NodeFromEnumerable(IEnumerable collection)
{
Node node = null;
LeafNode leaf;
IEnumerator enumerator = collection.GetEnumerator();
while ((leaf = LeafFromEnumerator(enumerator)) != null) {
if (node == null)
node = leaf;
else {
if ((uint)(node.count) + (uint)(leaf.count) > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
node = node.AppendInPlace(leaf, true);
}
}
return node;
}
///
/// Consumes up to MAXLEAF items from an Enumerator and places them in a leaf
/// node. If the enumerator is at the end, null is returned.
///
/// The enumerator to take items from.
/// A LeafNode with items taken from the enumerator.
private LeafNode LeafFromEnumerator(IEnumerator enumerator)
{
int i = 0;
T[] items = null;
while (i < MAXLEAF && enumerator.MoveNext()) {
if (i == 0)
items = new T[MAXLEAF];
items[i++] = enumerator.Current;
}
if (items != null)
return new LeafNode(i, items);
else
return null;
}
///
/// Create a node that has N copies of the given node.
///
/// Number of copies. Must be non-negative.
/// Node to make copies of.
/// null if node is null or copies is 0. Otherwise, a node consisting of copies
/// of node.
/// copies is negative.
private Node NCopiesOfNode(int copies, Node node)
{
if (copies < 0)
throw new ArgumentOutOfRangeException("copies", Strings.ArgMustNotBeNegative);
// Do the simple cases.
if (copies == 0 || node == null)
return null;
if (copies == 1)
return node;
if ((long)copies * (long)(node.count) > MAXITEMS)
throw new InvalidOperationException(Strings.CollectionTooLarge);
// Build up the copies by powers of two.
int n = 1;
Node power = node, builder = null;
while (copies > 0) {
power.MarkShared();
if ((copies & n) != 0) {
// This power of two is used in the final result.
copies -= n;
if (builder == null)
builder = power;
else
builder = builder.Append(power, false);
}
n *= 2;
power = power.Append(power, false);
}
return builder;
}
///
/// Check the balance of the current tree and rebalance it if it is more than BALANCEFACTOR
/// levels away from fully balanced. Note that rebalancing a tree may leave it two levels away from
/// fully balanced.
///
private void CheckBalance()
{
if (root != null &&
(root.Depth > BALANCEFACTOR && !(root.Depth - BALANCEFACTOR <= MAXFIB && Count >= FIBONACCI[root.Depth - BALANCEFACTOR])))
{
Rebalance();
}
}
///
/// Rebalance the current tree. Once rebalanced, the depth of the current tree is no more than
/// two levels from fully balanced, where fully balanced is defined as having Fibonacci(N+2) or more items
/// in a tree of depth N.
///
/// The rebalancing algorithm is from "Ropes: an Alternative to Strings", by
/// Boehm, Atkinson, and Plass, in SOFTWARE--PRACTICE AND EXPERIENCE, VOL. 25(12), 1315–1330 (DECEMBER 1995).
///
internal void Rebalance()
{
Node[] rebalanceArray;
int slots;
// The basic rebalancing algorithm is add nodes to a rabalance array, where a node at index K in the
// rebalance array has Fibonacci(K+1) to Fibonacci(K+2) items, and the entire list has the nodes
// from largest to smallest concatenated.
if (root == null)
return;
if (root.Depth <= 1 || (root.Depth-2 <= MAXFIB && Count >= FIBONACCI[root.Depth-2]))
return; // already sufficiently balanced.
// How many slots does the rebalance array need?
for (slots = 0; slots <= MAXFIB; ++slots)
if (root.Count < FIBONACCI[slots])
break;
rebalanceArray = new Node[slots];
// Add all the nodes to the rebalance array.
AddNodeToRebalanceArray(rebalanceArray, root, false);
// Concatinate all the node in the rebalance array.
Node result = null;
for (int slot = 0; slot < slots; ++slot) {
Node n = rebalanceArray[slot];
if (n != null) {
if (result == null)
result = n;
else
result = result.PrependInPlace(n, !n.Shared);
}
}
// And we're done. Check that it worked!
root = result;
Debug.Assert(root.Depth <= 1 || (root.Depth - 2 <= MAXFIB && Count >= FIBONACCI[root.Depth - 2]));
}
///
/// Part of the rebalancing algorithm. Adds a node to the rebalance array. If it is already balanced, add it directly, otherwise
/// add its children.
///
/// Rebalance array to insert into.
/// Node to add.
/// If true, mark the node as shared before adding, because one
/// of its parents was shared.
private void AddNodeToRebalanceArray(Node[] rebalanceArray, Node node, bool shared)
{
if (node.Shared)
shared = true;
if (node.IsBalanced()) {
if (shared)
node.MarkShared();
AddBalancedNodeToRebalanceArray(rebalanceArray, node);
}
else {
ConcatNode n = (ConcatNode)node; // leaf nodes are always balanced.
AddNodeToRebalanceArray(rebalanceArray, n.left, shared);
AddNodeToRebalanceArray(rebalanceArray, n.right, shared);
}
}
///
/// Part of the rebalancing algorithm. Adds a balanced node to the rebalance array.
///
/// Rebalance array to insert into.
/// Node to add.
private void AddBalancedNodeToRebalanceArray(Node[] rebalanceArray, Node balancedNode)
{
int slot;
int count;
Node accum = null;
Debug.Assert(balancedNode.IsBalanced());
count = balancedNode.Count;
slot = 0;
while (count >= FIBONACCI[slot + 1]) {
Node n = rebalanceArray[slot];
if (n != null) {
rebalanceArray[slot] = null;
if (accum == null)
accum = n;
else
accum = accum.PrependInPlace(n, !n.Shared);
}
++slot;
}
// slot is the location where balancedNode originally ended up, but possibly
// not the final resting place.
if (accum != null)
balancedNode = balancedNode.PrependInPlace(accum, !accum.Shared);
for (;;) {
Node n = rebalanceArray[slot];
if (n != null) {
rebalanceArray[slot] = null;
balancedNode = balancedNode.PrependInPlace(n, !n.Shared);
}
if (balancedNode.Count < FIBONACCI[slot + 1]) {
rebalanceArray[slot] = balancedNode;
break;
}
++slot;
}
#if DEBUG
// The above operations should ensure that everything in the rebalance array is now almost balanced.
for (int i = 0; i < rebalanceArray.Length; ++i) {
if (rebalanceArray[i] != null)
Debug.Assert(rebalanceArray[i].IsAlmostBalanced());
}
#endif //DEBUG
}
///
/// Convert the list to a new list by applying a delegate to each item in the collection. The resulting list
/// contains the result of applying to each item in the list, in
/// order. The current list is unchanged.
///
/// The type each item is being converted to.
/// A delegate to the method to call, passing each item in .
/// The resulting BigList from applying to each item in this list.
/// is null.
public new BigList ConvertAll(Converter converter)
{
return new BigList(Algorithms.Convert(this, converter));
}
///
/// Reverses the current list in place.
///
public void Reverse()
{
Algorithms.ReverseInPlace(this);
}
///
/// Reverses the items in the range of items starting from , in place.
///
/// The starting index of the range to reverse.
/// The number of items in range to reverse.
public void Reverse(int start, int count)
{
Algorithms.ReverseInPlace(Range(start, count));
}
///
/// Sorts the list in place.
///
/// The Quicksort algorithm is used to sort the items. In virtually all cases,
/// this takes time O(N log N), where N is the number of items in the list.
/// Values are compared by using the IComparable or IComparable<T>
/// interface implementation on the type T.
/// The type T does not implement either the IComparable or
/// IComparable<T> interfaces.
public void Sort()
{
Sort(Comparers.DefaultComparer());
}
///
/// Sorts the list in place. A supplied IComparer<T> is used
/// to compare the items in the list.
///
/// The Quicksort algorithms is used to sort the items. In virtually all cases,
/// this takes time O(N log N), where N is the number of items in the list.
/// The comparer instance used to compare items in the collection. Only
/// the Compare method is used.
public void Sort(IComparer comparer)
{
Algorithms.SortInPlace(this, comparer);
}
///
/// Sorts the list in place. A supplied Comparison<T> delegate is used
/// to compare the items in the list.
///
/// The Quicksort algorithms is used to sort the items. In virtually all cases,
/// this takes time O(N log N), where N is the number of items in the list.
/// The comparison delegate used to compare items in the collection.
public void Sort(Comparison comparison)
{
Sort(Comparers.ComparerFromComparison(comparison));
}
///
/// Searches a sorted list for an item via binary search. The list must be sorted
/// in the order defined by the default ordering of the item type; otherwise,
/// incorrect results will be returned.
///
/// The item to search for.
/// Returns the index of the first occurence of in the list. If the item does not occur
/// in the list, the bitwise complement of the first item larger than in the list is returned. If no item is
/// larger than , the bitwise complement of Count is returned.
/// The type T does not implement either the IComparable or
/// IComparable<T> interfaces.
public int BinarySearch(T item)
{
return BinarySearch(item, Comparers.DefaultComparer());
}
///
/// Searches a sorted list for an item via binary search. The list must be sorted
/// by the ordering defined by the passed IComparer<T> interface; otherwise,
/// incorrect results will be returned.
///
/// The item to search for.
/// The IComparer<T> interface used to sort the list.
/// Returns the index of the first occurence of in the list. If the item does not occur
/// in the list, the bitwise complement of the first item larger than in the list is returned. If no item is
/// larger than , the bitwise complement of Count is returned.
public int BinarySearch(T item, IComparer comparer)
{
int count, index;
count = Algorithms.BinarySearch(this, item, comparer, out index);
if (count == 0)
return (~index);
else
return index;
}
///
/// Searches a sorted list for an item via binary search. The list must be sorted
/// by the ordering defined by the passed Comparison<T> delegate; otherwise,
/// incorrect results will be returned.
///
/// The item to search for.
/// The comparison delegate used to sort the list.
/// Returns the index of the first occurence of in the list. If the item does not occur
/// in the list, the bitwise complement of the first item larger than in the list is returned. If no item is
/// larger than , the bitwise complement of Count is returned.
public int BinarySearch(T item, Comparison comparison)
{
return BinarySearch(item, Comparers.ComparerFromComparison(comparison));
}
#if DEBUG
///
/// Attempts to validate the internal consistency of the tree.
///
public void Validate()
{
if (root != null) {
root.Validate();
Debug.Assert(Count != 0);
}
else
Debug.Assert(Count == 0);
}
///
/// Prints out the internal structure of the tree, for debugging purposes.
///
public void Print()
{
Console.WriteLine("SERIES: Count={0}", Count);
if (Count > 0) {
Console.Write("ITEMS: ");
foreach (T item in this) {
Console.Write("{0} ", item);
}
Console.WriteLine();
Console.WriteLine("TREE:");
root.Print(" ", " ");
}
Console.WriteLine();
}
#endif //DEBUG
///
/// The base class for the two kinds of nodes in the tree: Concat nodes
/// and Leaf nodes.
///
[Serializable]
private abstract class Node
{
// Number of items in this node.
public int count;
// If true, indicates that this node is referenced by multiple
// concat nodes or multiple BigList. Neither this node nor
// nodes below it may be modifed ever again. Never becomes
// false after being set to true. It's volatile so that accesses
// from another thread work appropriately -- if shared is set
// to true, no other thread will attempt to change the node.
protected volatile bool shared;
///
/// The number of items stored in the node (or below it).
///
/// The number of items in the node or below.
public int Count
{
get { return count; }
}
///
/// Is this node shared by more that one list (or within a single)
/// lists. If true, indicates that this node, and any nodes below it,
/// may never be modified. Never becomes false after being set to
/// true.
///
///
public bool Shared
{
get { return shared; }
}
///
/// Marks this node as shared by setting the shared variable.
///
public void MarkShared()
{
shared = true;
}
///
/// Gets the depth of this node. A leaf node has depth 0,
/// a concat node with two leaf children has depth 1, etc.
///
/// The depth of this node.
public abstract int Depth { get;}
///
/// Returns the items at the given index in this node.
///
/// 0-based index, relative to this node.
/// Item at that index.
public abstract T GetAt(int index);
///
/// Returns a node that has a sub-range of items from this node. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive first element, relative to this node.
/// Inclusize last element, relative to this node.
/// Node with the given sub-range.
public abstract Node Subrange(int first, int last);
// Any operation that could potentially modify a node exists
// in two forms -- the "in place" form that possibly modifies the
// node, and the non-"in place" that returns a new node with
// the modification. However, even "in-place" operations may return
// a new node, because a shared node can never be modified, even
// by an in-place operation.
///
/// Changes the item at the given index. Never changes this node,
/// but always returns a new node with the given item changed.
///
/// Index, relative to this node, to change.
/// New item to place at the given index.
/// A new node with the given item changed.
public abstract Node SetAt(int index, T item);
///
/// Changes the item at the given index. May change this node,
/// or return a new node with the given item changed.
///
/// Index, relative to this node, to change.
/// New item to place at the given index.
/// A node with the give item changed. If it can be done in place
/// then "this" is returned.
public abstract Node SetAtInPlace(int index, T item);
///
/// Append a node after this node. Never changes this node, but returns
/// a new node with the given appending done.
///
/// Node to append.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A new node with the give node appended to this node.
public abstract Node Append(Node node, bool nodeIsUnused);
///
/// Append a node after this node. May change this node, or return
/// a new node.
///
/// Node to append.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A node with the give node appended to this node. May be a new
/// node or the current node.
public abstract Node AppendInPlace(Node node, bool nodeIsUnused);
///
/// Append a item after this node. May change this node, or return
/// a new node. Equivalent to AppendInPlace(new LeafNode(item), true), but
/// may be more efficient because a new LeafNode might not be allocated.
///
/// Item to append.
/// A node with the given item appended to this node. May be a new
/// node or the current node.
public abstract Node AppendInPlace(T item);
///
/// Remove a range of items from this node. Never changes this node, but returns
/// a new node with the removing done. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive index of first item in sub-range, relative
/// to this node.
/// Inclusize index of last item in sub-range, relative
/// to this node.
/// A new node with the sub-range removed.
public abstract Node RemoveRange(int first, int last);
///
/// Remove a range of items from this node. May change this node, or returns
/// a new node with the given appending done. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive index of first item in sub-range, relative
/// to this node.
/// Inclusize index of last item in sub-range, relative
/// to this node.
/// A node with the sub-range removed. If done in-place, returns
/// "this".
public abstract Node RemoveRangeInPlace(int first, int last);
///
/// Inserts a node inside this node. Never changes this node, but returns
/// a new node with the given appending done.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Node to insert.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A new node with the give node inserted.
public abstract Node Insert(int index, Node node, bool nodeIsUnused);
///
/// Inserts an item inside this node. May change this node, or return
/// a new node with the given appending done. Equivalent to
/// InsertInPlace(new LeafNode(item), true), but may be more efficient.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Item to insert.
/// A node with the give item inserted. If done in-place, returns
/// "this".
public abstract Node InsertInPlace(int index, T item);
///
/// Inserts a node inside this node. May change this node, or return
/// a new node with the given appending done.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Node to insert.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A node with the given item inserted. If done in-place, returns
/// "this".
public abstract Node InsertInPlace(int index, Node node, bool nodeIsUnused);
#if DEBUG
///
/// Validates the node for consistency, as much as possible. Also validates
/// child nodes, if any.
///
public abstract void Validate();
///
/// Print out the contents of this node.
///
/// Prefix to use in front of this node.
/// Prefixed to use in front of children of this node.
public abstract void Print(string prefixNode, string prefixChildren);
#endif //DEBUG
///
/// Prefpend a node before this node. Never changes this node, but returns
/// a new node with the given prepending done.
///
/// Node to prepend.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A new node with the give node prepended to this node.
public Node Prepend(Node node, bool nodeIsUnused)
{
if (nodeIsUnused)
return node.AppendInPlace(this, false);
else
return node.Append(this, false);
}
///
/// Prepend a node before this node. May change this node, or return
/// a new node.
///
/// Node to prepend.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A node with the give node prepended to this node. May be a new
/// node or the current node.
public Node PrependInPlace(Node node, bool nodeIsUnused)
{
if (nodeIsUnused)
return node.AppendInPlace(this, !this.shared);
else
return node.Append(this, !this.shared);
}
///
/// Prepend a item before this node. May change this node, or return
/// a new node. Equivalent to PrependInPlace(new LeafNode(item), true), but
/// may be more efficient because a new LeafNode might not be allocated.
///
/// Item to prepend.
/// A node with the given item prepended to this node. May be a new
/// node or the current node.
public abstract Node PrependInPlace(T item);
///
/// Determine if this node is balanced. A node is balanced if the number
/// of items is greater than
/// Fibonacci(Depth+2). Balanced nodes are never rebalanced unless
/// they go out of balance again.
///
/// True if the node is balanced by this definition.
public bool IsBalanced()
{
return (Depth <= MAXFIB && Count >= FIBONACCI[Depth]);
}
///
/// Determine if this node is almost balanced. A node is almost balanced if t
/// its depth is at most one greater than a fully balanced node with the same count.
///
/// True if the node is almost balanced by this definition.
public bool IsAlmostBalanced()
{
return (Depth == 0 || (Depth - 1 <= MAXFIB && Count >= FIBONACCI[Depth - 1]));
}
}
///
/// The LeafNode class is the type of node that lives at the leaf of a tree and holds
/// the actual items stored in the list. Each leaf holds at least 1, and at most MAXLEAF
/// items in the items array. The number of items stored is found in "count", which may
/// be less than "items.Length".
///
[Serializable]
private sealed class LeafNode : Node
{
///
/// Array that stores the items in the nodes. Always has a least "count" elements,
/// but may have more as padding.
///
public T[] items;
///
/// Creates a LeafNode that holds a single item.
///
/// Item to place into the leaf node.
public LeafNode(T item)
{
// CONSIDER: is MAXLEAF always the right thing to do? It seems to work well in most cases.
count = 1;
items = new T[MAXLEAF];
items[0] = item;
}
///
/// Creates a new leaf node with the indicates count of item and the
///
/// Number of items. Can't be zero.
/// The array of items. The LeafNode takes
/// possession of this array.
public LeafNode(int count, T[] newItems)
{
Debug.Assert(count <= newItems.Length && count > 0);
Debug.Assert(newItems.Length <= MAXLEAF);
this.count = count;
items = newItems;
}
public override int Depth
{
get { return 0; }
}
///
/// Returns the items at the given index in this node.
///
/// 0-based index, relative to this node.
/// Item at that index.
public override T GetAt(int index)
{
return items[index];
}
///
/// Changes the item at the given index. May change this node,
/// or return a new node with the given item changed.
///
/// Index, relative to this node, to change.
/// New item to place at the given index.
/// A node with the give item changed. If it can be done in place
/// then "this" is returned.
public override Node SetAtInPlace(int index, T item)
{
if (shared)
return SetAt(index, item); // Can't update a shared node in place.
items[index] = item;
return this;
}
///
/// Changes the item at the given index. Never changes this node,
/// but always returns a new node with the given item changed.
///
/// Index, relative to this node, to change.
/// New item to place at the given index.
/// A new node with the given item changed.
public override Node SetAt(int index, T item)
{
T[] newItems = (T[])items.Clone();
newItems[index] = item;
return new LeafNode(count, newItems);
}
///
/// If other is a leaf node, and the resulting size would be less than MAXLEAF, merge
/// the other leaf node into this one (after this one) and return true.
///
/// Other node to possible merge.
/// If could be merged into this node, returns
/// true. Otherwise returns false and the current node is unchanged.
private bool MergeLeafInPlace(Node other)
{
Debug.Assert(!shared);
LeafNode otherLeaf = (other as LeafNode);
int newCount;
if (otherLeaf != null && (newCount = otherLeaf.Count + this.count) <= MAXLEAF) {
// Combine the two leaf nodes into one.
if (newCount > items.Length) {
T[] newItems = new T[MAXLEAF];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
Array.Copy(otherLeaf.items, 0, items, count, otherLeaf.count);
count = newCount;
return true;
}
return false;
}
///
/// If other is a leaf node, and the resulting size would be less than MAXLEAF, merge
/// the other leaf node with this one (after this one) and return a new node with
/// the merged items. Does not modify this.
/// If no merging, return null.
///
/// Other node to possible merge.
/// If the nodes could be merged, returns the new node. Otherwise
/// returns null.
private Node MergeLeaf(Node other)
{
LeafNode otherLeaf = (other as LeafNode);
int newCount;
if (otherLeaf != null && (newCount = otherLeaf.Count + this.count) <= MAXLEAF) {
// Combine the two leaf nodes into one.
T[] newItems = new T[MAXLEAF];
Array.Copy(items, 0, newItems, 0, count);
Array.Copy(otherLeaf.items, 0, newItems, count, otherLeaf.count);
return new LeafNode(newCount, newItems);
}
return null;
}
///
/// Prepend a item before this node. May change this node, or return
/// a new node. Equivalent to PrependInPlace(new LeafNode(item), true), but
/// may be more efficient because a new LeafNode might not be allocated.
///
/// Item to prepend.
/// A node with the given item prepended to this node. May be a new
/// node or the current node.
public override Node PrependInPlace(T item)
{
if (shared)
return Prepend(new LeafNode(item), true); // Can't update a shared node in place.
// Add into the current leaf, if possible.
if (count < MAXLEAF) {
if (count == items.Length) {
T[] newItems = new T[MAXLEAF];
Array.Copy(items, 0, newItems, 1, count);
items = newItems;
}
else {
Array.Copy(items, 0, items, 1, count);
}
items[0] = item;
count += 1;
return this;
}
else {
return new ConcatNode(new LeafNode(item), this);
}
}
///
/// Append a item after this node. May change this node, or return
/// a new node. Equivalent to AppendInPlace(new LeafNode(item), true), but
/// may be more efficient because a new LeafNode might not be allocated.
///
/// Item to append.
/// A node with the given item appended to this node. May be a new
/// node or the current node.
public override Node AppendInPlace(T item)
{
if (shared)
return Append(new LeafNode(item), true); // Can't update a shared node in place.
// Add into the current leaf, if possible.
if (count < MAXLEAF) {
if (count == items.Length) {
T[] newItems = new T[MAXLEAF];
Array.Copy(items, 0, newItems, 0, count);
items = newItems;
}
items[count] = item;
count += 1;
return this;
}
else {
return new ConcatNode(this, new LeafNode(item));
}
}
///
/// Append a node after this node. May change this node, or return
/// a new node.
///
/// Node to append.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A node with the give node appended to this node. May be a new
/// node or the current node.
public override Node AppendInPlace(Node node, bool nodeIsUnused)
{
if (shared)
return Append(node, nodeIsUnused); // Can't update a shared node in place.
// If we're appending a leaf, try to merge them if possible.
if (MergeLeafInPlace(node)) {
return this;
}
// If we're appending a tree with a left leaf node, try to merge them if possible.
ConcatNode otherConcat = (node as ConcatNode);
if (otherConcat != null && MergeLeafInPlace(otherConcat.left)) {
if (! nodeIsUnused)
otherConcat.right.MarkShared();
return new ConcatNode(this, otherConcat.right);
}
// Otherwise, create a Concat node.
if (! nodeIsUnused)
node.MarkShared();
return new ConcatNode(this, node);
}
public override Node Append(Node node, bool nodeIsUnused)
{
Node result;
// If we're appending a leaf, try to merge them if possible.
if ((result = MergeLeaf(node)) != null)
return result;
// If we're appending a concat with a left leaf, try to merge them if possible.
ConcatNode otherConcat = (node as ConcatNode);
if (otherConcat != null && (result = MergeLeaf(otherConcat.left)) != null) {
if (! nodeIsUnused)
otherConcat.right.MarkShared();
return new ConcatNode(result, otherConcat.right);
}
// Otherwise, create a Concat node.
if (!nodeIsUnused)
node.MarkShared();
MarkShared();
return new ConcatNode(this, node);
}
///
/// Inserts an item inside this node. May change this node, or return
/// a new node with the given appending done. Equivalent to
/// InsertInPlace(new LeafNode(item), true), but may be more efficient.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Item to insert.
/// A node with the give item inserted. If done in-place, returns
/// "this".
public override Node InsertInPlace(int index, T item)
{
if (shared)
return Insert(index, new LeafNode(item), true); // Can't update a shared node in place.
// Insert into the current leaf, if possible.
if (count < MAXLEAF) {
if (count == items.Length) {
T[] newItems = new T[MAXLEAF];
if (index > 0)
Array.Copy(items, 0, newItems, 0, index);
if (count > index)
Array.Copy(items, index, newItems, index + 1, count - index);
items = newItems;
}
else {
if (count > index)
Array.Copy(items, index, items, index + 1, count - index);
}
items[index] = item;
count += 1;
return this;
}
else {
if (index == count) {
// Inserting at count is just an appending operation.
return new ConcatNode(this, new LeafNode(item));
}
else if (index == 0) {
// Inserting at 0 is just a prepending operation.
return new ConcatNode(new LeafNode(item), this);
}
else {
// Split into two nodes, and put the new item at the end of the first.
T[] leftItems = new T[MAXLEAF];
Array.Copy(items, 0, leftItems, 0, index);
leftItems[index] = item;
Node leftNode = new LeafNode(index + 1, leftItems);
T[] rightItems = new T[count - index];
Array.Copy(items, index, rightItems, 0, count - index);
Node rightNode = new LeafNode(count - index, rightItems);
return new ConcatNode(leftNode, rightNode);
}
}
}
///
/// Inserts a node inside this node. May change this node, or return
/// a new node with the given appending done.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Node to insert.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A node with the given item inserted. If done in-place, returns
/// "this".
public override Node InsertInPlace(int index, Node node, bool nodeIsUnused)
{
if (shared)
return Insert(index, node, nodeIsUnused); // Can't update a shared node in place.
LeafNode otherLeaf = (node as LeafNode);
int newCount;
if (otherLeaf != null && (newCount = otherLeaf.Count + this.count) <= MAXLEAF) {
// Combine the two leaf nodes into one.
if (newCount > items.Length) {
T[] newItems = new T[MAXLEAF];
Array.Copy(items, 0, newItems, 0, index);
Array.Copy(otherLeaf.items, 0, newItems, index, otherLeaf.Count);
Array.Copy(items, index, newItems, index + otherLeaf.Count, count - index);
items = newItems;
}
else {
Array.Copy(items, index, items, index + otherLeaf.Count, count - index);
Array.Copy(otherLeaf.items, 0, items, index, otherLeaf.count);
}
count = newCount;
return this;
}
else if (index == 0) {
// Inserting at 0 is a prepend.
return PrependInPlace(node, nodeIsUnused);
}
else if (index == count) {
// Inserting at count is an append.
return AppendInPlace(node, nodeIsUnused);
}
else {
// Split existing node into two nodes at the insertion point, then concat all three nodes together.
T[] leftItems = new T[index];
Array.Copy(items, 0, leftItems, 0, index);
Node leftNode = new LeafNode(index, leftItems);
T[] rightItems = new T[count - index];
Array.Copy(items, index, rightItems, 0, count - index);
Node rightNode = new LeafNode(count - index, rightItems);
leftNode = leftNode.AppendInPlace(node, nodeIsUnused);
leftNode = leftNode.AppendInPlace(rightNode, true);
return leftNode;
}
}
///
/// Inserts a node inside this node. Never changes this node, but returns
/// a new node with the given appending done.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Node to insert.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A new node with the give node inserted.
public override Node Insert(int index, Node node, bool nodeIsUnused)
{
LeafNode otherLeaf = (node as LeafNode);
int newCount;
if (otherLeaf != null && (newCount = otherLeaf.Count + this.count) <= MAXLEAF) {
// Combine the two leaf nodes into one.
T[] newItems = new T[MAXLEAF];
Array.Copy(items, 0, newItems, 0, index);
Array.Copy(otherLeaf.items, 0, newItems, index, otherLeaf.Count);
Array.Copy(items, index, newItems, index + otherLeaf.Count, count - index);
return new LeafNode(newCount, newItems);
}
else if (index == 0) {
// Inserting at 0 is a prepend.
return Prepend(node, nodeIsUnused);
}
else if (index == count) {
// Inserting at count is an append.
return Append(node, nodeIsUnused);
}
else {
// Split existing node into two nodes at the insertion point, then concat all three nodes together.
T[] leftItems = new T[index];
Array.Copy(items, 0, leftItems, 0, index);
Node leftNode = new LeafNode(index, leftItems);
T[] rightItems = new T[count - index];
Array.Copy(items, index, rightItems, 0, count - index);
Node rightNode = new LeafNode(count - index, rightItems);
leftNode = leftNode.AppendInPlace(node, nodeIsUnused);
leftNode = leftNode.AppendInPlace(rightNode, true);
return leftNode;
}
}
///
/// Remove a range of items from this node. May change this node, or returns
/// a new node with the given appending done. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive index of first item in sub-range, relative
/// to this node.
/// Inclusize index of last item in sub-range, relative
/// to this node.
/// A node with the sub-range removed. If done in-place, returns
/// "this".
public override Node RemoveRangeInPlace(int first, int last)
{
if (shared)
return RemoveRange(first, last);
Debug.Assert(first <= last);
Debug.Assert(last >= 0);
if (first <= 0 && last >= count - 1) {
return null; // removing entire node.
}
if (first < 0)
first = 0;
if (last >= count)
last = count - 1;
int newCount = first + (count - last - 1); // number of items remaining.
if (count > last + 1)
Array.Copy(items, last + 1, items, first, count - last - 1);
for (int i = newCount; i < count; ++i)
items[i] = default(T);
count = newCount;
return this;
}
///
/// Remove a range of items from this node. Never changes this node, but returns
/// a new node with the removing done. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive index of first item in sub-range, relative
/// to this node.
/// Inclusize index of last item in sub-range, relative
/// to this node.
/// A new node with the sub-range removed.
public override Node RemoveRange(int first, int last)
{
Debug.Assert(first <= last);
Debug.Assert(last >= 0);
if (first <= 0 && last >= count - 1) {
return null; // removing entire node.
}
if (first < 0)
first = 0;
if (last >= count)
last = count - 1;
int newCount = first + (count - last - 1); // number of items remaining.
T[] newItems = new T[newCount];
if (first > 0)
Array.Copy(items, 0, newItems, 0, first);
if (count > last + 1)
Array.Copy(items, last + 1, newItems, first, count - last - 1);
return new LeafNode(newCount, newItems);
}
///
/// Returns a node that has a sub-range of items from this node. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive first element, relative to this node.
/// Inclusize last element, relative to this node.
/// Node with the given sub-range.
public override Node Subrange(int first, int last)
{
Debug.Assert(first <= last);
Debug.Assert(last >= 0);
if (first <= 0 && last >= count - 1) {
MarkShared();
return this;
}
else {
if (first < 0)
first = 0;
if (last >= count)
last = count - 1;
int n = last - first + 1;
T[] newItems = new T[n];
Array.Copy(items, first, newItems, 0, n);
return new LeafNode(n, newItems);
}
}
#if DEBUG
///
/// Validates the node for consistency, as much as possible. Also validates
/// child nodes, if any.
///
public override void Validate()
{
// Check count and length of buffer.
Debug.Assert(count > 0);
Debug.Assert(items != null);
Debug.Assert(items.Length > 0);
Debug.Assert(count <= MAXLEAF);
Debug.Assert(items.Length <= MAXLEAF);
Debug.Assert(count <= items.Length);
}
///
/// Print out the contents of this node.
///
/// Prefix to use in front of this node.
/// Prefixed to use in front of children of this node.
public override void Print(string prefixNode, string prefixChildren)
{
Console.Write("{0}LEAF {1} count={2}/{3} ", prefixNode, shared ? "S" : " ", count, items.Length);
for (int i = 0; i < count; ++i)
Console.Write("{0} ", items[i]);
Console.WriteLine();
}
#endif //DEBUG
}
///
/// A ConcatNode is an interior (non-leaf) node that represents the concatination of
/// the left and right child nodes. Both children must always be non-null.
///
[Serializable]
private sealed class ConcatNode : Node
{
///
/// The left and right child nodes. They are never null.
///
public Node left, right;
///
/// The depth of this node -- the maximum length path to
/// a leaf. If this node has two children that are leaves, the
/// depth in 1.
///
private short depth;
///
/// The depth of this node -- the maximum length path to
/// a leaf. If this node has two children that are leaves, the
/// depth in 1.
///
/// The depth of this node.
public override int Depth
{
get { return depth; }
}
///
/// Create a new ConcatNode with the given children.
///
/// The left child. May not be null.
/// The right child. May not be null.
public ConcatNode(Node left, Node right)
{
Debug.Assert(left != null && right != null);
this.left = left;
this.right = right;
this.count = left.Count + right.Count;
if (left.Depth > right.Depth)
this.depth = (short)(left.Depth + 1);
else
this.depth = (short)(right.Depth + 1);
}
///
/// Create a new node with the given children. Mark unchanged
/// children as shared. There are four
/// possible cases:
/// 1. If one of the new children is null, the other new child is returned.
/// 2. If neither child has changed, then this is marked as shared as returned.
/// 3. If one child has changed, the other child is marked shared an a new node is returned.
/// 4. If both children have changed, a new node is returned.
///
/// New left child.
/// New right child.
/// New node with the given children. Returns null if and only if both
/// new children are null.
private Node NewNode(Node newLeft, Node newRight)
{
if (left == newLeft) {
if (right == newRight) {
MarkShared();
return this; // Nothing changed. In this case we can return the same node.
}
else
left.MarkShared();
}
else {
if (right == newRight)
right.MarkShared();
}
if (newLeft == null)
return newRight;
else if (newRight == null)
return newLeft;
else
return new ConcatNode(newLeft, newRight);
}
///
/// Updates a node with the given new children. If one of the new children is
/// null, the other is returned. If both are null, null is returned.
///
/// New left child.
/// New right child.
/// Node with the given children. Usually, but not always, this. Returns
/// null if and only if both new children are null.
private Node NewNodeInPlace(Node newLeft, Node newRight)
{
Debug.Assert(!shared);
if (newLeft == null)
return newRight;
else if (newRight == null)
return newLeft;
left = newLeft;
right = newRight;
count = left.Count + right.Count;
if (left.Depth > right.Depth)
depth = (short)(left.Depth + 1);
else
depth = (short)(right.Depth + 1);
return this;
}
///
/// Returns the items at the given index in this node.
///
/// 0-based index, relative to this node.
/// Item at that index.
public override T GetAt(int index)
{
int leftCount = left.Count;
if (index < leftCount)
return left.GetAt(index);
else
return right.GetAt(index - leftCount);
}
///
/// Changes the item at the given index. May change this node,
/// or return a new node with the given item changed.
///
/// Index, relative to this node, to change.
/// New item to place at the given index.
/// A node with the give item changed. If it can be done in place
/// then "this" is returned.
public override Node SetAtInPlace(int index, T item)
{
if (shared)
return SetAt(index, item); // Can't update a shared node in place.
int leftCount = left.Count;
if (index < leftCount) {
Node newLeft = left.SetAtInPlace(index, item);
if (newLeft != left)
return NewNodeInPlace(newLeft, right);
else
return this;
}
else {
Node newRight = right.SetAtInPlace(index - leftCount, item);
if (newRight != right)
return NewNodeInPlace(left, newRight);
else
return this;
}
}
///
/// Changes the item at the given index. Never changes this node,
/// but always returns a new node with the given item changed.
///
/// Index, relative to this node, to change.
/// New item to place at the given index.
/// A new node with the given item changed.
public override Node SetAt(int index, T item)
{
int leftCount = left.Count;
if (index < leftCount) {
return NewNode(left.SetAt(index, item), right);
}
else {
return NewNode(left, right.SetAt(index - leftCount, item));
}
}
///
/// Prepend a item before this node. May change this node, or return
/// a new node. Equivalent to PrependInPlace(new LeafNode(item), true), but
/// may be more efficient because a new LeafNode might not be allocated.
///
/// Item to prepend.
/// A node with the given item prepended to this node. May be a new
/// node or the current node.
public override Node PrependInPlace(T item)
{
if (shared)
return Prepend(new LeafNode(item), true); // Can't update a shared node in place.
LeafNode leftLeaf;
if (left.Count < MAXLEAF && !left.Shared && (leftLeaf = left as LeafNode) != null) {
// Prepend the item to the left leaf. This keeps repeated prepends from creating
// single item nodes.
int c = leftLeaf.Count;
if (c == leftLeaf.items.Length) {
T[] newItems = new T[MAXLEAF];
Array.Copy(leftLeaf.items, 0, newItems, 1, c);
leftLeaf.items = newItems;
}
else {
Array.Copy(leftLeaf.items, 0, leftLeaf.items, 1, c);
}
leftLeaf.items[0] = item;
leftLeaf.count += 1;
this.count += 1;
return this;
}
else
return new ConcatNode(new LeafNode(item), this);
}
///
/// Append a item after this node. May change this node, or return
/// a new node. Equivalent to AppendInPlace(new LeafNode(item), true), but
/// may be more efficient because a new LeafNode might not be allocated.
///
/// Item to append.
/// A node with the given item appended to this node. May be a new
/// node or the current node.
public override Node AppendInPlace(T item)
{
if (shared)
return Append(new LeafNode(item), true); // Can't update a shared node in place.
LeafNode rightLeaf;
if (right.Count < MAXLEAF && !right.Shared && (rightLeaf = right as LeafNode) != null) {
int c = rightLeaf.Count;
if (c == rightLeaf.items.Length) {
T[] newItems = new T[MAXLEAF]; // use MAXLEAF when appending, because we'll probably append again.
Array.Copy(rightLeaf.items, 0, newItems, 0, c);
rightLeaf.items = newItems;
}
rightLeaf.items[c] = item;
rightLeaf.count += 1;
this.count += 1;
return this;
}
else
return new ConcatNode(this, new LeafNode(item));
}
///
/// Append a node after this node. May change this node, or return
/// a new node.
///
/// Node to append.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A node with the give node appended to this node. May be a new
/// node or the current node.
public override Node AppendInPlace(Node node, bool nodeIsUnused)
{
if (shared)
return Append(node, nodeIsUnused); // Can't update a shared node in place.
if (right.Count + node.Count <= MAXLEAF && right is LeafNode && node is LeafNode)
return NewNodeInPlace(left, right.AppendInPlace(node, nodeIsUnused));
if (!nodeIsUnused)
node.MarkShared();
return new ConcatNode(this, node);
}
public override Node Append(Node node, bool nodeIsUnused)
{
// If possible combine with a child leaf node on the right.
if (right.Count + node.Count <= MAXLEAF && right is LeafNode && node is LeafNode)
return NewNode(left, right.Append(node, nodeIsUnused));
// Concatinate with this node.
this.MarkShared();
if (!nodeIsUnused)
node.MarkShared();
return new ConcatNode(this, node);
}
///
/// Inserts an item inside this node. May change this node, or return
/// a new node with the given appending done. Equivalent to
/// InsertInPlace(new LeafNode(item), true), but may be more efficient.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Item to insert.
/// A node with the give item inserted. If done in-place, returns
/// "this".
public override Node InsertInPlace(int index, T item)
{
if (shared)
return Insert(index, new LeafNode(item), true);
int leftCount = left.Count;
if (index <= leftCount)
return NewNodeInPlace(left.InsertInPlace(index, item), right);
else
return NewNodeInPlace(left, right.InsertInPlace(index - leftCount, item));
}
///
/// Inserts a node inside this node. May change this node, or return
/// a new node with the given appending done.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Node to insert.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A node with the given item inserted. If done in-place, returns
/// "this".
public override Node InsertInPlace(int index, Node node, bool nodeIsUnused)
{
if (shared)
return Insert(index, node, nodeIsUnused);
int leftCount = left.Count;
if (index < leftCount)
return NewNodeInPlace(left.InsertInPlace(index, node, nodeIsUnused), right);
else
return NewNodeInPlace(left, right.InsertInPlace(index - leftCount, node, nodeIsUnused));
}
///
/// Inserts a node inside this node. Never changes this node, but returns
/// a new node with the given appending done.
///
/// Index, relative to this node, to insert at. Must
/// be in bounds.
/// Node to insert.
/// If true, the given node is not used
/// in any current list, so it may be change, overwritten, or destroyed
/// if convenient. If false, the given node is in use. It should be marked
/// as shared if is is used within the return value.
/// A new node with the give node inserted.
public override Node Insert(int index, Node node, bool nodeIsUnused)
{
int leftCount = left.Count;
if (index < leftCount)
return NewNode(left.Insert(index, node, nodeIsUnused), right);
else
return NewNode(left, right.Insert(index - leftCount, node, nodeIsUnused));
}
///
/// Remove a range of items from this node. May change this node, or returns
/// a new node with the given appending done. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive index of first item in sub-range, relative
/// to this node.
/// Inclusize index of last item in sub-range, relative
/// to this node.
/// A node with the sub-range removed. If done in-place, returns
/// "this".
public override Node RemoveRangeInPlace(int first, int last)
{
if (shared)
return RemoveRange(first, last);
Debug.Assert(first < count);
Debug.Assert(last >= 0);
if (first <= 0 && last >= count - 1) {
return null;
}
int leftCount = left.Count;
Node newLeft = left, newRight = right;
// Is part of the left being removed?
if (first < leftCount)
newLeft = left.RemoveRangeInPlace(first, last);
// Is part of the right being remove?
if (last >= leftCount)
newRight = right.RemoveRangeInPlace(first - leftCount, last - leftCount);
return NewNodeInPlace(newLeft, newRight);
}
///
/// Remove a range of items from this node. Never changes this node, but returns
/// a new node with the removing done. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive index of first item in sub-range, relative
/// to this node.
/// Inclusize index of last item in sub-range, relative
/// to this node.
/// A new node with the sub-range removed.
public override Node RemoveRange(int first, int last)
{
Debug.Assert(first < count);
Debug.Assert(last >= 0);
if (first <= 0 && last >= count - 1) {
return null;
}
int leftCount = left.Count;
Node newLeft = left, newRight = right;
// Is part of the left being removed?
if (first < leftCount)
newLeft = left.RemoveRange(first, last);
// Is part of the right being remove?
if (last >= leftCount)
newRight = right.RemoveRange(first - leftCount, last - leftCount);
return NewNode(newLeft, newRight);
}
///
/// Returns a node that has a sub-range of items from this node. The
/// sub-range may not be empty, but may extend outside the node.
/// In other words, first might be less than zero or last might be greater
/// than count. But, last can't be less than zero and first can't be
/// greater than count. Also, last must be greater than or equal to last.
///
/// Inclusive first element, relative to this node.
/// Inclusize last element, relative to this node.
/// Node with the given sub-range.
public override Node Subrange(int first, int last)
{
Debug.Assert(first < count);
Debug.Assert(last >= 0);
if (first <= 0 && last >= count - 1) {
// range encapsulate the whole node, so just return it.
MarkShared();
return this;
}
int leftCount = left.Count;
Node leftPart = null, rightPart = null;
// Is part of the left included?
if (first < leftCount)
leftPart = left.Subrange(first, last);
// Is part of the right included?
if (last >= leftCount)
rightPart = right.Subrange(first - leftCount, last - leftCount);
Debug.Assert(leftPart != null || rightPart != null);
// Combine the left parts and the right parts.
if (leftPart == null)
return rightPart;
else if (rightPart == null)
return leftPart;
else
return new ConcatNode(leftPart, rightPart);
}
#if DEBUG
///
/// Validates the node for consistency, as much as possible. Also validates
/// child nodes, if any.
///
public override void Validate()
{
Debug.Assert(left != null);
Debug.Assert(right != null);
Debug.Assert(Depth > 0);
Debug.Assert(Count > 0);
Debug.Assert(Math.Max(left.Depth, right.Depth) + 1 == Depth);
Debug.Assert(left.Count + right.Count == Count);
left.Validate();
right.Validate();
}
///
/// Print out the contents of this node.
///
/// Prefix to use in front of this node.
/// Prefixed to use in front of children of this node.
public override void Print(string prefixNode, string prefixChildren)
{
Console.WriteLine("{0}CONCAT {1} {2} count={3} depth={4}", prefixNode, shared ? "S" : " ", IsBalanced() ? "B" : (IsAlmostBalanced() ? "A" : " "), count, depth);
left.Print(prefixChildren + "|-L-", prefixChildren + "| ");
right.Print(prefixChildren + "|-R-", prefixChildren + " ");
}
#endif //DEBUG
}
///
/// The class that is used to implement IList<T> to view a sub-range
/// of a BigList. The object stores a wrapped list, and a start/count indicating
/// a sub-range of the list. Insertion/deletions through the sub-range view
/// cause the count to change also; insertions and deletions directly on
/// the wrapped list do not.
///
/// This is different from Algorithms.Range in a very few respects:
/// it is specialized to only wrap BigList, and it is a lot more efficient in enumeration.
[Serializable]
private class BigListRange : ListBase
{
private BigList wrappedList;
private int start;
private int count;
///
/// Create a sub-range view object on the indicate part
/// of the list.
///
/// List to wrap.
/// The start index of the view in the wrapped list.
/// The number of items in the view.
public BigListRange(BigList wrappedList, int start, int count)
{
this.wrappedList = wrappedList;
this.start = start;
this.count = count;
}
public override int Count
{
get
{
return Math.Min(count, wrappedList.Count - start);
}
}
public override void Clear()
{
if (wrappedList.Count - start < count)
count = wrappedList.Count - start;
while (count > 0) {
wrappedList.RemoveAt(start + count - 1);
--count;
}
}
public override void Insert(int index, T item)
{
if (index < 0 || index > count)
throw new ArgumentOutOfRangeException("index");
wrappedList.Insert(start + index, item);
++count;
}
public override void RemoveAt(int index)
{
if (index < 0 || index >= count)
throw new ArgumentOutOfRangeException("index");
wrappedList.RemoveAt(start + index);
--count;
}
public override T this[int index]
{
get
{
if (index < 0 || index >= count)
throw new ArgumentOutOfRangeException("index");
return wrappedList[start + index];
}
set
{
if (index < 0 || index >= count)
throw new ArgumentOutOfRangeException("index");
wrappedList[start + index] = value;
}
}
public override IEnumerator GetEnumerator()
{
return wrappedList.GetEnumerator(start, count);
}
}
}
}