#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