From ff44edcdf112ef660e758d0f5a0d83a7d5b4392f Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Fri, 10 Jul 2009 18:42:23 +0000 Subject: [PATCH] Began implementing a block-allocating Deque class similar to C++'s std::deque - insertion and removal at both ends already working git-svn-id: file:///srv/devel/repo-conversion/nusu@158 d2e56fa2-650e-0410-a79f-9358c0239efd --- Nuclex.Support (Xbox 360).csproj | 6 +- Nuclex.Support.csproj | 4 + Source/Collections/Deque.Test.cs | 101 ++++++++++ Source/Collections/Deque.cs | 244 +++++++++++++++++++++++ Source/Collections/ItemEventArgs.Test.cs | 4 +- Source/IO/PartialStream.Test.cs | 1 - 6 files changed, 356 insertions(+), 4 deletions(-) create mode 100644 Source/Collections/Deque.Test.cs create mode 100644 Source/Collections/Deque.cs diff --git a/Nuclex.Support (Xbox 360).csproj b/Nuclex.Support (Xbox 360).csproj index 1aff2d1..d810efe 100644 --- a/Nuclex.Support (Xbox 360).csproj +++ b/Nuclex.Support (Xbox 360).csproj @@ -1,4 +1,4 @@ - + {DFFEAB70-51B8-4714-BCA6-79B733BBC520} {2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} @@ -69,6 +69,10 @@ AssertHelper.cs + + + Deque.cs + diff --git a/Nuclex.Support.csproj b/Nuclex.Support.csproj index bf1bc76..9c60d86 100644 --- a/Nuclex.Support.csproj +++ b/Nuclex.Support.csproj @@ -51,6 +51,10 @@ AssertHelper.cs + + + Deque.cs + diff --git a/Source/Collections/Deque.Test.cs b/Source/Collections/Deque.Test.cs new file mode 100644 index 0000000..04f1f5d --- /dev/null +++ b/Source/Collections/Deque.Test.cs @@ -0,0 +1,101 @@ +#region CPL License +/* +Nuclex Framework +Copyright (C) 2002-2009 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 + +#if UNITTEST + +using System; +using System.Collections.Generic; + +using NUnit.Framework; +using NMock2; + +namespace Nuclex.Support.Collections { + +#if false + /// Unit Test for the double ended queue + [TestFixture] + public class DequeTest { + + /// Verifies that the AddLast() method of the deque is working + [Test] + public void TestAddLast() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddLast(item); + } + + for(int item = 0; item < 48; ++item) { + Assert.AreEqual(item, intDeque[item]); + } + } + + /// Verifies that the AddFirst() method of the deque is working + [Test] + public void TestAddFirst() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddFirst(item); + } + + for(int item = 0; item < 48; ++item) { + Assert.AreEqual(47 - item, intDeque[item]); + } + } + + /// + /// Verifies that the RemoveFirst() method of the deque is working + /// + [Test] + public void TestRemoveFirst() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddLast(item); + } + + for(int item = 0; item < 48; ++item) { + Assert.AreEqual(item, intDeque.First); + Assert.AreEqual(48 - item, intDeque.Count); + intDeque.RemoveFirst(); + } + } + + /// + /// Verifies that the RemoveLast() method of the deque is working + /// + [Test] + public void TestRemoveLast() { + Deque intDeque = new Deque(16); + for(int item = 0; item < 48; ++item) { + intDeque.AddLast(item); + } + + for(int item = 0; item < 48; ++item) { + Assert.AreEqual(47 - item, intDeque.Last); + Assert.AreEqual(48 - item, intDeque.Count); + intDeque.RemoveLast(); + } + } + + } +#endif + +} // namespace Nuclex.Support.Collections + +#endif // UNITTEST diff --git a/Source/Collections/Deque.cs b/Source/Collections/Deque.cs new file mode 100644 index 0000000..32ead50 --- /dev/null +++ b/Source/Collections/Deque.cs @@ -0,0 +1,244 @@ +using System; +using System.Collections.Generic; +using System.Collections; + +namespace Nuclex.Support.Collections { + +#if false + /// 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 still can be accessed by index. + /// + /// + public class Deque /*: IList, IList*/ { + + /// 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 ItemType[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 ItemType 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 ItemType First { + get { + if(this.lastBlockEndIndex <= this.firstBlockStartIndex) { + throw new InvalidOperationException("The deque is empty"); + } + return this.blocks[0][this.firstBlockStartIndex]; + } + } + + /// The last item in the double-ended queue + public ItemType Last { + get { + if(this.lastBlockEndIndex <= this.firstBlockStartIndex) { + throw new InvalidOperationException("The deque is empty"); + } + return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1]; + } + } + + /// Removes the first item in the double-ended queue + public void RemoveFirst() { + if(this.count == 0) { + throw new InvalidOperationException("Cannot remove items from empty deque"); + } + + ++this.firstBlockStartIndex; + if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty + if(this.count > 1) { // Still more blocks in queue, remove block + this.blocks.RemoveAt(0); + this.firstBlockStartIndex = 0; + } else { // Last block - do not remove + this.firstBlockStartIndex = 0; + this.lastBlockEndIndex = 0; + } + } + --this.count; + } + + /// Removes the last item in the double-ended queue + public void RemoveLast() { + if(this.count == 0) { + throw new InvalidOperationException("Cannot remove items from empty deque"); + } + + --this.lastBlockEndIndex; + if(this.lastBlockEndIndex == 0) { // Block became empty + if(this.count > 1) { + this.blocks.RemoveAt(this.blocks.Count - 1); + this.lastBlockEndIndex = this.blockSize; + } else { // Last block - do not remove + this.firstBlockStartIndex = 0; + this.lastBlockEndIndex = 0; + } + } + --this.count; + } + + /// Inserts an item at the beginning of the double-ended queue + /// Item that will be inserted into the queue + public void AddFirst(ItemType item) { + if(this.firstBlockStartIndex > 0) { + --this.firstBlockStartIndex; + } else { // Need to allocate a new block + this.blocks.Insert(0, new ItemType[this.blockSize]); + this.firstBlockStartIndex = this.blockSize - 1; + } + + this.blocks[0][this.firstBlockStartIndex] = item; + ++this.count; + } + + /// 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; + } else { // Need to allocate a new block + this.blocks.Add(new ItemType[this.blockSize]); + this.lastBlockEndIndex = 1; + } + + this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1] = item; + ++this.count; + } + + public void Insert(int index, ItemType item) { + if(index == this.count) { + AddLast(item); + } else { + int blockIndex, subIndex; + findIndex(index, out blockIndex, out subIndex); + + //for(int block = this.blocks.Count - 1; block > + } + } + // http://www.codeproject.com/KB/dotnet/ccnetsandcastle.aspx + /* + public int IndexOf(ItemType item) { + switch(this.blocks.Count) { + + // No block exist, so the item cannot be found + case 0: { + return -1; + } + + // Only one block exists, start index and end index apply to the same block + case 1: { + int count = this.lastBlockEndIndex - this.firstBlockStartIndex; + int index = Array.IndexOf( + this.blocks[0], item, this.firstBlockStartIndex, count + ); + + // 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; + } + } + + // Two blocks exist, start index is in first block, end index in second block + case 2: { + + // Scan the first block for the item and if found, return the index + int count = this.blockSize - this.firstBlockStartIndex; + int index = Array.IndexOf( + this.blocks[0], item, this.firstBlockStartIndex, this.blockSize + ); + + // If we found something, we need to adjust its index + if(index != -1) { + return (index - this.firstBlockStartIndex); + } + + // Nothing found, continue the search in the + index = Array.IndexOf( + this.blocks[1], item, 0, this.lastBlockEndIndex + ); + if(index == -1) { + return -1; + } else { + return (index - this.firstBlockStartIndex + this.blockSize); + } + } + + default: { + int count = this.blockSize - this.firstBlockStartIndex; + int index = Array.IndexOf( + this.blocks[0], item, this.firstBlockStartIndex, this.blockSize + ); + return -1; + } + + } + } + */ + /// 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); + } + + /// 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; + + } +#endif + +} // namespace Nuclex.Support.Collections diff --git a/Source/Collections/ItemEventArgs.Test.cs b/Source/Collections/ItemEventArgs.Test.cs index 8c32b4e..fdad07b 100644 --- a/Source/Collections/ItemEventArgs.Test.cs +++ b/Source/Collections/ItemEventArgs.Test.cs @@ -18,11 +18,11 @@ License along with this library */ #endregion +#if UNITTEST + using System; using System.Collections.Generic; -#if UNITTEST - using NUnit.Framework; using NMock2; diff --git a/Source/IO/PartialStream.Test.cs b/Source/IO/PartialStream.Test.cs index 509befb..d7ad2ae 100644 --- a/Source/IO/PartialStream.Test.cs +++ b/Source/IO/PartialStream.Test.cs @@ -329,7 +329,6 @@ namespace Nuclex.Support.IO { Assert.AreEqual(10, bytesRead); Assert.AreEqual(20, partialStream.Position); - } }