using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace ZeroLevel.Services.Async { /// /// A double-ended queue (deque), which provides O(1) indexed access, O(1) removals from the front and back, amortized O(1) insertions to the front and back, and O(N) insertions and removals anywhere else (with the operations getting slower as the index approaches the middle). /// /// The type of elements contained in the deque. [DebuggerDisplay("Count = {Count}, Capacity = {Capacity}")] [DebuggerTypeProxy(typeof(Deque<>.DebugView))] internal sealed class Deque : IList, System.Collections.IList { /// /// The default capacity. /// private const int DefaultCapacity = 8; /// /// The circular buffer that holds the view. /// private T[] buffer; /// /// The offset into where the view begins. /// private int offset; /// /// Initializes a new instance of the class with the specified capacity. /// /// The initial capacity. Must be greater than 0. public Deque(int capacity) { if (capacity < 1) throw new ArgumentOutOfRangeException("capacity", "Capacity must be greater than 0."); buffer = new T[capacity]; } /// /// Initializes a new instance of the class with the elements from the specified collection. /// /// The collection. public Deque(IEnumerable collection) { int count = collection.Count(); if (count > 0) { buffer = new T[count]; DoInsertRange(0, collection, count); } else { buffer = new T[DefaultCapacity]; } } /// /// Initializes a new instance of the class. /// public Deque() : this(DefaultCapacity) { } #region GenericListImplementations /// /// Gets a value indicating whether this list is read-only. This implementation always returns false. /// /// true if this list is read-only; otherwise, false. bool ICollection.IsReadOnly { get { return false; } } /// /// Gets or sets the item at the specified index. /// /// The index of the item to get or set. /// is not a valid index in this list. /// This property is set and the list is read-only. public T this[int index] { get { CheckExistingIndexArgument(this.Count, index); return DoGetItem(index); } set { CheckExistingIndexArgument(this.Count, index); DoSetItem(index, value); } } /// /// Inserts an item to this list at the specified index. /// /// The zero-based index at which should be inserted. /// The object to insert into this list. /// /// is not a valid index in this list. /// /// /// This list is read-only. /// public void Insert(int index, T item) { CheckNewIndexArgument(Count, index); DoInsert(index, item); } /// /// Removes the item at the specified index. /// /// The zero-based index of the item to remove. /// /// is not a valid index in this list. /// /// /// This list is read-only. /// public void RemoveAt(int index) { CheckExistingIndexArgument(Count, index); DoRemoveAt(index); } /// /// Determines the index of a specific item in this list. /// /// The object to locate in this list. /// The index of if found in this list; otherwise, -1. public int IndexOf(T item) { var comparer = EqualityComparer.Default; int ret = 0; foreach (var sourceItem in this) { if (comparer.Equals(item, sourceItem)) return ret; ++ret; } return -1; } /// /// Adds an item to the end of this list. /// /// The object to add to this list. /// /// This list is read-only. /// void ICollection.Add(T item) { DoInsert(Count, item); } /// /// Determines whether this list contains a specific value. /// /// The object to locate in this list. /// /// true if is found in this list; otherwise, false. /// bool ICollection.Contains(T item) { return this.Contains(item, null); } /// /// Copies the elements of this list to an , starting at a particular index. /// /// The one-dimensional that is the destination of the elements copied from this slice. The must have zero-based indexing. /// The zero-based index in at which copying begins. /// /// is null. /// /// /// is less than 0. /// /// /// is equal to or greater than the length of . /// -or- /// The number of elements in the source is greater than the available space from to the end of the destination . /// void ICollection.CopyTo(T[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException("array", "Array is null"); int count = this.Count; CheckRangeArguments(array.Length, arrayIndex, count); for (int i = 0; i != count; ++i) { array[arrayIndex + i] = this[i]; } } /// /// Removes the first occurrence of a specific object from this list. /// /// The object to remove from this list. /// /// true if was successfully removed from this list; otherwise, false. This method also returns false if is not found in this list. /// /// /// This list is read-only. /// public bool Remove(T item) { int index = IndexOf(item); if (index == -1) return false; DoRemoveAt(index); return true; } /// /// Returns an enumerator that iterates through the collection. /// /// /// A that can be used to iterate through the collection. /// public IEnumerator GetEnumerator() { int count = this.Count; for (int i = 0; i != count; ++i) { yield return DoGetItem(i); } } /// /// Returns an enumerator that iterates through a collection. /// /// /// An object that can be used to iterate through the collection. /// System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion #region ObjectListImplementations int System.Collections.IList.Add(object value) { AddToBack((T)value); return Count - 1; } bool System.Collections.IList.Contains(object value) { return this.Contains((T)value); } int System.Collections.IList.IndexOf(object value) { return IndexOf((T)value); } void System.Collections.IList.Insert(int index, object value) { Insert(index, (T)value); } bool System.Collections.IList.IsFixedSize { get { return false; } } bool System.Collections.IList.IsReadOnly { get { return false; } } void System.Collections.IList.Remove(object value) { Remove((T)value); } object System.Collections.IList.this[int index] { get { return this[index]; } set { this[index] = (T)value; } } void System.Collections.ICollection.CopyTo(Array array, int index) { if (array == null) throw new ArgumentNullException("array", "Destination array cannot be null."); CheckRangeArguments(array.Length, index, Count); for (int i = 0; i != Count; ++i) { try { array.SetValue(this[i], index + i); } catch (InvalidCastException ex) { throw new ArgumentException("Destination array is of incorrect type.", ex); } } } bool System.Collections.ICollection.IsSynchronized { get { return false; } } object System.Collections.ICollection.SyncRoot { get { return this; } } #endregion #region GenericListHelpers /// /// Checks the argument to see if it refers to a valid insertion point in a source of a given length. /// /// The length of the source. This parameter is not checked for validity. /// The index into the source. /// is not a valid index to an insertion point for the source. private static void CheckNewIndexArgument(int sourceLength, int index) { if (index < 0 || index > sourceLength) { throw new ArgumentOutOfRangeException("index", "Invalid new index " + index + " for source length " + sourceLength); } } /// /// Checks the argument to see if it refers to an existing element in a source of a given length. /// /// The length of the source. This parameter is not checked for validity. /// The index into the source. /// is not a valid index to an existing element for the source. private static void CheckExistingIndexArgument(int sourceLength, int index) { if (index < 0 || index >= sourceLength) { throw new ArgumentOutOfRangeException("index", "Invalid existing index " + index + " for source length " + sourceLength); } } /// /// Checks the and arguments for validity when applied to a source of a given length. Allows 0-element ranges, including a 0-element range at the end of the source. /// /// The length of the source. This parameter is not checked for validity. /// The index into source at which the range begins. /// The number of elements in the range. /// Either or is less than 0. /// The range [offset, offset + count) is not within the range [0, sourceLength). private static void CheckRangeArguments(int sourceLength, int offset, int count) { if (offset < 0) { throw new ArgumentOutOfRangeException("offset", "Invalid offset " + offset); } if (count < 0) { throw new ArgumentOutOfRangeException("count", "Invalid count " + count); } if (sourceLength - offset < count) { throw new ArgumentException("Invalid offset (" + offset + ") or count + (" + count + ") for source length " + sourceLength); } } #endregion /// /// Gets a value indicating whether this instance is empty. /// private bool IsEmpty { get { return Count == 0; } } /// /// Gets a value indicating whether this instance is at full capacity. /// private bool IsFull { get { return Count == Capacity; } } /// /// Gets a value indicating whether the buffer is "split" (meaning the beginning of the view is at a later index in than the end). /// private bool IsSplit { get { // Overflow-safe version of "(offset + Count) > Capacity" return offset > (Capacity - Count); } } /// /// Gets or sets the capacity for this deque. This value must always be greater than zero, and this property cannot be set to a value less than . /// /// Capacity cannot be set to a value less than . public int Capacity { get { return buffer.Length; } set { if (value < 1) throw new ArgumentOutOfRangeException("value", "Capacity must be greater than 0."); if (value < Count) throw new InvalidOperationException("Capacity cannot be set to a value less than Count"); if (value == buffer.Length) return; // Create the new buffer and copy our existing range. T[] newBuffer = new T[value]; if (IsSplit) { // The existing buffer is split, so we have to copy it in parts int length = Capacity - offset; Array.Copy(buffer, offset, newBuffer, 0, length); Array.Copy(buffer, 0, newBuffer, length, Count - length); } else { // The existing buffer is whole Array.Copy(buffer, offset, newBuffer, 0, Count); } // Set up to use the new buffer. buffer = newBuffer; offset = 0; } } /// /// Gets the number of elements contained in this deque. /// /// The number of elements contained in this deque. public int Count { get; private set; } /// /// Applies the offset to , resulting in a buffer index. /// /// The deque index. /// The buffer index. private int DequeIndexToBufferIndex(int index) { return (index + offset) % Capacity; } /// /// Gets an element at the specified view index. /// /// The zero-based view index of the element to get. This index is guaranteed to be valid. /// The element at the specified index. private T DoGetItem(int index) { return buffer[DequeIndexToBufferIndex(index)]; } /// /// Sets an element at the specified view index. /// /// The zero-based view index of the element to get. This index is guaranteed to be valid. /// The element to store in the list. private void DoSetItem(int index, T item) { buffer[DequeIndexToBufferIndex(index)] = item; } /// /// Inserts an element at the specified view index. /// /// The zero-based view index at which the element should be inserted. This index is guaranteed to be valid. /// The element to store in the list. private void DoInsert(int index, T item) { EnsureCapacityForOneElement(); if (index == 0) { DoAddToFront(item); return; } else if (index == Count) { DoAddToBack(item); return; } DoInsertRange(index, new[] { item }, 1); } /// /// Removes an element at the specified view index. /// /// The zero-based view index of the element to remove. This index is guaranteed to be valid. private void DoRemoveAt(int index) { if (index == 0) { DoRemoveFromFront(); return; } else if (index == Count - 1) { DoRemoveFromBack(); return; } DoRemoveRange(index, 1); } /// /// Increments by using modulo- arithmetic. /// /// The value by which to increase . May not be negative. /// The value of after it was incremented. private int PostIncrement(int value) { int ret = offset; offset += value; offset %= Capacity; return ret; } /// /// Decrements by using modulo- arithmetic. /// /// The value by which to reduce . May not be negative or greater than . /// The value of before it was decremented. private int PreDecrement(int value) { offset -= value; if (offset < 0) offset += Capacity; return offset; } /// /// Inserts a single element to the back of the view. must be false when this method is called. /// /// The element to insert. private void DoAddToBack(T value) { buffer[DequeIndexToBufferIndex(Count)] = value; ++Count; } /// /// Inserts a single element to the front of the view. must be false when this method is called. /// /// The element to insert. private void DoAddToFront(T value) { buffer[PreDecrement(1)] = value; ++Count; } /// /// Removes and returns the last element in the view. must be false when this method is called. /// /// The former last element. private T DoRemoveFromBack() { T ret = buffer[DequeIndexToBufferIndex(Count - 1)]; --Count; return ret; } /// /// Removes and returns the first element in the view. must be false when this method is called. /// /// The former first element. private T DoRemoveFromFront() { --Count; return buffer[PostIncrement(1)]; } /// /// Inserts a range of elements into the view. /// /// The index into the view at which the elements are to be inserted. /// The elements to insert. /// The number of elements in . Must be greater than zero, and the sum of and must be less than or equal to . private void DoInsertRange(int index, IEnumerable collection, int collectionCount) { // Make room in the existing list if (index < Count / 2) { // Inserting into the first half of the list // Move lower items down: [0, index) -> [Capacity - collectionCount, Capacity - collectionCount + index) // This clears out the low "index" number of items, moving them "collectionCount" places down; // after rotation, there will be a "collectionCount"-sized hole at "index". int copyCount = index; int writeIndex = Capacity - collectionCount; for (int j = 0; j != copyCount; ++j) buffer[DequeIndexToBufferIndex(writeIndex + j)] = buffer[DequeIndexToBufferIndex(j)]; // Rotate to the new view this.PreDecrement(collectionCount); } else { // Inserting into the second half of the list // Move higher items up: [index, count) -> [index + collectionCount, collectionCount + count) int copyCount = Count - index; int writeIndex = index + collectionCount; for (int j = copyCount - 1; j != -1; --j) buffer[DequeIndexToBufferIndex(writeIndex + j)] = buffer[DequeIndexToBufferIndex(index + j)]; } // Copy new items into place int i = index; foreach (T item in collection) { buffer[DequeIndexToBufferIndex(i)] = item; ++i; } // Adjust valid count Count += collectionCount; } /// /// Removes a range of elements from the view. /// /// The index into the view at which the range begins. /// The number of elements in the range. This must be greater than 0 and less than or equal to . private void DoRemoveRange(int index, int collectionCount) { if (index == 0) { // Removing from the beginning: rotate to the new view this.PostIncrement(collectionCount); Count -= collectionCount; return; } else if (index == Count - collectionCount) { // Removing from the ending: trim the existing view Count -= collectionCount; return; } if ((index + (collectionCount / 2)) < Count / 2) { // Removing from first half of list // Move lower items up: [0, index) -> [collectionCount, collectionCount + index) int copyCount = index; int writeIndex = collectionCount; for (int j = copyCount - 1; j != -1; --j) buffer[DequeIndexToBufferIndex(writeIndex + j)] = buffer[DequeIndexToBufferIndex(j)]; // Rotate to new view this.PostIncrement(collectionCount); } else { // Removing from second half of list // Move higher items down: [index + collectionCount, count) -> [index, count - collectionCount) int copyCount = Count - collectionCount - index; int readIndex = index + collectionCount; for (int j = 0; j != copyCount; ++j) buffer[DequeIndexToBufferIndex(index + j)] = buffer[DequeIndexToBufferIndex(readIndex + j)]; } // Adjust valid count Count -= collectionCount; } /// /// Doubles the capacity if necessary to make room for one more element. When this method returns, is false. /// private void EnsureCapacityForOneElement() { if (this.IsFull) { this.Capacity = this.Capacity * 2; } } /// /// Inserts a single element at the back of this deque. /// /// The element to insert. public void AddToBack(T value) { EnsureCapacityForOneElement(); DoAddToBack(value); } /// /// Inserts a single element at the front of this deque. /// /// The element to insert. public void AddToFront(T value) { EnsureCapacityForOneElement(); DoAddToFront(value); } /// /// Inserts a collection of elements into this deque. /// /// The index at which the collection is inserted. /// The collection of elements to insert. /// is not a valid index to an insertion point for the source. public void InsertRange(int index, IEnumerable collection) { int collectionCount = collection.Count(); CheckNewIndexArgument(Count, index); // Overflow-safe check for "this.Count + collectionCount > this.Capacity" if (collectionCount > Capacity - Count) { this.Capacity = checked(Count + collectionCount); } if (collectionCount == 0) { return; } this.DoInsertRange(index, collection, collectionCount); } /// /// Removes a range of elements from this deque. /// /// The index into the deque at which the range begins. /// The number of elements to remove. /// Either or is less than 0. /// The range [, + ) is not within the range [0, ). public void RemoveRange(int offset, int count) { CheckRangeArguments(Count, offset, count); if (count == 0) { return; } this.DoRemoveRange(offset, count); } /// /// Removes and returns the last element of this deque. /// /// The former last element. /// The deque is empty. public T RemoveFromBack() { if (this.IsEmpty) throw new InvalidOperationException("The deque is empty."); return this.DoRemoveFromBack(); } /// /// Removes and returns the first element of this deque. /// /// The former first element. /// The deque is empty. public T RemoveFromFront() { if (this.IsEmpty) throw new InvalidOperationException("The deque is empty."); return this.DoRemoveFromFront(); } /// /// Removes all items from this deque. /// public void Clear() { this.offset = 0; this.Count = 0; } [DebuggerNonUserCode] private sealed class DebugView { private readonly Deque deque; public DebugView(Deque deque) { this.deque = deque; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public T[] Items { get { var array = new T[deque.Count]; ((ICollection)deque).CopyTo(array, 0); return array; } } } } }