From ed62fa85f247bff9078a7dacfaaebc88e9c9a066 Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Sat, 11 Jul 2009 19:55:59 +0000 Subject: [PATCH] Implemented deque Insert() and RemoveAt() methods with fixed shifting of all items to the right for now - still have to extend the code to shift items to the closest end; added more unit tests to achieve 100% test coverage for the code git-svn-id: file:///srv/devel/repo-conversion/nusu@159 d2e56fa2-650e-0410-a79f-9358c0239efd --- Source/Collections/Deque.Test.cs | 259 ++++++++++++++++++++++- Source/Collections/Deque.cs | 113 +++++++++- Source/Tracking/TransactionGroup.Test.cs | 2 +- 3 files changed, 369 insertions(+), 5 deletions(-) diff --git a/Source/Collections/Deque.Test.cs b/Source/Collections/Deque.Test.cs index 04f1f5d..e0c737e 100644 --- a/Source/Collections/Deque.Test.cs +++ b/Source/Collections/Deque.Test.cs @@ -28,7 +28,8 @@ using NMock2; namespace Nuclex.Support.Collections { -#if false +#if true + /// Unit Test for the double ended queue [TestFixture] public class DequeTest { @@ -93,7 +94,263 @@ namespace Nuclex.Support.Collections { } } + /// + /// Validates that an exception is thrown if the 'First' property is accessed + /// in an empty deque + /// + [Test] + public void TestThrowOnAccessFirstInEmptyDeque() { + Deque intDeque = new Deque(); + Assert.Throws( + delegate() { Console.WriteLine(intDeque.First); } + ); + } + + /// + /// Validates that an exception is thrown if the 'Last' property is accessed + /// in an empty deque + /// + [Test] + public void TestThrowOnAccessLastInEmptyDeque() { + Deque intDeque = new Deque(); + Assert.Throws( + delegate() { Console.WriteLine(intDeque.Last); } + ); + } + + /// + /// Validates that an exception is thrown if the first item is attempted to be + /// removed from an empty deque + /// + [Test] + public void TestThrowOnRemoveFirstFromEmptyDeque() { + Deque intDeque = new Deque(); + Assert.Throws( + delegate() { intDeque.RemoveFirst(); } + ); + } + + /// + /// Validates that an exception is thrown if the last item is attempted to be + /// removed from an empty deque + /// + [Test] + public void TestThrowOnRemoveLastFromEmptyDeque() { + Deque intDeque = new Deque(); + Assert.Throws( + delegate() { intDeque.RemoveLast(); } + ); + } + + /// + /// Tests whether the Insert() method of the deque can insert an item at + /// the end of the deque + /// + [Test] + public void TestInsertAtEnd() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddLast(item); + } + + intDeque.Insert(intDeque.Count, 12345); + + Assert.AreEqual(12345, intDeque.Last); + } + + /// + /// Tests whether the Insert() method of the deque can insert an item into + /// the last block of the deque when that last block is already full + /// + [Test] + public void TestInsertInLastBlock() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddLast(item); + } + + intDeque.Insert(45, 12345); + + Assert.AreEqual(49, intDeque.Count); + for(int index = 0; index < 44; ++index) { + Assert.AreEqual(index, intDeque[index]); + } + Assert.AreEqual(12345, intDeque[45]); + for(int index = 46; index < 49; ++index) { + Assert.AreEqual(index - 1, intDeque[index]); + } + } + + /// + /// Tests whether the Insert() method of the deque can insert an item into + /// the second-to-last block of the deque + /// + [Test] + public void TestInsertInSecondToLastBlock() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 40; ++item) { + intDeque.AddLast(item); + } + + intDeque.Insert(24, 12345); + + Assert.AreEqual(41, intDeque.Count); + for(int index = 0; index < 24; ++index) { + Assert.AreEqual(index, intDeque[index]); + } + Assert.AreEqual(12345, intDeque[24]); + for(int index = 25; index < 41; ++index) { + Assert.AreEqual(index - 1, intDeque[index]); + } + } + + /// + /// Tests whether the Insert() method of the deque can insert an item into + /// the third-to-last block of the deque + /// + [Test] + public void TestInsertInThirdToLastBlock() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 40; ++item) { + intDeque.AddLast(item); + } + + intDeque.Insert(8, 12345); + + Assert.AreEqual(41, intDeque.Count); + + for(int index = 0; index < 8; ++index) { + Assert.AreEqual(index, intDeque[index]); + } + Assert.AreEqual(12345, intDeque[8]); + for(int index = 9; index < 41; ++index) { + Assert.AreEqual(index - 1, intDeque[index]); + } + } + + /// + /// Tests whether the RemoveAt() method of the deque can remove an item from + /// the end of the deque + /// + [Test] + public void TestRemoveAtEnd() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddLast(item); + } + + intDeque.RemoveAt(intDeque.Count - 1); + + Assert.AreEqual(46, intDeque.Last); + } + + /// + /// Tests whether the RemoveAt() method of the deque can remove an item + /// from the last block of the deque + /// + [Test] + public void TestRemoveFromLastBlock() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddLast(item); + } + + intDeque.RemoveAt(45); + + Assert.AreEqual(47, intDeque.Count); + + for(int index = 0; index < 45; ++index) { + Assert.AreEqual(index, intDeque[index]); + } + for(int index = 45; index < 47; ++index) { + Assert.AreEqual(index + 1, intDeque[index]); + } + } + + /// + /// Tests whether the RemoveAt() method of the deque can remove an item from + /// the second-to-last block of the deque + /// + [Test] + public void TestRemoveFromSecondToLastBlock() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 40; ++item) { + intDeque.AddLast(item); + } + + intDeque.RemoveAt(24); + + Assert.AreEqual(39, intDeque.Count); + + for(int index = 0; index < 24; ++index) { + Assert.AreEqual(index, intDeque[index]); + } + for(int index = 24; index < 39; ++index) { + Assert.AreEqual(index + 1, intDeque[index]); + } + } + + /// + /// Tests whether the RemoveAt() method of the deque can remove an item from + /// the third-to-last block of the deque + /// + [Test] + public void TestRemoveFromThirdToLastBlock() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 33; ++item) { + intDeque.AddLast(item); + } + + intDeque.RemoveAt(8); + + Assert.AreEqual(32, intDeque.Count); + + for(int index = 0; index < 8; ++index) { + Assert.AreEqual(index, intDeque[index]); + } + for(int index = 8; index < 32; ++index) { + Assert.AreEqual(index + 1, intDeque[index]); + } + } + + /// + /// Verifies that items can be assigned by their index + /// + [Test] + public void TestIndexAssignment() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 32; ++item) { + intDeque.AddLast(item); + } + intDeque[16] = 12345; + intDeque[17] = 54321; + + for(int index = 0; index < 16; ++index) { + intDeque.RemoveFirst(); + } + + Assert.AreEqual(12345, intDeque.First); + intDeque.RemoveFirst(); + Assert.AreEqual(54321, intDeque.First); + } + + /// + /// Verifies that an exception is thrown if an invalid index is accessed + /// + [Test] + public void TestThrowOnInvalidIndex() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 32; ++item) { + intDeque.AddLast(item); + } + + Assert.Throws( + delegate() { Console.WriteLine(intDeque[32]); } + ); + } + } + #endif } // namespace Nuclex.Support.Collections diff --git a/Source/Collections/Deque.cs b/Source/Collections/Deque.cs index 32ead50..18430d5 100644 --- a/Source/Collections/Deque.cs +++ b/Source/Collections/Deque.cs @@ -4,7 +4,8 @@ using System.Collections; namespace Nuclex.Support.Collections { -#if false +#if true + /// A double-ended queue that allocates memory in blocks /// Type of the items being stored in the queue /// @@ -81,6 +82,8 @@ namespace Nuclex.Support.Collections { if(this.count == 0) { throw new InvalidOperationException("Cannot remove items from empty deque"); } + + // TODO: Zero removed array entry if array is kept ++this.firstBlockStartIndex; if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty @@ -101,6 +104,8 @@ namespace Nuclex.Support.Collections { throw new InvalidOperationException("Cannot remove items from empty deque"); } + // TODO: Zero removed array entry if array is kept + --this.lastBlockEndIndex; if(this.lastBlockEndIndex == 0) { // Block became empty if(this.count > 1) { @@ -142,17 +147,118 @@ namespace Nuclex.Support.Collections { ++this.count; } + /// Inserts the item at the specified index + /// Index the item will be inserted at + /// Item that will be inserted public void Insert(int index, ItemType item) { + // TODO: Not perfect. Can shift to left or to right in a deque. + // Rewrite! if(index == this.count) { AddLast(item); } else { int blockIndex, subIndex; findIndex(index, out blockIndex, out subIndex); - //for(int block = this.blocks.Count - 1; block > + int lastBlock = this.blocks.Count - 1; + int blockLength; + + // If the lastmost block is full, we need to add another block + if(this.lastBlockEndIndex == this.blockSize) { + this.blocks.Add(new ItemType[this.blockSize]); + this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1]; + this.lastBlockEndIndex = 1; + + blockLength = this.blockSize - 1; + } else { + blockLength = this.lastBlockEndIndex; + ++this.lastBlockEndIndex; + } + + // If the insertion point is not in the lastmost block + if(blockIndex != lastBlock) { + Array.Copy( + this.blocks[lastBlock], 0, this.blocks[lastBlock], 1, blockLength + ); + this.blocks[lastBlock][0] = this.blocks[lastBlock - 1][this.blockSize - 1]; + + // Move all the blocks following the insertion point to the right by one item. + // If there are no blocks inbetween, this for loop will not run. + for(int tempIndex = lastBlock - 1; tempIndex > blockIndex; --tempIndex) { + Array.Copy( + this.blocks[tempIndex], 0, this.blocks[tempIndex], 1, this.blockSize - 1 + ); + this.blocks[tempIndex][0] = this.blocks[tempIndex - 1][this.blockSize - 1]; + } + + blockLength = this.blockSize - 1; + } + + // Finally, move the items in the block the insertion takes place in + Array.Copy( + this.blocks[blockIndex], subIndex, + this.blocks[blockIndex], subIndex + 1, + blockLength - subIndex + ); + + this.blocks[blockIndex][subIndex] = item; + ++this.count; } } - // http://www.codeproject.com/KB/dotnet/ccnetsandcastle.aspx + + /// Removes the item at the specified index + /// Index of the item that will be removed + public void RemoveAt(int index) { + // TODO: Not perfect. Can shift to left or to right in a deque. + // Rewrite! + if(index == this.count - 1) { + RemoveLast(); + } else { + int blockIndex, subIndex; + findIndex(index, out blockIndex, out subIndex); + + int lastBlock = this.blocks.Count - 1; + int startIndex; + + if(blockIndex < lastBlock) { + Array.Copy( + this.blocks[blockIndex], subIndex + 1, + this.blocks[blockIndex], subIndex, + this.blockSize - subIndex - 1 + ); + this.blocks[blockIndex][this.blockSize - 1] = this.blocks[blockIndex + 1][0]; + + for(int tempIndex = blockIndex + 1; tempIndex < lastBlock; ++tempIndex) { + Array.Copy( + this.blocks[tempIndex], 1, + this.blocks[tempIndex], 0, + this.blockSize - 1 + ); + this.blocks[tempIndex][this.blockSize - 1] = this.blocks[tempIndex + 1][0]; + } + + startIndex = 0; + } else { + startIndex = subIndex; + } + + Array.Copy( + this.blocks[lastBlock], startIndex + 1, + this.blocks[lastBlock], startIndex, + this.lastBlockEndIndex - startIndex - 1 + ); + + if(this.lastBlockEndIndex == 1) { + this.blocks.RemoveAt(lastBlock); + this.lastBlockEndIndex = this.blockSize; + } else { + this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(ItemType); + --this.lastBlockEndIndex; + } + + --this.count; + } + } + /* public int IndexOf(ItemType item) { switch(this.blocks.Count) { @@ -239,6 +345,7 @@ namespace Nuclex.Support.Collections { private int lastBlockEndIndex; } + #endif } // namespace Nuclex.Support.Collections diff --git a/Source/Tracking/TransactionGroup.Test.cs b/Source/Tracking/TransactionGroup.Test.cs index 6488237..2ee58eb 100644 --- a/Source/Tracking/TransactionGroup.Test.cs +++ b/Source/Tracking/TransactionGroup.Test.cs @@ -193,7 +193,7 @@ namespace Nuclex.Support.Tracking { this.mockery = new Mockery(); } - /// Validates that the set transaction properly sums the progress + /// Validates that the transaction group correctly sums the progress [Test] public void TestSummedProgress() { using(