//****************************** // 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; // CONSIDER: always create a small initial buffer, so that the checks in Add for a null buffer aren't needed. This // would improve performance slightly, but make empty Deque's take more memory. namespace Wintellect.PowerCollections { /// /// The Deque class implements a type of list known as a Double Ended Queue. A Deque /// is quite similar to a List, in that items have indices (starting at 0), and the item at any /// index can be efficiently retrieved. The difference between a List and a Deque lies in the /// efficiency of inserting elements at the beginning. In a List, items can be efficiently added /// to the end, but inserting an item at the beginning of the List is slow, taking time /// proportional to the size of the List. In a Deque, items can be added to the beginning /// or end equally efficiently, regardless of the number of items in the Deque. As a trade-off /// for this increased flexibility, Deque is somewhat slower than List (but still constant time) when /// being indexed to get or retrieve elements. /// /// /// The Deque class can also be used as a more flexible alternative to the Queue /// and Stack classes. Deque is as efficient as Queue and Stack for adding or removing items, /// but is more flexible: it allows access /// to all items in the queue, and allows adding or removing from either end. /// Deque is implemented as a ring buffer, which is grown as necessary. The size /// of the buffer is doubled whenever the existing capacity is too small to hold all the /// elements. /// /// The type of items stored in the Deque. [Serializable] public class Deque : ListBase, ICloneable { // The initial size of the buffer. private const int INITIAL_SIZE = 8; // A ring buffer containing all the items in the deque. Shrinks or grows as needed. // Except temporarily during an add, there is always at least one empty item. private T[] buffer; // Index of the first item (index 0) in the deque. // Always in the range 0 through buffer.Length - 1. private int start; // Index just beyond the last item in the deque. If equal to start, the deque is empty. // Always in the range 0 through buffer.Length - 1. private int end; // 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); } } /// /// Create a new Deque that is initially empty. /// public Deque() { } /// /// Create a new Deque initialized with the items from the passed collection, /// in order. /// /// A collection of items to initialize the Deque with. public Deque(IEnumerable collection) { AddManyToBack(collection); } /// /// Gets the number of items currently stored in the Deque. The last item /// in the Deque has index Count-1. /// /// Getting the count of items in the Deque takes a small constant /// amount of time. /// The number of items stored in this Deque. public sealed override int Count { get { if (end >= start) return end - start; else return end + buffer.Length - start; } } /// /// Copies all the items in the Deque into an array. /// /// Array to copy to. /// Starting index in to copy to. public sealed override void CopyTo(T[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array"); // This override is provided to give a more efficient implementation to CopyTo than // the default one provided by CollectionBase. int length = (buffer == null) ? 0 : buffer.Length; if (start > end) { Array.Copy(buffer, start, array, arrayIndex, length - start); Array.Copy(buffer, 0, array, arrayIndex + length - start, end); } else { if (end > start) Array.Copy(buffer, start, array, arrayIndex, end - start); } } /// /// Gets or sets the capacity of the Deque. The Capacity is the number of /// items that this Deque can hold without expanding its internal buffer. Since /// Deque will automatically expand its buffer when necessary, in almost all cases /// it is unnecessary to worry about the capacity. However, if it is known that a /// Deque will contain exactly 1000 items eventually, it can slightly improve /// efficiency to set the capacity to 1000 up front, so that the Deque does not /// have to expand automatically. /// /// The number of items that this Deque can hold without expanding its /// internal buffer. /// The capacity is being set /// to less than Count, or to too large a value. public int Capacity { get { if (buffer == null) return 0; else return buffer.Length - 1; } set { if (value < Count) throw new ArgumentOutOfRangeException("value", Strings.CapacityLessThanCount); if (value > int.MaxValue - 1) throw new ArgumentOutOfRangeException("value"); if (value == Capacity) return; T[] newBuffer = new T[value + 1]; CopyTo(newBuffer, 0); end = Count; start = 0; buffer = newBuffer; } } /// /// Trims the amount of memory used by the Deque by changing /// the Capacity to be equal to Count. If no more items will be added /// to the Deque, calling TrimToSize will reduce the amount of memory /// used by the Deque. /// public void TrimToSize() { Capacity = Count; } /// /// Removes all items from the Deque. /// /// Clearing the Deque takes a small constant amount of time, regardless of /// how many items are currently in the Deque. public sealed override void Clear() { StopEnumerations(); buffer = null; start = end = 0; } /// /// Gets or sets an item at a particular index in the Deque. /// /// Getting or setting the item at a particular index takes a small constant amount /// of time, no matter what index is used. /// The index of the item to retrieve or change. The front item has index 0, and /// the back item has index Count-1. /// The value at the indicated index. /// The index is less than zero or greater than or equal /// to Count. public sealed override T this[int index] { get { int i = index + start; if (i < start) // handles both the case where index < 0, or the above addition overflow to a negative number. throw new ArgumentOutOfRangeException("index"); if (end >= start) { if (i >= end) throw new ArgumentOutOfRangeException("index"); return buffer[i]; } else { int length = buffer.Length; if (i >= length) { i -= length; if (i >= end) throw new ArgumentOutOfRangeException("index"); } return buffer[i]; } } set { // Like List, we stop enumerations after a set operation. There is no // technical reason to do this, however. StopEnumerations(); int i = index + start; if (i < start) // handles both the case where index < 0, or the above addition overflow to a negative number. throw new ArgumentOutOfRangeException("index"); if (end >= start) { if (i >= end) throw new ArgumentOutOfRangeException("index"); buffer[i] = value; } else { int length = buffer.Length; if (i >= length) { i -= length; if (i >= end) throw new ArgumentOutOfRangeException("index"); } buffer[i] = value; } } } /// /// 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. If the items /// are added to or removed from the Deque during enumeration, the /// enumeration ends with an InvalidOperationException. /// /// An IEnumerator<T> that enumerates all the /// items in the list. /// The Deque has an item added or deleted during the enumeration. public sealed override IEnumerator GetEnumerator() { int startStamp = changeStamp; int count = Count; for (int i = 0; i < count; ++i) { yield return this[i]; CheckEnumerationStamp(startStamp); } } /// /// Creates the initial buffer and initialized the Deque to contain one initial /// item. /// /// First and only item for the Deque. private void CreateInitialBuffer(T firstItem) { // The buffer hasn't been created yet. buffer = new T[INITIAL_SIZE]; start = 0; end = 1; buffer[0] = firstItem; return; } /// /// Inserts a new item at the given index in the Deque. All items at indexes /// equal to or greater than move up one index /// in the Deque. /// /// The amount of time to insert an item in the Deque is proportional /// to the distance of index from the closest end of the Deque: /// O(Min(, Count - )). /// Thus, inserting an item at the front or end of the Deque is always fast; the middle of /// of the Deque is the slowest place to insert. /// /// The index in the Deque to insert the item at. After the /// insertion, the inserted item is located at this index. The /// front item in the Deque 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(); int count = Count; if (index < 0 || index > Count) throw new ArgumentOutOfRangeException("index"); if (buffer == null) { // The buffer hasn't been created yet. CreateInitialBuffer(item); return; } int length = buffer.Length; int i; // The location the new item was placed at. if (index < count / 2) { // Inserting into the first half of the list. Move items with // lower index down in the buffer. start -= 1; if (start < 0) start += length; i = index + start; if (i >= length) { i -= length; if (length - 1 > start) Array.Copy(buffer, start + 1, buffer, start, length - 1 - start); buffer[length - 1] = buffer[0]; // unneeded if end == 0, but doesn't hurt if (i > 0) Array.Copy(buffer, 1, buffer, 0, i); } else { if (i > start) Array.Copy(buffer, start + 1, buffer, start, i - start); } } else { // Inserting into the last half of the list. Move items with higher // index up in the buffer. i = index + start; if (i >= length) i -= length; if (i <= end) { if (end > i) Array.Copy(buffer, i, buffer, i + 1, end - i); end += 1; if (end >= length) end -= length; } else { if (end > 0) Array.Copy(buffer, 0, buffer, 1, end); buffer[0] = buffer[length - 1]; if (length - 1 > i) Array.Copy(buffer, i, buffer, i + 1, length - 1 - i); end += 1; } } buffer[i] = item; if (start == end) IncreaseBuffer(); } /// /// Inserts a collection of items at the given index in the Deque. All items at indexes /// equal to or greater than increase their indices in the Deque /// by the number of items inserted. /// /// The amount of time to insert a collection in the Deque is proportional /// to the distance of index from the closest end of the Deque, plus the number of items /// inserted (M): /// O(M + Min(, Count - )). /// /// The index in the Deque to insert the collection at. After the /// insertion, the first item of the inserted collection is located at this index. The /// front item in the Deque has index 0. /// The collection of items to insert at the given index. /// is /// less than zero or greater than Count. public void InsertRange(int index, IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); StopEnumerations(); int count = Count; if (index < 0 || index > Count) throw new ArgumentOutOfRangeException("index"); // We need an ICollection, because we need the count of the collection. // If needed, copy the items to a temporary list. ICollection coll; if (collection is ICollection) coll = (ICollection) collection; else { coll = new List(collection); } if (coll.Count == 0) return; // nothing to do. // Create enough capacity in the list for the new items. if (Capacity < Count + coll.Count) Capacity = Count + coll.Count; int length = buffer.Length; int s, d; if (index < count / 2) { // Inserting into the first half of the list. Move items with // lower index down in the buffer. s = start; d = s - coll.Count; if (d < 0) d += length; start = d; int c = index; while (c > 0) { int chunk = c; if (length - d < chunk) chunk = length - d; if (length - s < chunk) chunk = length - s; Array.Copy(buffer, s, buffer, d, chunk); c -= chunk; if ((d += chunk) >= length) d -= length; if ((s += chunk) >= length) s -= length; } } else { // Inserting into the last half of the list. Move items with higher // index up in the buffer. s = end; d = s + coll.Count; if (d >= length) d -= length; end = d; int move = count - index; // number of items at end to move int c = move; while (c > 0) { int chunk = c; if (d > 0 && d < chunk) chunk = d; if (s > 0 && s < chunk) chunk = s; if ((d -= chunk) < 0) d += length; if ((s -= chunk) < 0) s += length; Array.Copy(buffer, s, buffer, d, chunk); c -= chunk; } d -= coll.Count; if (d < 0) d += length; } // Copy the items into the space vacated, which starts at d. foreach (T item in coll) { buffer[d] = item; if (++d >= length) d -= length; } } /// /// Removes the item at the given index in the Deque. All items at indexes /// greater than move down one index /// in the Deque. /// /// The amount of time to delete an item in the Deque is proportional /// to the distance of index from the closest end of the Deque: /// O(Min(, Count - 1 - )). /// Thus, deleting an item at the front or end of the Deque is always fast; the middle of /// of the Deque is the slowest place to delete. /// /// 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) { StopEnumerations(); int count = Count; if (index < 0 || index >= count) throw new ArgumentOutOfRangeException("index"); int length = buffer.Length; int i; // index of removed item if (index < count / 2) { // Removing in the first half of the list. Move items with // lower index up in the buffer. i = index + start; if (i >= length) { i -= length; if (i > 0) Array.Copy(buffer, 0, buffer, 1, i); buffer[0] = buffer[length - 1]; if (length - 1 > start) Array.Copy(buffer, start, buffer, start + 1, length - 1 - start); } else { if (i > start) Array.Copy(buffer, start, buffer, start + 1, i - start); } buffer[start] = default(T); start += 1; if (start >= length) start -= length; } else { // Removing in the second half of the list. Move items with // higher indexes down in the buffer. i = index + start; if (i >= length) i -= length; end -= 1; if (end < 0) end = length - 1; if (i <= end) { if (end > i) Array.Copy(buffer, i + 1, buffer, i, end - i); } else { if (length - 1 > i) Array.Copy(buffer, i + 1, buffer, i, length - 1 - i); buffer[length - 1] = buffer[0]; if (end > 0) Array.Copy(buffer, 1, buffer, 0, end); } buffer[end] = default(T); } } /// /// 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 to the closest end of the Deque: /// O(Min(, Count - - )). /// /// 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) { StopEnumerations(); int dequeCount = Count; if (index < 0 || index >= dequeCount) throw new ArgumentOutOfRangeException("index"); if (count < 0 || count > dequeCount - index) throw new ArgumentOutOfRangeException("count"); if (count == 0) return; int length = buffer.Length; int s, d; if (index < dequeCount / 2) { // Removing in the first half of the list. Move items with // lower index up in the buffer. s = start + index; if (s >= length) s -= length; d = s + count; if (d >= length) d -= length; int c = index; while (c > 0) { int chunk = c; if (d > 0 && d < chunk) chunk = d; if (s > 0 && s < chunk) chunk = s; if ((d -= chunk) < 0) d += length; if ((s -= chunk) < 0) s += length; Array.Copy(buffer, s, buffer, d, chunk); c -= chunk; } // At this point, s == start for (c = 0; c < count; ++c) { buffer[s] = default(T); if (++s >= length) s -= length; } start = s; } else { // Removing in the second half of the list. Move items with // higher indexes down in the buffer. int move = dequeCount - index - count; s = end - move; if (s < 0) s += length; d = s - count; if (d < 0) d += length; int c = move; while (c > 0) { int chunk = c; if (length - d < chunk) chunk = length - d; if (length - s < chunk) chunk = length - s; Array.Copy(buffer, s, buffer, d, chunk); c -= chunk; if ((d += chunk) >= length) d -= length; if ((s += chunk) >= length) s -= length; } // At this point, s == end. for (c = 0; c < count; ++c) { if (--s < 0) s += length; buffer[s] = default(T); } end = s; } } /// /// Increase the amount of buffer space. When calling this method, the Deque /// must not be empty. If start and end are equal, that indicates a completely /// full Deque. /// private void IncreaseBuffer() { int count = Count; int length = buffer.Length; T[] newBuffer = new T[length * 2]; if (start >= end) { Array.Copy(buffer, start, newBuffer, 0, length - start); Array.Copy(buffer, 0, newBuffer, length - start, end); end = end + length - start; start = 0; } else { Array.Copy(buffer, start, newBuffer, 0, end - start); end = end - start; start = 0; } buffer = newBuffer; } /// /// Adds an item to the front of the Deque. The indices of all existing items /// in the Deque are increased by 1. This method is /// equivalent to Insert(0, item) but is a little more /// efficient. /// /// Adding an item to the front of the Deque takes /// a small constant amount of time, regardless of how many items are in the Deque. /// The item to add. public void AddToFront(T item) { StopEnumerations(); if (buffer == null) { // The buffer hasn't been created yet. CreateInitialBuffer(item); return; } if (--start < 0) start += buffer.Length; buffer[start] = item; if (start == end) IncreaseBuffer(); } /// /// Adds a collection of items to the front of the Deque. The indices of all existing items /// in the Deque are increased by the number of items inserted. The first item in the added collection becomes the /// first item in the Deque. /// /// This method takes time O(M), where M is the number of items in the /// . /// The collection of items to add. public void AddManyToFront(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); InsertRange(0, collection); } /// /// Adds an item to the back of the Deque. The indices of all existing items /// in the Deque are unchanged. This method is /// equivalent to Insert(Count, item) but is a little more /// efficient. /// /// Adding an item to the back of the Deque takes /// a small constant amount of time, regardless of how many items are in the Deque. /// The item to add. public void AddToBack(T item) { StopEnumerations(); if (buffer == null) { // The buffer hasn't been created yet. CreateInitialBuffer(item); return; } buffer[end] = item; if (++end >= buffer.Length) end -= buffer.Length; if (start == end) IncreaseBuffer(); } /// /// Adds an item to the back of the Deque. The indices of all existing items /// in the Deque are unchanged. This method is /// equivalent to AddToBack(item). /// /// Adding an item to the back of the Deque takes /// a small constant amount of time, regardless of how many items are in the Deque. /// The item to add. public sealed override void Add(T item) { AddToBack(item); } /// /// Adds a collection of items to the back of the Deque. The indices of all existing items /// in the Deque are unchanged. The last item in the added collection becomes the /// last item in the Deque. /// /// This method takes time O(M), where M is the number of items in the /// . /// The collection of item to add. public void AddManyToBack(IEnumerable collection) { if (collection == null) throw new ArgumentNullException("collection"); foreach (T item in collection) AddToBack(item); } /// /// Removes an item from the front of the Deque. The indices of all existing items /// in the Deque are decreased by 1. This method is /// equivalent to RemoveAt(0) but is a little more /// efficient. /// /// Removing an item from the front of the Deque takes /// a small constant amount of time, regardless of how many items are in the Deque. /// The item that was removed. /// The Deque is empty. public T RemoveFromFront() { if (start == end) throw new InvalidOperationException(Strings.CollectionIsEmpty); StopEnumerations(); T item = buffer[start]; buffer[start] = default(T); if (++start >= buffer.Length) start -= buffer.Length; return item; } /// /// Removes an item from the back of the Deque. The indices of all existing items /// in the Deque are unchanged. This method is /// equivalent to RemoveAt(Count-1) but is a little more /// efficient. /// /// Removing an item from the back of the Deque takes /// a small constant amount of time, regardless of how many items are in the Deque. /// The Deque is empty. public T RemoveFromBack() { if (start == end) throw new InvalidOperationException(Strings.CollectionIsEmpty); StopEnumerations(); if (--end < 0) end += buffer.Length; T item = buffer[end]; buffer[end] = default(T); return item; } /// /// Retreives the item currently at the front of the Deque. The Deque is /// unchanged. This method is /// equivalent to deque[0] (except that a different exception is thrown). /// /// Retreiving the item at the front of the Deque takes /// a small constant amount of time, regardless of how many items are in the Deque. /// The item at the front of the Deque. /// The Deque is empty. public T GetAtFront() { if (start == end) throw new InvalidOperationException(Strings.CollectionIsEmpty); return buffer[start]; } /// /// Retreives the item currently at the back of the Deque. The Deque is /// unchanged. This method is /// equivalent to deque[deque.Count - 1] (except that a different exception is thrown). /// /// Retreiving the item at the back of the Deque takes /// a small constant amount of time, regardless of how many items are in the Deque. /// The item at the back of the Deque. /// The Deque is empty. public T GetAtBack() { if (start == end) throw new InvalidOperationException(Strings.CollectionIsEmpty); if (end == 0) return buffer[buffer.Length - 1]; else return buffer[end - 1]; } /// /// Creates a new Deque that is a copy of this one. /// /// Copying a Deque takes O(N) time, where N is the number of items in this Deque.. /// A copy of the current deque. public Deque Clone() { return new Deque(this); } /// /// Creates a new Deque that is a copy of this one. /// /// Copying a Deque takes O(N) time, where N is the number of items in this Deque.. /// A copy of the current deque. object ICloneable.Clone() { return this.Clone(); } /// /// Makes a deep clone of this Deque. A new Deque 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 each element is copied as if by simple assignment. /// /// If T is a reference type, it must implement /// ICloneable. Otherwise, an InvalidOperationException is thrown. /// Cloning the Deque takes time O(N), where N is the number of items in the Deque. /// The cloned Deque. /// T is a reference type that does not implement ICloneable. public Deque CloneContents() { bool itemIsValueType; if (!Util.IsCloneableType(typeof(T), out itemIsValueType)) throw new InvalidOperationException(string.Format(Strings.TypeNotCloneable, typeof(T).FullName)); Deque clone = new Deque(); // Clone each item, and add it to the new ordered set. foreach (T item in this) { T itemClone; if (itemIsValueType) itemClone = item; else { if (item == null) itemClone = default(T); // Really null, because we know T is a reference type else itemClone = (T)(((ICloneable)item).Clone()); } clone.AddToBack(itemClone); } return clone; } #if DEBUG /// /// Print out the internal state of the Deque for debugging. /// internal void Print() { Console.WriteLine("length={0} start={1} end={2}", buffer.Length, start, end); for (int i = 0; i < buffer.Length; ++i) { if (i == start) Console.Write("start-> "); else Console.Write(" "); if (i == end) Console.Write("end-> "); else Console.Write(" "); Console.WriteLine("{0,4} {1}", i, buffer[i]); } Console.WriteLine(); } #endif // DEBUG } }