From 3ee5fdfc676d19b04ceb06e384242f3c28bf1bb7 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Tue, 14 Jul 2009 20:15:34 +0000 Subject: [PATCH] The deque class now fully supports the IList<> and IList interfaces (with the exception of CopyTo() and CopyToArray() which are not implemented yet); implemented a Clear() method; the deque enumerator is now fully functioning (but still missing an out-of-sync check); moved IndexOf() into its own file; wrote several additional unit tests to verify all the new interface methods are working and to keep test coverage at 100% git-svn-id: file:///srv/devel/repo-conversion/nusu@163 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (Xbox 360).csproj | 6 + Nuclex.Support.csproj | 6 + Source/Collections/Deque.Insertion.cs | 16 +- Source/Collections/Deque.Interfaces.cs | 126 ++++++++++ Source/Collections/Deque.Removal.cs | 69 +++++- Source/Collections/Deque.Search.cs | 66 +++++ Source/Collections/Deque.Test.cs | 320 +++++++++++++++++++++++-- Source/Collections/Deque.cs | 198 ++++++++++----- 8 files changed, 713 insertions(+), 94 deletions(-) create mode 100644 Source/Collections/Deque.Interfaces.cs create mode 100644 Source/Collections/Deque.Search.cs 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; }