diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj
index 099dbf5..228c046 100644
--- a/Nuclex.Support (Xbox 360).csproj
+++ b/Nuclex.Support (Xbox 360).csproj
@@ -73,9 +73,15 @@
Deque.cs
+
+ Deque.cs
+
Deque.cs
+
+ Deque.cs
+
Deque.cs
diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj
index 8088796..059af82 100644
--- a/Nuclex.Support.csproj
+++ b/Nuclex.Support.csproj
@@ -55,9 +55,15 @@
Deque.cs
+
+ Deque.cs
+
Deque.cs
+
+ Deque.cs
+
Deque.cs
diff --git a/Source/Collections/Deque.Insertion.cs b/Source/Collections/Deque.Insertion.cs
index 7d9b99e..d96f123 100644
--- a/Source/Collections/Deque.Insertion.cs
+++ b/Source/Collections/Deque.Insertion.cs
@@ -23,14 +23,14 @@ namespace Nuclex.Support.Collections {
/// Appends an item to the end of the double-ended queue
/// Item that will be appended to the queue
public void AddLast(ItemType item) {
- if(this.lastBlockEndIndex < this.blockSize) {
- ++this.lastBlockEndIndex;
+ if(this.lastBlockCount < this.blockSize) {
+ ++this.lastBlockCount;
} else { // Need to allocate a new block
this.blocks.Add(new ItemType[this.blockSize]);
- this.lastBlockEndIndex = 1;
+ this.lastBlockCount = 1;
}
- this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item;
+ this.blocks[this.blocks.Count - 1][this.lastBlockCount - 1] = item;
++this.count;
}
@@ -137,15 +137,15 @@ namespace Nuclex.Support.Collections {
int blockLength;
// If the lastmost block is full, we need to add another block
- if(this.lastBlockEndIndex == this.blockSize) {
+ if(this.lastBlockCount == this.blockSize) {
this.blocks.Add(new ItemType[this.blockSize]);
this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1];
- this.lastBlockEndIndex = 1;
+ this.lastBlockCount = 1;
blockLength = this.blockSize - 1;
} else {
- blockLength = this.lastBlockEndIndex;
- ++this.lastBlockEndIndex;
+ blockLength = this.lastBlockCount;
+ ++this.lastBlockCount;
}
// If the insertion point is not in the lastmost block
diff --git a/Source/Collections/Deque.Interfaces.cs b/Source/Collections/Deque.Interfaces.cs
new file mode 100644
index 0000000..17c8341
--- /dev/null
+++ b/Source/Collections/Deque.Interfaces.cs
@@ -0,0 +1,126 @@
+using System;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace Nuclex.Support.Collections {
+
+ partial class Deque {
+
+ #region IEnumerable members
+
+ /// Obtains a new enumerator for the contents of the deque
+ /// The new enumerator
+ IEnumerator IEnumerable.GetEnumerator() {
+ return new Enumerator(this);
+ }
+
+ #endregion
+
+ #region IList Members
+
+ /// Adds an item to the deque
+ /// Item that will be added to the deque
+ /// The index at which the new item was added
+ int IList.Add(object value) {
+ verifyCompatibleObject(value);
+
+ AddLast((ItemType)value);
+ return this.count - 1;
+ }
+
+ /// Checks whether the deque contains the specified item
+ /// Item the deque will be scanned for
+ /// True if the deque contained the specified item
+ bool IList.Contains(object value) {
+ return isCompatibleObject(value) && Contains((ItemType)value);
+ }
+
+ /// Determines the index of the item in the deque
+ /// Item whose index will be determined
+ /// The index of the specified item in the deque
+ int IList.IndexOf(object value) {
+ if(isCompatibleObject(value)) {
+ return IndexOf((ItemType)value);
+ } else {
+ return -1;
+ }
+ }
+
+ /// Inserts an item into the deque at the specified location
+ /// Index at which the item will be inserted
+ /// Item that will be inserted
+ void IList.Insert(int index, object value) {
+ verifyCompatibleObject(value);
+ Insert(index, (ItemType)value);
+ }
+
+ /// Whether the deque has a fixed size
+ bool IList.IsFixedSize {
+ get { return false; }
+ }
+
+ /// Whether the deque is read-only
+ bool IList.IsReadOnly {
+ get { throw new NotImplementedException(); }
+ }
+
+ /// Removes the specified item from the deque
+ /// Item that will be removed from the deque
+ void IList.Remove(object value) {
+ if(isCompatibleObject(value)) {
+ Remove((ItemType)value);
+ }
+ }
+
+ /// Accesses an item in the deque by its index
+ /// Index of the item that will be accessed
+ /// The item at the specified index
+ object IList.this[int index] {
+ get { return this[index]; }
+ set {
+ verifyCompatibleObject(value);
+ this[index] = (ItemType)value;
+ }
+ }
+
+ #endregion
+
+ #region ICollection Members
+
+ /// Adds an item into the deque
+ /// Item that will be added to the deque
+ void ICollection.Add(ItemType item) {
+ AddLast(item);
+ }
+
+ /// Whether the collection is read-only
+ bool ICollection.IsReadOnly {
+ get { return false; }
+ }
+
+ #endregion
+
+ #region ICollection Members
+
+ /// Copies the contents of the deque into an array
+ /// Array the contents of the deque will be copied into
+ /// Index at which writing into the array will begin
+ void ICollection.CopyTo(Array array, int index) {
+ throw new NotImplementedException();
+ }
+
+ /// Whether the deque is thread-synchronized
+ bool ICollection.IsSynchronized {
+ get { return false; }
+ }
+
+ /// Synchronization root of the instance
+ object ICollection.SyncRoot {
+ get { return this; }
+ }
+
+ #endregion
+
+ }
+
+} // namespace Nuclex.Support.Collections
diff --git a/Source/Collections/Deque.Removal.cs b/Source/Collections/Deque.Removal.cs
index 7438f37..61b34e8 100644
--- a/Source/Collections/Deque.Removal.cs
+++ b/Source/Collections/Deque.Removal.cs
@@ -6,6 +6,49 @@ namespace Nuclex.Support.Collections {
partial class Deque {
+ /// Removes all items from the deque
+ public void Clear() {
+ if(this.blocks.Count > 1) { // Are there multiple blocks?
+
+ // Clear the items in the first block to avoid holding on to references
+ // in memory unreachable to the user
+ for(int index = this.firstBlockStartIndex; index < this.blockSize; ++index) {
+ this.blocks[0][index] = default(ItemType);
+ }
+
+ // Remove any other blocks
+ this.blocks.RemoveRange(1, this.blocks.Count - 1);
+
+ } else { // Nope, only a single block exists
+
+ // Clear the items in the block to release any reference we may be keeping alive
+ for(
+ int index = this.firstBlockStartIndex; index < this.lastBlockCount; ++index
+ ) {
+ this.blocks[0][index] = default(ItemType);
+ }
+
+ }
+
+ // Reset the counters to restart the deque from scratch
+ this.firstBlockStartIndex = 0;
+ this.lastBlockCount = 0;
+ this.count = 0;
+ }
+
+ /// Removes the specified item from the deque
+ /// Item that will be removed from the deque
+ /// True if the item was found and removed
+ public bool Remove(ItemType item) {
+ int index = IndexOf(item);
+ if(index == -1) {
+ return false;
+ }
+
+ RemoveAt(index);
+ return true;
+ }
+
/// Removes the first item in the double-ended queue
public void RemoveFirst() {
if(this.count == 0) {
@@ -16,6 +59,8 @@ namespace Nuclex.Support.Collections {
// in unreachable spaces of its memory.
this.blocks[0][this.firstBlockStartIndex] = default(ItemType);
+ // Cut off the item from the first block. If the block became empty and it's
+ // not the last remaining block, remove it as well.
++this.firstBlockStartIndex;
if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty
if(this.count > 1) { // Still more blocks in queue, remove block
@@ -23,7 +68,7 @@ namespace Nuclex.Support.Collections {
this.firstBlockStartIndex = 0;
} else { // Last block - do not remove
this.firstBlockStartIndex = 0;
- this.lastBlockEndIndex = 0;
+ this.lastBlockCount = 0;
}
}
--this.count;
@@ -38,16 +83,18 @@ namespace Nuclex.Support.Collections {
// This is necessary to make sure the deque doesn't hold dead objects alive
// in unreachable spaces of its memory.
int lastBlock = this.blocks.Count - 1;
- this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(ItemType);
+ this.blocks[lastBlock][this.lastBlockCount - 1] = default(ItemType);
- --this.lastBlockEndIndex;
- if(this.lastBlockEndIndex == 0) { // Block became empty
+ // Cut off the last item in the last block. If the block became empty and it's
+ // not the last remaining block, remove it as well.
+ --this.lastBlockCount;
+ if(this.lastBlockCount == 0) { // Block became empty
if(this.count > 1) {
this.blocks.RemoveAt(lastBlock);
- this.lastBlockEndIndex = this.blockSize;
+ this.lastBlockCount = this.blockSize;
} else { // Last block - do not remove
this.firstBlockStartIndex = 0;
- this.lastBlockEndIndex = 0;
+ this.lastBlockCount = 0;
}
}
--this.count;
@@ -159,15 +206,15 @@ namespace Nuclex.Support.Collections {
Array.Copy(
this.blocks[lastBlock], startIndex + 1,
this.blocks[lastBlock], startIndex,
- this.lastBlockEndIndex - startIndex - 1
+ this.lastBlockCount - startIndex - 1
);
- if(this.lastBlockEndIndex == 1) {
+ if(this.lastBlockCount == 1) {
this.blocks.RemoveAt(lastBlock);
- this.lastBlockEndIndex = this.blockSize;
+ this.lastBlockCount = this.blockSize;
} else {
- this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(ItemType);
- --this.lastBlockEndIndex;
+ this.blocks[lastBlock][this.lastBlockCount - 1] = default(ItemType);
+ --this.lastBlockCount;
}
--this.count;
diff --git a/Source/Collections/Deque.Search.cs b/Source/Collections/Deque.Search.cs
new file mode 100644
index 0000000..4a4fcf0
--- /dev/null
+++ b/Source/Collections/Deque.Search.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Collections;
+
+namespace Nuclex.Support.Collections {
+
+ partial class Deque {
+
+ ///
+ /// Determines the index of the first occurence of the specified item in the deque
+ ///
+ /// Item that will be located in the deque
+ /// The index of the item or -1 if it wasn't found
+ public int IndexOf(ItemType item) {
+ if(this.blocks.Count == 1) { // Only one block to scan?
+ int length = this.lastBlockCount - this.firstBlockStartIndex;
+ int index = Array.IndexOf(
+ this.blocks[0], item, this.firstBlockStartIndex, length
+ );
+
+ // If we found something, we need to adjust its index so the first item in
+ // the deque always appears at index 0 to the user
+ if(index != -1) {
+ return (index - this.firstBlockStartIndex);
+ } else {
+ return -1;
+ }
+ } else { // At least two blocks exist
+
+ // Scan the first block for the item and if found, return the index
+ int length = this.blockSize - this.firstBlockStartIndex;
+ int index = Array.IndexOf(
+ this.blocks[0], item, this.firstBlockStartIndex, length
+ );
+
+ // If we found something, we need to adjust its index
+ if(index != -1) {
+ return (index - this.firstBlockStartIndex);
+ }
+
+ int lastBlock = this.blocks.Count - 1;
+ for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) {
+ index = Array.IndexOf(
+ this.blocks[tempIndex], item, 0, this.blockSize
+ );
+ if(index != -1) {
+ return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
+ }
+ }
+
+ // Nothing found, continue the search in the
+ index = Array.IndexOf(
+ this.blocks[lastBlock], item, 0, this.lastBlockCount
+ );
+ if(index == -1) {
+ return -1;
+ } else {
+ return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
+ }
+
+ }
+ }
+
+ }
+
+} // namespace Nuclex.Support.Collections
diff --git a/Source/Collections/Deque.Test.cs b/Source/Collections/Deque.Test.cs
index a5a52ef..4f1d4ec 100644
--- a/Source/Collections/Deque.Test.cs
+++ b/Source/Collections/Deque.Test.cs
@@ -21,6 +21,7 @@ License along with this library
#if UNITTEST
using System;
+using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
@@ -103,10 +104,7 @@ namespace Nuclex.Support.Collections {
[Test]
public void TestInsert() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
- Deque intDeque = new Deque(16);
- for(int item = 0; item < 96; ++item) {
- intDeque.AddLast(item);
- }
+ Deque intDeque = createDeque(96);
intDeque.Insert(testedIndex, 12345);
@@ -129,14 +127,7 @@ namespace Nuclex.Support.Collections {
[Test]
public void TestInsertNonNormalized() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
- Deque intDeque = new Deque(16);
- for(int item = 4; item < 96; ++item) {
- intDeque.AddLast(item);
- }
- intDeque.AddFirst(3);
- intDeque.AddFirst(2);
- intDeque.AddFirst(1);
- intDeque.AddFirst(0);
+ Deque intDeque = createNonNormalizedDeque(96);
intDeque.Insert(testedIndex, 12345);
@@ -202,7 +193,7 @@ namespace Nuclex.Support.Collections {
}
}
}
-
+
///
/// Tests whether the RemoveAt() method keeps the state of the deque intact when
/// it has to remove a block from the left end of the deque
@@ -301,10 +292,7 @@ namespace Nuclex.Support.Collections {
///
[Test]
public void TestIndexAssignment() {
- Deque intDeque = new Deque(16);
- for(int item = 0; item < 32; ++item) {
- intDeque.AddLast(item);
- }
+ Deque intDeque = createDeque(32);
intDeque[16] = 12345;
intDeque[17] = 54321;
@@ -351,7 +339,290 @@ namespace Nuclex.Support.Collections {
///
[Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)]
public void TestIndexOfNonNormalized(int count) {
+ Deque intDeque = createNonNormalizedDeque(count);
+
+ for(int item = 0; item < count; ++item) {
+ Assert.AreEqual(item, intDeque.IndexOf(item));
+ }
+ Assert.AreEqual(-1, intDeque.IndexOf(count));
+ }
+
+ /// Verifies that the deque's enumerator works
+ [Test]
+ public void TestEnumerator() {
+ Deque intDeque = createNonNormalizedDeque(40);
+
+ for(int testRun = 0; testRun < 2; ++testRun) {
+ using(IEnumerator enumerator = intDeque.GetEnumerator()) {
+ for(int index = 0; index < intDeque.Count; ++index) {
+ Assert.IsTrue(enumerator.MoveNext());
+ Assert.AreEqual(index, enumerator.Current);
+ }
+
+ Assert.IsFalse(enumerator.MoveNext());
+
+ enumerator.Reset();
+ }
+ }
+ }
+
+ /// Verifies that the deque's enumerator works
+ [Test]
+ public void TestObjectEnumerator() {
+ Deque intDeque = createNonNormalizedDeque(40);
+
+ for(int testRun = 0; testRun < 2; ++testRun) {
+ IEnumerator enumerator = ((IEnumerable)intDeque).GetEnumerator();
+ for(int index = 0; index < intDeque.Count; ++index) {
+ Assert.IsTrue(enumerator.MoveNext());
+ Assert.AreEqual(index, enumerator.Current);
+ }
+
+ Assert.IsFalse(enumerator.MoveNext());
+
+ enumerator.Reset();
+ }
+ }
+
+ ///
+ /// Verifies that an exception is thrown if the enumerator is accessed in
+ /// an invalid position
+ ///
+ [Test]
+ public void TestThrowOnInvalidEnumeratorPosition() {
+ Deque intDeque = createNonNormalizedDeque(40);
+
+ using(IEnumerator enumerator = intDeque.GetEnumerator()) {
+ Assert.Throws(
+ delegate() { Console.WriteLine(enumerator.Current); }
+ );
+
+ while(enumerator.MoveNext()) { }
+
+ Assert.Throws(
+ delegate() { Console.WriteLine(enumerator.Current); }
+ );
+ }
+ }
+
+ /// Tests whether a small deque can be cleared
+ [Test]
+ public void TestClearSmallDeque() {
+ Deque intDeque = createDeque(12);
+ intDeque.Clear();
+ Assert.AreEqual(0, intDeque.Count);
+ }
+
+ /// Tests whether a large deque can be cleared
+ [Test]
+ public void TestClearLargeDeque() {
+ Deque intDeque = createDeque(40);
+ intDeque.Clear();
+ Assert.AreEqual(0, intDeque.Count);
+ }
+
+ /// Verifies that the non-typesafe Add() method is working
+ [Test]
+ public void TestAddObject() {
+ Deque intDeque = new Deque();
+ Assert.AreEqual(0, ((IList)intDeque).Add(123));
+ Assert.AreEqual(1, intDeque.Count);
+ }
+
+ ///
+ /// Tests whether an exception is thrown if the non-typesafe Add() method is
+ /// used to add an incompatible object into the deque
+ ///
+ [Test]
+ public void TestThrowOnAddIncompatibleObject() {
+ Deque intDeque = new Deque();
+ Assert.Throws(
+ delegate() { ((IList)intDeque).Add("Hello World"); }
+ );
+ }
+
+ /// Verifies that the Add() method is working
+ [Test]
+ public void TestAdd() {
+ Deque intDeque = new Deque();
+ ((IList)intDeque).Add(123);
+ Assert.AreEqual(1, intDeque.Count);
+ }
+
+ /// Tests whether the Contains() method is working
+ [Test]
+ public void TestContains() {
+ Deque intDeque = createDeque(16);
+ Assert.IsTrue(intDeque.Contains(14));
+ Assert.IsFalse(intDeque.Contains(16));
+ }
+
+ /// Tests the non-typesafe Contains() method
+ [Test]
+ public void TestContainsObject() {
+ Deque intDeque = createDeque(16);
+ Assert.IsTrue(((IList)intDeque).Contains(14));
+ Assert.IsFalse(((IList)intDeque).Contains(16));
+ Assert.IsFalse(((IList)intDeque).Contains("Hello World"));
+ }
+
+ /// Tests the non-typesafe Contains() method
+ [Test]
+ public void TestIndexOfObject() {
+ Deque intDeque = createDeque(16);
+ Assert.AreEqual(14, ((IList)intDeque).IndexOf(14));
+ Assert.AreEqual(-1, ((IList)intDeque).IndexOf(16));
+ Assert.AreEqual(-1, ((IList)intDeque).IndexOf("Hello World"));
+ }
+
+ /// Tests wether the non-typesafe Insert() method is working
+ [Test]
+ public void TestInsertObject() {
+ for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
+ Deque intDeque = createDeque(96);
+
+ ((IList)intDeque).Insert(testedIndex, 12345);
+
+ Assert.AreEqual(97, intDeque.Count);
+
+ for(int index = 0; index < testedIndex; ++index) {
+ Assert.AreEqual(index, intDeque[index]);
+ }
+ Assert.AreEqual(12345, intDeque[testedIndex]);
+ for(int index = testedIndex + 1; index < 97; ++index) {
+ Assert.AreEqual(index - 1, intDeque[index]);
+ }
+ }
+ }
+
+ ///
+ /// Verifies that an exception is thrown if an incompatible object is inserted
+ /// into the deque
+ ///
+ [Test]
+ public void TestThrowOnInsertIncompatibleObject() {
+ Deque intDeque = createDeque(12);
+ Assert.Throws(
+ delegate() { ((IList)intDeque).Insert(8, "Hello World"); }
+ );
+ }
+
+ /// Validates that the IsFixedObject property is set to false
+ [Test]
+ public void TestIsFixedObject() {
+ Deque intDeque = new Deque();
+ Assert.IsFalse(((IList)intDeque).IsFixedSize);
+ }
+
+ /// Validates that the IsSynchronized property is set to false
+ [Test]
+ public void TestIsSynchronized() {
+ Deque intDeque = new Deque();
+ Assert.IsFalse(((IList)intDeque).IsSynchronized);
+ }
+
+ ///
+ /// Verifies that items can be assigned by their index using the non-typesafe
+ /// IList interface
+ ///
+ [Test]
+ public void TestObjectIndexAssignment() {
+ Deque intDeque = createDeque(32);
+
+ ((IList)intDeque)[16] = 12345;
+ ((IList)intDeque)[17] = 54321;
+
+ Assert.AreEqual(12345, ((IList)intDeque)[16]);
+ Assert.AreEqual(54321, ((IList)intDeque)[17]);
+ }
+
+ ///
+ /// Tests whether an exception is thrown if an incompatible object is assigned
+ /// to the deque
+ ///
+ [Test]
+ public void TestIncompatibleObjectIndexAssignment() {
+ Deque intDeque = createDeque(2);
+ Assert.Throws(
+ delegate() { ((IList)intDeque)[0] = "Hello World"; }
+ );
+ }
+
+ /// Verifies that the Remove() method is working correctly
+ [Test]
+ public void TestRemove() {
+ Deque intDeque = createDeque(16);
+ Assert.AreEqual(16, intDeque.Count);
+ Assert.IsTrue(intDeque.Remove(13));
+ Assert.IsFalse(intDeque.Remove(13));
+ Assert.AreEqual(15, intDeque.Count);
+ }
+
+ /// Tests the non-typesafe remove method
+ [Test]
+ public void TestRemoveObject() {
+ Deque intDeque = createDeque(10);
+ Assert.IsTrue(intDeque.Contains(8));
+ Assert.AreEqual(10, intDeque.Count);
+ ((IList)intDeque).Remove(8);
+ Assert.IsFalse(intDeque.Contains(8));
+ Assert.AreEqual(9, intDeque.Count);
+ }
+
+ ///
+ /// Tests the non-typesafe remove method used to remove an incompatible object
+ ///
+ [Test]
+ public void TestRemoveIncompatibleObject() {
+ Deque intDeque = createDeque(10);
+ ((IList)intDeque).Remove("Hello World"); // should simply do nothing
+ Assert.AreEqual(10, intDeque.Count);
+ }
+
+ ///
+ /// Verifies that the IsSynchronized property and the SyncRoot property are working
+ ///
+ [Test]
+ public void TestSynchronization() {
+ Deque intDeque = new Deque();
+
+ if(!(intDeque as ICollection).IsSynchronized) {
+ lock((intDeque as ICollection).SyncRoot) {
+ Assert.AreEqual(0, intDeque.Count);
+ }
+ }
+ }
+
+ ///
+ /// Validates that the IsReadOnly property of the deque returns false
+ ///
+ [Test]
+ public void TestIsReadOnly() {
+ Deque intDeque = new Deque();
+ Assert.IsFalse(((IList)intDeque).IsReadOnly);
+ Assert.IsFalse(((ICollection)intDeque).IsReadOnly);
+ }
+
+ /// Tests the non-typesafe CopyTo() method
+ [Test]
+ public void TestCopyToObjectArray() {
+ // TODO Write a unit test for the non-typesafe CopyTo() method
+ }
+
+ /// Tests the CopyTo() method
+ [Test]
+ public void TestCopyToArray() {
+ // TODO Write a unit test for the typesafe CopyTo() method
+ }
+
+ ///
+ /// Creates a deque whose first element does not coincide with a block boundary
+ ///
+ /// Number of items the deque will be filled with
+ /// The newly created deque
+ private static Deque createNonNormalizedDeque(int count) {
Deque intDeque = new Deque(16);
+
for(int item = 4; item < count; ++item) {
intDeque.AddLast(item);
}
@@ -360,10 +631,21 @@ namespace Nuclex.Support.Collections {
if(count > 1) { intDeque.AddFirst(1); }
if(count > 0) { intDeque.AddFirst(0); }
+ return intDeque;
+ }
+
+ /// Creates a deque filled with the specified number of items
+ ///
+ /// Number of items the deque will be filled with
+ /// The newly created deque
+ private static Deque createDeque(int count) {
+ Deque intDeque = new Deque(16);
+
for(int item = 0; item < count; ++item) {
- Assert.AreEqual(item, intDeque.IndexOf(item));
+ intDeque.AddLast(item);
}
- Assert.AreEqual(-1, intDeque.IndexOf(count));
+
+ return intDeque;
}
}
diff --git a/Source/Collections/Deque.cs b/Source/Collections/Deque.cs
index 6bd1f30..2201a68 100644
--- a/Source/Collections/Deque.cs
+++ b/Source/Collections/Deque.cs
@@ -18,7 +18,111 @@ namespace Nuclex.Support.Collections {
/// require items to be copied around and still can be accessed by index.
///
///
- public partial class Deque /*: IList, IList*/ {
+ 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.lastBlockCount - 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 ItemType Current {
+ get {
+ 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 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;
+ }
+
+ /// The item at the enumerator's current position
+ object IEnumerator.Current {
+ get { return Current; }
+ }
+
+ /// 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 ItemType[] currentBlock;
+ /// Index in the current block
+ private int subIndex;
+
+ }
+
+ #endregion // class Enumerator
/// Initializes a new deque
public Deque() : this(512) { }
@@ -58,7 +162,7 @@ namespace Nuclex.Support.Collections {
/// The first item in the double-ended queue
public ItemType First {
get {
- if(this.lastBlockEndIndex <= this.firstBlockStartIndex) {
+ if(this.count == 0) {
throw new InvalidOperationException("The deque is empty");
}
return this.blocks[0][this.firstBlockStartIndex];
@@ -68,66 +172,31 @@ namespace Nuclex.Support.Collections {
/// The last item in the double-ended queue
public ItemType Last {
get {
- if(this.lastBlockEndIndex <= this.firstBlockStartIndex) {
+ if(this.count == 0) {
throw new InvalidOperationException("The deque is empty");
}
- return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1];
+ return this.blocks[this.blocks.Count - 1][this.lastBlockCount - 1];
}
}
- ///
- /// Determines the index of the first occurence of the specified item in the deque
- ///
- /// Item that will be located in the deque
- /// The index of the item or -1 if it wasn't found
- public int IndexOf(ItemType item) {
- if(this.blocks.Count == 1) { // Only one block to scan?
- int length = this.lastBlockEndIndex - this.firstBlockStartIndex;
- int index = Array.IndexOf(
- this.blocks[0], item, this.firstBlockStartIndex, length
- );
+ /// 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(ItemType item) {
+ return (IndexOf(item) != -1);
+ }
- // If we found something, we need to adjust its index so the first item in
- // the deque always appears at index 0 to the user
- if(index != -1) {
- return (index - this.firstBlockStartIndex);
- } else {
- return -1;
- }
- } else { // At least two blocks exist
+ /// 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(ItemType[] array, int arrayIndex) {
+ throw new NotImplementedException();
+ }
- // Scan the first block for the item and if found, return the index
- int length = this.blockSize - this.firstBlockStartIndex;
- int index = Array.IndexOf(
- this.blocks[0], item, this.firstBlockStartIndex, length
- );
-
- // If we found something, we need to adjust its index
- if(index != -1) {
- return (index - this.firstBlockStartIndex);
- }
-
- int lastBlock = this.blocks.Count - 1;
- for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) {
- index = Array.IndexOf(
- this.blocks[tempIndex], item, 0, this.blockSize
- );
- if(index != -1) {
- return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
- }
- }
-
- // Nothing found, continue the search in the
- index = Array.IndexOf(
- this.blocks[lastBlock], item, 0, this.lastBlockEndIndex
- );
- if(index == -1) {
- return -1;
- } else {
- return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
- }
-
- }
+ /// 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
@@ -143,6 +212,23 @@ namespace Nuclex.Support.Collections {
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 ItemType) || ((value == null) && !typeof(ItemType).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
@@ -152,7 +238,7 @@ namespace Nuclex.Support.Collections {
/// Starting index of data in the first block
private int firstBlockStartIndex;
/// End index of data in the last block
- private int lastBlockEndIndex;
+ private int lastBlockCount;
}