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:
parent
a817f52406
commit
3ee5fdfc67
|
@ -73,9 +73,15 @@
|
|||
<Compile Include="Source\Collections\Deque.Insertion.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Interfaces.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Removal.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Search.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Test.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
|
|
@ -55,9 +55,15 @@
|
|||
<Compile Include="Source\Collections\Deque.Insertion.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Interfaces.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Removal.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Search.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Source\Collections\Deque.Test.cs">
|
||||
<DependentUpon>Deque.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
|
|
@ -23,14 +23,14 @@ namespace Nuclex.Support.Collections {
|
|||
/// <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>
|
||||
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
|
||||
|
|
126
Source/Collections/Deque.Interfaces.cs
Normal file
126
Source/Collections/Deque.Interfaces.cs
Normal 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
|
|
@ -6,6 +6,49 @@ namespace Nuclex.Support.Collections {
|
|||
|
||||
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>
|
||||
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;
|
||||
|
|
66
Source/Collections/Deque.Search.cs
Normal file
66
Source/Collections/Deque.Search.cs
Normal 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
|
|
@ -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<int> intDeque = new Deque<int>(16);
|
||||
for(int item = 0; item < 96; ++item) {
|
||||
intDeque.AddLast(item);
|
||||
}
|
||||
Deque<int> 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<int> intDeque = new Deque<int>(16);
|
||||
for(int item = 4; item < 96; ++item) {
|
||||
intDeque.AddLast(item);
|
||||
}
|
||||
intDeque.AddFirst(3);
|
||||
intDeque.AddFirst(2);
|
||||
intDeque.AddFirst(1);
|
||||
intDeque.AddFirst(0);
|
||||
Deque<int> intDeque = createNonNormalizedDeque(96);
|
||||
|
||||
intDeque.Insert(testedIndex, 12345);
|
||||
|
||||
|
@ -301,10 +292,7 @@ namespace Nuclex.Support.Collections {
|
|||
/// </summary>
|
||||
[Test]
|
||||
public void TestIndexAssignment() {
|
||||
Deque<int> intDeque = new Deque<int>(16);
|
||||
for(int item = 0; item < 32; ++item) {
|
||||
intDeque.AddLast(item);
|
||||
}
|
||||
Deque<int> intDeque = createDeque(32);
|
||||
intDeque[16] = 12345;
|
||||
intDeque[17] = 54321;
|
||||
|
||||
|
@ -351,7 +339,290 @@ namespace Nuclex.Support.Collections {
|
|||
/// </summary>
|
||||
[Test, TestCase(0), TestCase(16), TestCase(32), TestCase(48)]
|
||||
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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/// <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) {
|
||||
Assert.AreEqual(item, intDeque.IndexOf(item));
|
||||
intDeque.AddLast(item);
|
||||
}
|
||||
Assert.AreEqual(-1, intDeque.IndexOf(count));
|
||||
|
||||
return intDeque;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,111 @@ namespace Nuclex.Support.Collections {
|
|||
/// require items to be copied around and still can be accessed by index.
|
||||
/// </para>
|
||||
/// </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>
|
||||
public Deque() : this(512) { }
|
||||
|
@ -58,7 +162,7 @@ namespace Nuclex.Support.Collections {
|
|||
/// <summary>The first item in the double-ended queue</summary>
|
||||
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 {
|
|||
/// <summary>The last item in the double-ended queue</summary>
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.lastBlockEndIndex - this.firstBlockStartIndex;
|
||||
int index = Array.IndexOf<ItemType>(
|
||||
this.blocks[0], item, this.firstBlockStartIndex, length
|
||||
);
|
||||
/// <summary>Determines whether the deque contains the specified item</summary>
|
||||
/// <param name="item">Item the deque will be scanned for</param>
|
||||
/// <returns>True if the deque contains the item, false otherwise</returns>
|
||||
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
|
||||
/// <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="arrayIndex">Array index the deque contents will begin at</param>
|
||||
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<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.lastBlockEndIndex
|
||||
);
|
||||
if(index == -1) {
|
||||
return -1;
|
||||
} else {
|
||||
return (index - this.firstBlockStartIndex + lastBlock * this.blockSize);
|
||||
}
|
||||
|
||||
}
|
||||
/// <summary>Obtains a new enumerator for the contents of the deque</summary>
|
||||
/// <returns>The new enumerator</returns>
|
||||
public IEnumerator<ItemType> GetEnumerator() {
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
/// <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>
|
||||
private int count;
|
||||
/// <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>
|
||||
private int firstBlockStartIndex;
|
||||
/// <summary>End index of data in the last block</summary>
|
||||
private int lastBlockEndIndex;
|
||||
private int lastBlockCount;
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user