#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