Nuclex.Support/Source/Collections/Deque.cs
2024-06-13 18:36:21 +02:00

334 lines
12 KiB
C#

#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 {
/// <summary>A double-ended queue that allocates memory in blocks</summary>
/// <typeparam name="TItem">Type of the items being stored in the queue</typeparam>
/// <remarks>
/// <para>
/// 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.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
public partial class Deque<TItem> : IList<TItem>, IList {
#region class Enumerator
/// <summary>Enumerates over the items in a deque</summary>
private class Enumerator : IEnumerator<TItem>, IEnumerator {
/// <summary>Initializes a new deque enumerator</summary>
/// <param name="deque">Deque whose items will be enumerated</param>
public Enumerator(Deque<TItem> deque) {
this.deque = deque;
this.blockSize = this.deque.blockSize;
this.lastBlock = this.deque.blocks.Count - 1;
this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
Reset();
}
/// <summary>Immediately releases all resources owned by the instance</summary>
public void Dispose() {
this.deque = null;
this.currentBlock = null;
}
/// <summary>The item at the enumerator's current position</summary>
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];
}
}
/// <summary>Advances the enumerator to the next item</summary>
/// <returns>True if there was a next item</returns>
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;
}
}
}
/// <summary>Resets the enumerator to its initial position</summary>
public void Reset() {
this.currentBlock = null;
this.currentBlockIndex = -1;
this.subIndex = this.deque.blockSize - 1;
#if DEBUG
this.expectedVersion = this.deque.version;
#endif
}
/// <summary>The item at the enumerator's current position</summary>
object IEnumerator.Current {
get { return Current; }
}
#if DEBUG
/// <summary>Ensures that the deque has not changed</summary>
private void checkVersion() {
if(this.expectedVersion != this.deque.version)
throw new InvalidOperationException("Deque has been modified");
}
#endif
/// <summary>Deque the enumerator belongs to</summary>
private Deque<TItem> deque;
/// <summary>Size of the blocks in the deque</summary>
private int blockSize;
/// <summary>Index of the last block in the deque</summary>
private int lastBlock;
/// <summary>End index of the items in the deque's last block</summary>
private int lastBlockEndIndex;
/// <summary>Index of the block the enumerator currently is in</summary>
private int currentBlockIndex;
/// <summary>Reference to the block being enumerated</summary>
private TItem[] currentBlock;
/// <summary>Index in the current block</summary>
private int subIndex;
#if DEBUG
/// <summary>Version the deque is expected to have</summary>
private int expectedVersion;
#endif
}
#endregion // class Enumerator
/// <summary>Initializes a new deque</summary>
public Deque() : this(512) { }
/// <summary>Initializes a new deque using the specified block size</summary>
/// <param name="blockSize">Size of the individual memory blocks used</param>
public Deque(int blockSize) {
this.blockSize = blockSize;
this.blocks = new List<TItem[]>();
this.blocks.Add(new TItem[this.blockSize]);
}
/// <summary>Number of items contained in the double ended queue</summary>
public int Count {
get { return this.count; }
}
/// <summary>Accesses an item by its index</summary>
/// <param name="index">Index of the item that will be accessed</param>
/// <returns>The item at the specified index</returns>
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;
}
}
/// <summary>The first item in the double-ended queue</summary>
public TItem First {
get {
if (this.count == 0) {
throw new InvalidOperationException("The deque is empty");
}
return this.blocks[0][this.firstBlockStartIndex];
}
}
/// <summary>The last item in the double-ended queue</summary>
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];
}
}
/// <summary>Determines whether the deque contains the specified item</summary>
/// <param name="item">Item the deque will be scanned for</param>
/// <returns>True if the deque contains the item, false otherwise</returns>
public bool Contains(TItem item) {
return (IndexOf(item) != -1);
}
/// <summary>Copies the contents of the deque into an array</summary>
/// <param name="array">Array the contents of the deque will be copied into</param>
/// <param name="arrayIndex">Array index the deque contents will begin at</param>
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
);
}
}
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
/// <returns>The new enumerator</returns>
public IEnumerator<TItem> GetEnumerator() {
return new Enumerator(this);
}
/// <summary>Calculates the block index and local sub index of an entry</summary>
/// <param name="index">Index of the entry that will be located</param>
/// <param name="blockIndex">Index of the block the entry is contained in</param>
/// <param name="subIndex">Local sub index of the entry within the block</param>
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);
}
/// <summary>
/// Determines whether the provided object can be placed in the deque
/// </summary>
/// <param name="value">Value that will be checked for compatibility</param>
/// <returns>True if the value can be placed in the deque</returns>
private static bool isCompatibleObject(object value) {
return ((value is TItem) || ((value == null) && !typeof(TItem).IsValueType));
}
/// <summary>Verifies that the provided object matches the deque's type</summary>
/// <param name="value">Value that will be checked for compatibility</param>
private static void verifyCompatibleObject(object value) {
if (!isCompatibleObject(value)) {
throw new ArgumentException("Value does not match the deque's type", "value");
}
}
/// <summary>Number if items currently stored in the deque</summary>
private int count;
/// <summary>Size of a single deque block</summary>
private int blockSize;
/// <summary>Memory blocks being used to store the deque's data</summary>
private List<TItem[]> blocks;
/// <summary>Starting index of data in the first block</summary>
private int firstBlockStartIndex;
/// <summary>End index of data in the last block</summary>
private int lastBlockEndIndex;
#if DEBUG
/// <summary>Used to detect when enumerators go out of sync</summary>
private int version;
#endif
}
} // namespace Nuclex.Support.Collections