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
This commit is contained in:
Markus Ewald 2009-07-14 20:15:34 +00:00
parent a817f52406
commit 3ee5fdfc67
8 changed files with 713 additions and 94 deletions

View File

@ -73,9 +73,15 @@
<Compile Include="Source\Collections\Deque.Insertion.cs"> <Compile Include="Source\Collections\Deque.Insertion.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\Deque.Interfaces.cs">
<DependentUpon>Deque.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\Deque.Removal.cs"> <Compile Include="Source\Collections\Deque.Removal.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\Deque.Search.cs">
<DependentUpon>Deque.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\Deque.Test.cs"> <Compile Include="Source\Collections\Deque.Test.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>

View File

@ -55,9 +55,15 @@
<Compile Include="Source\Collections\Deque.Insertion.cs"> <Compile Include="Source\Collections\Deque.Insertion.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\Deque.Interfaces.cs">
<DependentUpon>Deque.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\Deque.Removal.cs"> <Compile Include="Source\Collections\Deque.Removal.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Source\Collections\Deque.Search.cs">
<DependentUpon>Deque.cs</DependentUpon>
</Compile>
<Compile Include="Source\Collections\Deque.Test.cs"> <Compile Include="Source\Collections\Deque.Test.cs">
<DependentUpon>Deque.cs</DependentUpon> <DependentUpon>Deque.cs</DependentUpon>
</Compile> </Compile>

View File

@ -23,14 +23,14 @@ namespace Nuclex.Support.Collections {
/// <summary>Appends an item to the end of the double-ended queue</summary> /// <summary>Appends an item to the end of the double-ended queue</summary>
/// <param name="item">Item that will be appended to the queue</param> /// <param name="item">Item that will be appended to the queue</param>
public void AddLast(ItemType item) { public void AddLast(ItemType item) {
if(this.lastBlockEndIndex < this.blockSize) { if(this.lastBlockCount < this.blockSize) {
++this.lastBlockEndIndex; ++this.lastBlockCount;
} else { // Need to allocate a new block } else { // Need to allocate a new block
this.blocks.Add(new ItemType[this.blockSize]); 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; ++this.count;
} }
@ -137,15 +137,15 @@ namespace Nuclex.Support.Collections {
int blockLength; int blockLength;
// If the lastmost block is full, we need to add another block // 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.Add(new ItemType[this.blockSize]);
this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1]; this.blocks[lastBlock + 1][0] = this.blocks[lastBlock][this.blockSize - 1];
this.lastBlockEndIndex = 1; this.lastBlockCount = 1;
blockLength = this.blockSize - 1; blockLength = this.blockSize - 1;
} else { } else {
blockLength = this.lastBlockEndIndex; blockLength = this.lastBlockCount;
++this.lastBlockEndIndex; ++this.lastBlockCount;
} }
// If the insertion point is not in the lastmost block // If the insertion point is not in the lastmost block

View File

@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Collections;
namespace Nuclex.Support.Collections {
partial class Deque<ItemType> {
#region IEnumerable members
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
/// <returns>The new enumerator</returns>
IEnumerator IEnumerable.GetEnumerator() {
return new Enumerator(this);
}
#endregion
#region IList Members
/// <summary>Adds an item to the deque</summary>
/// <param name="value">Item that will be added to the deque</param>
/// <returns>The index at which the new item was added</returns>
int IList.Add(object value) {
verifyCompatibleObject(value);
AddLast((ItemType)value);
return this.count - 1;
}
/// <summary>Checks whether the deque contains the specified item</summary>
/// <param name="value">Item the deque will be scanned for</param>
/// <returns>True if the deque contained the specified item</returns>
bool IList.Contains(object value) {
return isCompatibleObject(value) && Contains((ItemType)value);
}
/// <summary>Determines the index of the item in the deque</summary>
/// <param name="value">Item whose index will be determined</param>
/// <returns>The index of the specified item in the deque</returns>
int IList.IndexOf(object value) {
if(isCompatibleObject(value)) {
return IndexOf((ItemType)value);
} else {
return -1;
}
}
/// <summary>Inserts an item into the deque at the specified location</summary>
/// <param name="index">Index at which the item will be inserted</param>
/// <param name="value">Item that will be inserted</param>
void IList.Insert(int index, object value) {
verifyCompatibleObject(value);
Insert(index, (ItemType)value);
}
/// <summary>Whether the deque has a fixed size</summary>
bool IList.IsFixedSize {
get { return false; }
}
/// <summary>Whether the deque is read-only</summary>
bool IList.IsReadOnly {
get { throw new NotImplementedException(); }
}
/// <summary>Removes the specified item from the deque</summary>
/// <param name="value">Item that will be removed from the deque</param>
void IList.Remove(object value) {
if(isCompatibleObject(value)) {
Remove((ItemType)value);
}
}
/// <summary>Accesses an item in the deque by its index</summary>
/// <param name="index">Index of the item that will be accessed</param>
/// <returns>The item at the specified index</returns>
object IList.this[int index] {
get { return this[index]; }
set {
verifyCompatibleObject(value);
this[index] = (ItemType)value;
}
}
#endregion
#region ICollection<ItemType> Members
/// <summary>Adds an item into the deque</summary>
/// <param name="item">Item that will be added to the deque</param>
void ICollection<ItemType>.Add(ItemType item) {
AddLast(item);
}
/// <summary>Whether the collection is read-only</summary>
bool ICollection<ItemType>.IsReadOnly {
get { return false; }
}
#endregion
#region ICollection Members
/// <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="index">Index at which writing into the array will begin</param>
void ICollection.CopyTo(Array array, int index) {
throw new NotImplementedException();
}
/// <summary>Whether the deque is thread-synchronized</summary>
bool ICollection.IsSynchronized {
get { return false; }
}
/// <summary>Synchronization root of the instance</summary>
object ICollection.SyncRoot {
get { return this; }
}
#endregion
}
} // namespace Nuclex.Support.Collections

View File

@ -6,6 +6,49 @@ namespace Nuclex.Support.Collections {
partial class Deque<ItemType> { partial class Deque<ItemType> {
/// <summary>Removes all items from the deque</summary>
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;
}
/// <summary>Removes the specified item from the deque</summary>
/// <param name="item">Item that will be removed from the deque</param>
/// <returns>True if the item was found and removed</returns>
public bool Remove(ItemType item) {
int index = IndexOf(item);
if(index == -1) {
return false;
}
RemoveAt(index);
return true;
}
/// <summary>Removes the first item in the double-ended queue</summary> /// <summary>Removes the first item in the double-ended queue</summary>
public void RemoveFirst() { public void RemoveFirst() {
if(this.count == 0) { if(this.count == 0) {
@ -16,6 +59,8 @@ namespace Nuclex.Support.Collections {
// in unreachable spaces of its memory. // in unreachable spaces of its memory.
this.blocks[0][this.firstBlockStartIndex] = default(ItemType); 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; ++this.firstBlockStartIndex;
if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty if(this.firstBlockStartIndex >= this.blockSize) { // Block became empty
if(this.count > 1) { // Still more blocks in queue, remove block if(this.count > 1) { // Still more blocks in queue, remove block
@ -23,7 +68,7 @@ namespace Nuclex.Support.Collections {
this.firstBlockStartIndex = 0; this.firstBlockStartIndex = 0;
} else { // Last block - do not remove } else { // Last block - do not remove
this.firstBlockStartIndex = 0; this.firstBlockStartIndex = 0;
this.lastBlockEndIndex = 0; this.lastBlockCount = 0;
} }
} }
--this.count; --this.count;
@ -38,16 +83,18 @@ namespace Nuclex.Support.Collections {
// This is necessary to make sure the deque doesn't hold dead objects alive // This is necessary to make sure the deque doesn't hold dead objects alive
// in unreachable spaces of its memory. // in unreachable spaces of its memory.
int lastBlock = this.blocks.Count - 1; int lastBlock = this.blocks.Count - 1;
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(ItemType); this.blocks[lastBlock][this.lastBlockCount - 1] = default(ItemType);
--this.lastBlockEndIndex; // Cut off the last item in the last block. If the block became empty and it's
if(this.lastBlockEndIndex == 0) { // Block became empty // not the last remaining block, remove it as well.
--this.lastBlockCount;
if(this.lastBlockCount == 0) { // Block became empty
if(this.count > 1) { if(this.count > 1) {
this.blocks.RemoveAt(lastBlock); this.blocks.RemoveAt(lastBlock);
this.lastBlockEndIndex = this.blockSize; this.lastBlockCount = this.blockSize;
} else { // Last block - do not remove } else { // Last block - do not remove
this.firstBlockStartIndex = 0; this.firstBlockStartIndex = 0;
this.lastBlockEndIndex = 0; this.lastBlockCount = 0;
} }
} }
--this.count; --this.count;
@ -159,15 +206,15 @@ namespace Nuclex.Support.Collections {
Array.Copy( Array.Copy(
this.blocks[lastBlock], startIndex + 1, this.blocks[lastBlock], startIndex + 1,
this.blocks[lastBlock], startIndex, 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.blocks.RemoveAt(lastBlock);
this.lastBlockEndIndex = this.blockSize; this.lastBlockCount = this.blockSize;
} else { } else {
this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(ItemType); this.blocks[lastBlock][this.lastBlockCount - 1] = default(ItemType);
--this.lastBlockEndIndex; --this.lastBlockCount;
} }
--this.count; --this.count;

View File

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Collections;
namespace Nuclex.Support.Collections {
partial class Deque<ItemType> {
/// <summary>
/// Determines the index of the first occurence of the specified item in the deque
/// </summary>
/// <param name="item">Item that will be located in the deque</param>
/// <returns>The index of the item or -1 if it wasn't found</returns>
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<ItemType>(
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<ItemType>(
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<ItemType>(
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<ItemType>(
this.blocks[lastBlock], item, 0, this.lastBlockCount
);
if(index == -1) {
return -1;
} else {
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
}
}
}
}
} // namespace Nuclex.Support.Collections

View File

@ -21,6 +21,7 @@ License along with this library
#if UNITTEST #if UNITTEST
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
@ -103,10 +104,7 @@ namespace Nuclex.Support.Collections {
[Test] [Test]
public void TestInsert() { public void TestInsert() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) { for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
Deque<int> intDeque = new Deque<int>(16); Deque<int> intDeque = createDeque(96);
for(int item = 0; item < 96; ++item) {
intDeque.AddLast(item);
}
intDeque.Insert(testedIndex, 12345); intDeque.Insert(testedIndex, 12345);
@ -129,14 +127,7 @@ namespace Nuclex.Support.Collections {
[Test] [Test]
public void TestInsertNonNormalized() { public void TestInsertNonNormalized() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) { for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
Deque<int> intDeque = new Deque<int>(16); Deque<int> intDeque = createNonNormalizedDeque(96);
for(int item = 4; item < 96; ++item) {
intDeque.AddLast(item);
}
intDeque.AddFirst(3);
intDeque.AddFirst(2);
intDeque.AddFirst(1);
intDeque.AddFirst(0);
intDeque.Insert(testedIndex, 12345); intDeque.Insert(testedIndex, 12345);
@ -301,10 +292,7 @@ namespace Nuclex.Support.Collections {
/// </summary> /// </summary>
[Test] [Test]
public void TestIndexAssignment() { public void TestIndexAssignment() {
Deque<int> intDeque = new Deque<int>(16); Deque<int> intDeque = createDeque(32);
for(int item = 0; item < 32; ++item) {
intDeque.AddLast(item);
}
intDeque[16] = 12345; intDeque[16] = 12345;
intDeque[17] = 54321; intDeque[17] = 54321;
@ -351,7 +339,290 @@ namespace Nuclex.Support.Collections {
/// </summary> /// </summary>
[Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)] [Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)]
public void TestIndexOfNonNormalized(int count) { public void TestIndexOfNonNormalized(int count) {
Deque<int> intDeque = createNonNormalizedDeque(count);
for(int item = 0; item < count; ++item) {
Assert.AreEqual(item, intDeque.IndexOf(item));
}
Assert.AreEqual(-1, intDeque.IndexOf(count));
}
/// <summary>Verifies that the deque's enumerator works</summary>
[Test]
public void TestEnumerator() {
Deque<int> intDeque = createNonNormalizedDeque(40);
for(int testRun = 0; testRun < 2; ++testRun) {
using(IEnumerator<int> 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();
}
}
}
/// <summary>Verifies that the deque's enumerator works</summary>
[Test]
public void TestObjectEnumerator() {
Deque<int> 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();
}
}
/// <summary>
/// Verifies that an exception is thrown if the enumerator is accessed in
/// an invalid position
/// </summary>
[Test]
public void TestThrowOnInvalidEnumeratorPosition() {
Deque<int> intDeque = createNonNormalizedDeque(40);
using(IEnumerator<int> enumerator = intDeque.GetEnumerator()) {
Assert.Throws<InvalidOperationException>(
delegate() { Console.WriteLine(enumerator.Current); }
);
while(enumerator.MoveNext()) { }
Assert.Throws<InvalidOperationException>(
delegate() { Console.WriteLine(enumerator.Current); }
);
}
}
/// <summary>Tests whether a small deque can be cleared</summary>
[Test]
public void TestClearSmallDeque() {
Deque<int> intDeque = createDeque(12);
intDeque.Clear();
Assert.AreEqual(0, intDeque.Count);
}
/// <summary>Tests whether a large deque can be cleared</summary>
[Test]
public void TestClearLargeDeque() {
Deque<int> intDeque = createDeque(40);
intDeque.Clear();
Assert.AreEqual(0, intDeque.Count);
}
/// <summary>Verifies that the non-typesafe Add() method is working</summary>
[Test]
public void TestAddObject() {
Deque<int> intDeque = new Deque<int>();
Assert.AreEqual(0, ((IList)intDeque).Add(123));
Assert.AreEqual(1, intDeque.Count);
}
/// <summary>
/// Tests whether an exception is thrown if the non-typesafe Add() method is
/// used to add an incompatible object into the deque
/// </summary>
[Test]
public void TestThrowOnAddIncompatibleObject() {
Deque<int> intDeque = new Deque<int>();
Assert.Throws<ArgumentException>(
delegate() { ((IList)intDeque).Add("Hello World"); }
);
}
/// <summary>Verifies that the Add() method is working</summary>
[Test]
public void TestAdd() {
Deque<int> intDeque = new Deque<int>();
((IList<int>)intDeque).Add(123);
Assert.AreEqual(1, intDeque.Count);
}
/// <summary>Tests whether the Contains() method is working</summary>
[Test]
public void TestContains() {
Deque<int> intDeque = createDeque(16);
Assert.IsTrue(intDeque.Contains(14));
Assert.IsFalse(intDeque.Contains(16));
}
/// <summary>Tests the non-typesafe Contains() method</summary>
[Test]
public void TestContainsObject() {
Deque<int> intDeque = createDeque(16);
Assert.IsTrue(((IList)intDeque).Contains(14));
Assert.IsFalse(((IList)intDeque).Contains(16));
Assert.IsFalse(((IList)intDeque).Contains("Hello World"));
}
/// <summary>Tests the non-typesafe Contains() method</summary>
[Test]
public void TestIndexOfObject() {
Deque<int> 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"));
}
/// <summary>Tests wether the non-typesafe Insert() method is working</summary>
[Test]
public void TestInsertObject() {
for(int testedIndex = 0; testedIndex <= 96; ++testedIndex) {
Deque<int> 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]);
}
}
}
/// <summary>
/// Verifies that an exception is thrown if an incompatible object is inserted
/// into the deque
/// </summary>
[Test]
public void TestThrowOnInsertIncompatibleObject() {
Deque<int> intDeque = createDeque(12);
Assert.Throws<ArgumentException>(
delegate() { ((IList)intDeque).Insert(8, "Hello World"); }
);
}
/// <summary>Validates that the IsFixedObject property is set to false</summary>
[Test]
public void TestIsFixedObject() {
Deque<int> intDeque = new Deque<int>();
Assert.IsFalse(((IList)intDeque).IsFixedSize);
}
/// <summary>Validates that the IsSynchronized property is set to false</summary>
[Test]
public void TestIsSynchronized() {
Deque<int> intDeque = new Deque<int>();
Assert.IsFalse(((IList)intDeque).IsSynchronized);
}
/// <summary>
/// Verifies that items can be assigned by their index using the non-typesafe
/// IList interface
/// </summary>
[Test]
public void TestObjectIndexAssignment() {
Deque<int> intDeque = createDeque(32);
((IList)intDeque)[16] = 12345;
((IList)intDeque)[17] = 54321;
Assert.AreEqual(12345, ((IList)intDeque)[16]);
Assert.AreEqual(54321, ((IList)intDeque)[17]);
}
/// <summary>
/// Tests whether an exception is thrown if an incompatible object is assigned
/// to the deque
/// </summary>
[Test]
public void TestIncompatibleObjectIndexAssignment() {
Deque<int> intDeque = createDeque(2);
Assert.Throws<ArgumentException>(
delegate() { ((IList)intDeque)[0] = "Hello World"; }
);
}
/// <summary>Verifies that the Remove() method is working correctly</summary>
[Test]
public void TestRemove() {
Deque<int> intDeque = createDeque(16);
Assert.AreEqual(16, intDeque.Count);
Assert.IsTrue(intDeque.Remove(13));
Assert.IsFalse(intDeque.Remove(13));
Assert.AreEqual(15, intDeque.Count);
}
/// <summary>Tests the non-typesafe remove method</summary>
[Test]
public void TestRemoveObject() {
Deque<int> 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);
}
/// <summary>
/// Tests the non-typesafe remove method used to remove an incompatible object
/// </summary>
[Test]
public void TestRemoveIncompatibleObject() {
Deque<int> intDeque = createDeque(10);
((IList)intDeque).Remove("Hello World"); // should simply do nothing
Assert.AreEqual(10, intDeque.Count);
}
/// <summary>
/// Verifies that the IsSynchronized property and the SyncRoot property are working
/// </summary>
[Test]
public void TestSynchronization() {
Deque<int> intDeque = new Deque<int>();
if(!(intDeque as ICollection).IsSynchronized) {
lock((intDeque as ICollection).SyncRoot) {
Assert.AreEqual(0, intDeque.Count);
}
}
}
/// <summary>
/// Validates that the IsReadOnly property of the deque returns false
/// </summary>
[Test]
public void TestIsReadOnly() {
Deque<int> intDeque = new Deque<int>();
Assert.IsFalse(((IList)intDeque).IsReadOnly);
Assert.IsFalse(((ICollection<int>)intDeque).IsReadOnly);
}
/// <summary>Tests the non-typesafe CopyTo() method</summary>
[Test]
public void TestCopyToObjectArray() {
// TODO Write a unit test for the non-typesafe CopyTo() method
}
/// <summary>Tests the CopyTo() method</summary>
[Test]
public void TestCopyToArray() {
// TODO Write a unit test for the typesafe CopyTo() method
}
/// <summary>
/// Creates a deque whose first element does not coincide with a block boundary
/// </summary>
/// <param name="count">Number of items the deque will be filled with</param>
/// <returns>The newly created deque</returns>
private static Deque<int> createNonNormalizedDeque(int count) {
Deque<int> intDeque = new Deque<int>(16); Deque<int> intDeque = new Deque<int>(16);
for(int item = 4; item < count; ++item) { for(int item = 4; item < count; ++item) {
intDeque.AddLast(item); intDeque.AddLast(item);
} }
@ -360,10 +631,21 @@ namespace Nuclex.Support.Collections {
if(count > 1) { intDeque.AddFirst(1); } if(count > 1) { intDeque.AddFirst(1); }
if(count > 0) { intDeque.AddFirst(0); } if(count > 0) { intDeque.AddFirst(0); }
for(int item = 0; item < count; ++item) { return intDeque;
Assert.AreEqual(item, intDeque.IndexOf(item));
} }
Assert.AreEqual(-1, intDeque.IndexOf(count));
/// <summary>Creates a deque filled with the specified number of items
/// </summary>
/// <param name="count">Number of items the deque will be filled with</param>
/// <returns>The newly created deque</returns>
private static Deque<int> createDeque(int count) {
Deque<int> intDeque = new Deque<int>(16);
for(int item = 0; item < count; ++item) {
intDeque.AddLast(item);
}
return intDeque;
} }
} }

View File

@ -18,7 +18,111 @@ namespace Nuclex.Support.Collections {
/// require items to be copied around and still can be accessed by index. /// require items to be copied around and still can be accessed by index.
/// </para> /// </para>
/// </remarks> /// </remarks>
public partial class Deque<ItemType> /*: IList<ItemType>, IList*/ { public partial class Deque<ItemType> : IList<ItemType>, IList {
#region class Enumerator
/// <summary>Enumerates over the items in a deque</summary>
private class Enumerator : IEnumerator<ItemType>, IEnumerator {
/// <summary>Initializes a new deque enumerator</summary>
/// <param name="deque">Deque whose items will be enumerated</param>
public Enumerator(Deque<ItemType> deque) {
this.deque = deque;
this.blockSize = this.deque.blockSize;
this.lastBlock = this.deque.blocks.Count - 1;
this.lastBlockEndIndex = this.deque.lastBlockCount - 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 ItemType Current {
get {
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 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;
}
/// <summary>The item at the enumerator's current position</summary>
object IEnumerator.Current {
get { return Current; }
}
/// <summary>Deque the enumerator belongs to</summary>
private Deque<ItemType> 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 ItemType[] currentBlock;
/// <summary>Index in the current block</summary>
private int subIndex;
}
#endregion // class Enumerator
/// <summary>Initializes a new deque</summary> /// <summary>Initializes a new deque</summary>
public Deque() : this(512) { } public Deque() : this(512) { }
@ -58,7 +162,7 @@ namespace Nuclex.Support.Collections {
/// <summary>The first item in the double-ended queue</summary> /// <summary>The first item in the double-ended queue</summary>
public ItemType First { public ItemType First {
get { get {
if(this.lastBlockEndIndex <= this.firstBlockStartIndex) { if(this.count == 0) {
throw new InvalidOperationException("The deque is empty"); throw new InvalidOperationException("The deque is empty");
} }
return this.blocks[0][this.firstBlockStartIndex]; return this.blocks[0][this.firstBlockStartIndex];
@ -68,66 +172,31 @@ namespace Nuclex.Support.Collections {
/// <summary>The last item in the double-ended queue</summary> /// <summary>The last item in the double-ended queue</summary>
public ItemType Last { public ItemType Last {
get { get {
if(this.lastBlockEndIndex <= this.firstBlockStartIndex) { if(this.count == 0) {
throw new InvalidOperationException("The deque is empty"); 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];
} }
} }
/// <summary> /// <summary>Determines whether the deque contains the specified item</summary>
/// Determines the index of the first occurence of the specified item in the deque /// <param name="item">Item the deque will be scanned for</param>
/// </summary> /// <returns>True if the deque contains the item, false otherwise</returns>
/// <param name="item">Item that will be located in the deque</param> public bool Contains(ItemType item) {
/// <returns>The index of the item or -1 if it wasn't found</returns> return (IndexOf(item) != -1);
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<ItemType>(
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<ItemType>(
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; /// <summary>Copies the contents of the deque into an array</summary>
for(int tempIndex = 1; tempIndex < lastBlock; ++tempIndex) { /// <param name="array">Array the contents of the deque will be copied into</param>
index = Array.IndexOf<ItemType>( /// <param name="arrayIndex">Array index the deque contents will begin at</param>
this.blocks[tempIndex], item, 0, this.blockSize public void CopyTo(ItemType[] array, int arrayIndex) {
); throw new NotImplementedException();
if(index != -1) {
return (index - this.firstBlockStartIndex + tempIndex * this.blockSize);
}
} }
// Nothing found, continue the search in the /// <summary>Obtains a new enumerator for the contents of the deque</summary>
index = Array.IndexOf<ItemType>( /// <returns>The new enumerator</returns>
this.blocks[lastBlock], item, 0, this.lastBlockEndIndex public IEnumerator<ItemType> GetEnumerator() {
); return new Enumerator(this);
if(index == -1) {
return -1;
} else {
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
}
}
} }
/// <summary>Calculates the block index and local sub index of an entry</summary> /// <summary>Calculates the block index and local sub index of an entry</summary>
@ -143,6 +212,23 @@ namespace Nuclex.Support.Collections {
blockIndex = Math.DivRem(index, this.blockSize, out subIndex); 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 ItemType) || ((value == null) && !typeof(ItemType).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> /// <summary>Number if items currently stored in the deque</summary>
private int count; private int count;
/// <summary>Size of a single deque block</summary> /// <summary>Size of a single deque block</summary>
@ -152,7 +238,7 @@ namespace Nuclex.Support.Collections {
/// <summary>Starting index of data in the first block</summary> /// <summary>Starting index of data in the first block</summary>
private int firstBlockStartIndex; private int firstBlockStartIndex;
/// <summary>End index of data in the last block</summary> /// <summary>End index of data in the last block</summary>
private int lastBlockEndIndex; private int lastBlockCount;
} }