#region Apache License 2.0 /* Nuclex .NET Framework Copyright (C) 2002-2024 Markus Ewald / Nuclex Development Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #endregion // Apache License 2.0 using System; using System.Collections.Generic; using System.Collections; namespace Nuclex.Support.Collections { /// A double-ended queue that allocates memory in blocks /// Type of the items being stored in the queue /// /// /// The double-ended queue allows items to be appended to either side of the queue /// without a hefty toll on performance. Like its namesake in C++, it is implemented /// using multiple arrays. /// /// /// Therefore, it's not only good at coping with lists that are modified at their /// beginning, but also at handling huge data sets since enlarging the deque doesn't /// require items to be copied around and it still can be accessed by index. /// /// public partial class Deque : IList, IList { #region class Enumerator /// Enumerates over the items in a deque private class Enumerator : IEnumerator, IEnumerator { /// Initializes a new deque enumerator /// Deque whose items will be enumerated public Enumerator(Deque deque) { this.deque = deque; this.blockSize = this.deque.blockSize; this.lastBlock = this.deque.blocks.Count - 1; this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1; Reset(); } /// Immediately releases all resources owned by the instance public void Dispose() { this.deque = null; this.currentBlock = null; } /// The item at the enumerator's current position public TItem Current { get { #if DEBUG checkVersion(); #endif if (this.currentBlock == null) { throw new InvalidOperationException("Enumerator is not on a valid position"); } return this.currentBlock[this.subIndex]; } } /// Advances the enumerator to the next item /// True if there was a next item public bool MoveNext() { #if DEBUG checkVersion(); #endif // If we haven't reached the last block yet if (this.currentBlockIndex < this.lastBlock) { // Advance to the next item. If the end of the current block is reached, // go to the next block's first item ++this.subIndex; if (this.subIndex >= this.blockSize) { ++this.currentBlockIndex; this.currentBlock = this.deque.blocks[this.currentBlockIndex]; if (this.currentBlockIndex == 0) { this.subIndex = this.deque.firstBlockStartIndex; } else { this.subIndex = 0; } } // Item found. If the current block wasn't the last block, an item *has* // to follow since otherwise, no further blocks would exist! return true; } else { // We in or beyond the last block // Are there any items left to advance to? if (this.subIndex < this.lastBlockEndIndex) { ++this.subIndex; return true; } else { // Nope, we've reached the end of the deque this.currentBlock = null; return false; } } } /// Resets the enumerator to its initial position public void Reset() { this.currentBlock = null; this.currentBlockIndex = -1; this.subIndex = this.deque.blockSize - 1; #if DEBUG this.expectedVersion = this.deque.version; #endif } /// The item at the enumerator's current position object IEnumerator.Current { get { return Current; } } #if DEBUG /// Ensures that the deque has not changed private void checkVersion() { if(this.expectedVersion != this.deque.version) throw new InvalidOperationException("Deque has been modified"); } #endif /// Deque the enumerator belongs to private Deque deque; /// Size of the blocks in the deque private int blockSize; /// Index of the last block in the deque private int lastBlock; /// End index of the items in the deque's last block private int lastBlockEndIndex; /// Index of the block the enumerator currently is in private int currentBlockIndex; /// Reference to the block being enumerated private TItem[] currentBlock; /// Index in the current block private int subIndex; #if DEBUG /// Version the deque is expected to have private int expectedVersion; #endif } #endregion // class Enumerator /// Initializes a new deque public Deque() : this(512) { } /// Initializes a new deque using the specified block size /// Size of the individual memory blocks used public Deque(int blockSize) { this.blockSize = blockSize; this.blocks = new List(); this.blocks.Add(new TItem[this.blockSize]); } /// Number of items contained in the double ended queue public int Count { get { return this.count; } } /// Accesses an item by its index /// Index of the item that will be accessed /// The item at the specified index public TItem this[int index] { get { int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex); return this.blocks[blockIndex][subIndex]; } set { int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex); this.blocks[blockIndex][subIndex] = value; } } /// The first item in the double-ended queue public TItem First { get { if (this.count == 0) { throw new InvalidOperationException("The deque is empty"); } return this.blocks[0][this.firstBlockStartIndex]; } } /// The last item in the double-ended queue public TItem Last { get { if (this.count == 0) { throw new InvalidOperationException("The deque is empty"); } return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1]; } } /// Determines whether the deque contains the specified item /// Item the deque will be scanned for /// True if the deque contains the item, false otherwise public bool Contains(TItem item) { return (IndexOf(item) != -1); } /// Copies the contents of the deque into an array /// Array the contents of the deque will be copied into /// Array index the deque contents will begin at public void CopyTo(TItem[] array, int arrayIndex) { if (this.count > (array.Length - arrayIndex)) { throw new ArgumentException( "Array too small to hold the collection items starting at the specified index" ); } if (this.blocks.Count == 1) { // Does only one block exist? // Copy the one and only block there is Array.Copy( this.blocks[0], this.firstBlockStartIndex, array, arrayIndex, this.lastBlockEndIndex - this.firstBlockStartIndex ); } else { // Multiple blocks exist // Copy the first block which is filled from the start index to its end int length = this.blockSize - this.firstBlockStartIndex; Array.Copy( this.blocks[0], this.firstBlockStartIndex, array, arrayIndex, length ); arrayIndex += length; // Copy all intermediate blocks (if there are any). These are completely filled int lastBlock = this.blocks.Count - 1; for (int index = 1; index < lastBlock; ++index) { Array.Copy( this.blocks[index], 0, array, arrayIndex, this.blockSize ); arrayIndex += this.blockSize; } // Copy the final block which is filled from the beginning to the end index Array.Copy( this.blocks[lastBlock], 0, array, arrayIndex, this.lastBlockEndIndex ); } } /// Obtains a new enumerator for the contents of the deque /// The new enumerator public IEnumerator GetEnumerator() { return new Enumerator(this); } /// Calculates the block index and local sub index of an entry /// Index of the entry that will be located /// Index of the block the entry is contained in /// Local sub index of the entry within the block private void findIndex(int index, out int blockIndex, out int subIndex) { if ((index < 0) || (index >= this.count)) { throw new ArgumentOutOfRangeException("Index out of range", "index"); } index += this.firstBlockStartIndex; blockIndex = Math.DivRem(index, this.blockSize, out subIndex); } /// /// Determines whether the provided object can be placed in the deque /// /// Value that will be checked for compatibility /// True if the value can be placed in the deque private static bool isCompatibleObject(object value) { return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType)); } /// Verifies that the provided object matches the deque's type /// Value that will be checked for compatibility private static void verifyCompatibleObject(object value) { if (!isCompatibleObject(value)) { throw new ArgumentException("Value does not match the deque's type", "value"); } } /// Number if items currently stored in the deque private int count; /// Size of a single deque block private int blockSize; /// Memory blocks being used to store the deque's data private List blocks; /// Starting index of data in the first block private int firstBlockStartIndex; /// End index of data in the last block private int lastBlockEndIndex; #if DEBUG /// Used to detect when enumerators go out of sync private int version; #endif } } // namespace Nuclex.Support.Collections