#region CPL License /* Nuclex Framework Copyright (C) 2002-2017 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.Collections; using System.Reflection; 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