lastBlockCount is called lastBlockEndIndex again (anyone got a better term - it's an index one past the last entry, not a count); implemented the CopyTo() method; added unit tests for the CopyTo() method

git-svn-id: file:///srv/devel/repo-conversion/nusu@164 d2e56fa2-650e-0410-a79f-9358c0239efd
This commit is contained in:
Markus Ewald 2009-07-14 21:08:10 +00:00
parent 3ee5fdfc67
commit 0b23b1f7c4
6 changed files with 120 additions and 31 deletions

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.lastBlockCount < this.blockSize) { if(this.lastBlockEndIndex < this.blockSize) {
++this.lastBlockCount; ++this.lastBlockEndIndex;
} 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.lastBlockCount = 1; this.lastBlockEndIndex = 1;
} }
this.blocks[this.blocks.Count - 1][this.lastBlockCount - 1] = item; this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 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.lastBlockCount == this.blockSize) { if(this.lastBlockEndIndex == 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.lastBlockCount = 1; this.lastBlockEndIndex = 1;
blockLength = this.blockSize - 1; blockLength = this.blockSize - 1;
} else { } else {
blockLength = this.lastBlockCount; blockLength = this.lastBlockEndIndex;
++this.lastBlockCount; ++this.lastBlockEndIndex;
} }
// If the insertion point is not in the lastmost block // If the insertion point is not in the lastmost block

View File

@ -61,7 +61,7 @@ namespace Nuclex.Support.Collections {
/// <summary>Whether the deque is read-only</summary> /// <summary>Whether the deque is read-only</summary>
bool IList.IsReadOnly { bool IList.IsReadOnly {
get { throw new NotImplementedException(); } get { return false; }
} }
/// <summary>Removes the specified item from the deque</summary> /// <summary>Removes the specified item from the deque</summary>
@ -106,7 +106,11 @@ namespace Nuclex.Support.Collections {
/// <param name="array">Array the contents of the deque will be copied into</param> /// <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> /// <param name="index">Index at which writing into the array will begin</param>
void ICollection.CopyTo(Array array, int index) { void ICollection.CopyTo(Array array, int index) {
throw new NotImplementedException(); if(!(array is ItemType[])) {
throw new ArgumentException("Incompatible array type", "array");
}
CopyTo((ItemType[])array, index);
} }
/// <summary>Whether the deque is thread-synchronized</summary> /// <summary>Whether the deque is thread-synchronized</summary>

View File

@ -23,7 +23,7 @@ namespace Nuclex.Support.Collections {
// Clear the items in the block to release any reference we may be keeping alive // Clear the items in the block to release any reference we may be keeping alive
for( for(
int index = this.firstBlockStartIndex; index < this.lastBlockCount; ++index int index = this.firstBlockStartIndex; index < this.lastBlockEndIndex; ++index
) { ) {
this.blocks[0][index] = default(ItemType); this.blocks[0][index] = default(ItemType);
} }
@ -32,7 +32,7 @@ namespace Nuclex.Support.Collections {
// Reset the counters to restart the deque from scratch // Reset the counters to restart the deque from scratch
this.firstBlockStartIndex = 0; this.firstBlockStartIndex = 0;
this.lastBlockCount = 0; this.lastBlockEndIndex = 0;
this.count = 0; this.count = 0;
} }
@ -68,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.lastBlockCount = 0; this.lastBlockEndIndex = 0;
} }
} }
--this.count; --this.count;
@ -83,18 +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.lastBlockCount - 1] = default(ItemType); this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(ItemType);
// Cut off the last item in the last block. If the block became empty and it's // 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. // not the last remaining block, remove it as well.
--this.lastBlockCount; --this.lastBlockEndIndex;
if(this.lastBlockCount == 0) { // Block became empty if(this.lastBlockEndIndex == 0) { // Block became empty
if(this.count > 1) { if(this.count > 1) {
this.blocks.RemoveAt(lastBlock); this.blocks.RemoveAt(lastBlock);
this.lastBlockCount = this.blockSize; this.lastBlockEndIndex = this.blockSize;
} else { // Last block - do not remove } else { // Last block - do not remove
this.firstBlockStartIndex = 0; this.firstBlockStartIndex = 0;
this.lastBlockCount = 0; this.lastBlockEndIndex = 0;
} }
} }
--this.count; --this.count;
@ -206,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.lastBlockCount - startIndex - 1 this.lastBlockEndIndex - startIndex - 1
); );
if(this.lastBlockCount == 1) { if(this.lastBlockEndIndex == 1) {
this.blocks.RemoveAt(lastBlock); this.blocks.RemoveAt(lastBlock);
this.lastBlockCount = this.blockSize; this.lastBlockEndIndex = this.blockSize;
} else { } else {
this.blocks[lastBlock][this.lastBlockCount - 1] = default(ItemType); this.blocks[lastBlock][this.lastBlockEndIndex - 1] = default(ItemType);
--this.lastBlockCount; --this.lastBlockEndIndex;
} }
--this.count; --this.count;

View File

@ -13,7 +13,7 @@ namespace Nuclex.Support.Collections {
/// <returns>The index of the item or -1 if it wasn't found</returns> /// <returns>The index of the item or -1 if it wasn't found</returns>
public int IndexOf(ItemType item) { public int IndexOf(ItemType item) {
if(this.blocks.Count == 1) { // Only one block to scan? if(this.blocks.Count == 1) { // Only one block to scan?
int length = this.lastBlockCount - this.firstBlockStartIndex; int length = this.lastBlockEndIndex - this.firstBlockStartIndex;
int index = Array.IndexOf<ItemType>( int index = Array.IndexOf<ItemType>(
this.blocks[0], item, this.firstBlockStartIndex, length this.blocks[0], item, this.firstBlockStartIndex, length
); );
@ -50,7 +50,7 @@ namespace Nuclex.Support.Collections {
// Nothing found, continue the search in the // Nothing found, continue the search in the
index = Array.IndexOf<ItemType>( index = Array.IndexOf<ItemType>(
this.blocks[lastBlock], item, 0, this.lastBlockCount this.blocks[lastBlock], item, 0, this.lastBlockEndIndex
); );
if(index == -1) { if(index == -1) {
return -1; return -1;

View File

@ -606,13 +606,54 @@ namespace Nuclex.Support.Collections {
/// <summary>Tests the non-typesafe CopyTo() method</summary> /// <summary>Tests the non-typesafe CopyTo() method</summary>
[Test] [Test]
public void TestCopyToObjectArray() { public void TestCopyToObjectArray() {
// TODO Write a unit test for the non-typesafe CopyTo() method Deque<int> intDeque = createNonNormalizedDeque(40);
int[] array = new int[40];
((ICollection)intDeque).CopyTo(array, 0);
Assert.AreEqual(intDeque, array);
} }
/// <summary>Tests the CopyTo() method</summary> /// <summary>Tests the CopyTo() method</summary>
[Test] [Test]
public void TestCopyToArray() { public void TestCopyToArray() {
// TODO Write a unit test for the typesafe CopyTo() method Deque<int> intDeque = createDeque(12);
intDeque.RemoveFirst();
intDeque.RemoveFirst();
int[] array = new int[14];
intDeque.CopyTo(array, 4);
Assert.AreEqual(
new int[] { 0, 0, 0, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 },
array
);
}
/// <summary>
/// Verifies that the non-typesafe CopyTo() method throws an exception if
/// the array is of an incompatible type
/// </summary>
[Test]
public void TestThrowOnCopyToIncompatibleObjectArray() {
Deque<int> intDeque = createDeque(4);
short[] array = new short[4];
Assert.Throws<ArgumentException>(
delegate() { ((ICollection)intDeque).CopyTo(array, 4); }
);
}
/// <summary>
/// Verifies that the CopyTo() method throws an exception if the target array
/// is too small
/// </summary>
[Test]
public void TestThrowOnCopyToTooSmallArray() {
Deque<int> intDeque = createDeque(8);
Assert.Throws<ArgumentException>(
delegate() { intDeque.CopyTo(new int[7], 0); }
);
} }
/// <summary> /// <summary>

View File

@ -31,7 +31,7 @@ namespace Nuclex.Support.Collections {
this.deque = deque; this.deque = deque;
this.blockSize = this.deque.blockSize; this.blockSize = this.deque.blockSize;
this.lastBlock = this.deque.blocks.Count - 1; this.lastBlock = this.deque.blocks.Count - 1;
this.lastBlockEndIndex = this.deque.lastBlockCount - 1; this.lastBlockEndIndex = this.deque.lastBlockEndIndex - 1;
Reset(); Reset();
} }
@ -175,7 +175,7 @@ namespace Nuclex.Support.Collections {
if(this.count == 0) { 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.lastBlockCount - 1]; return this.blocks[this.blocks.Count - 1][this.lastBlockEndIndex - 1];
} }
} }
@ -190,7 +190,51 @@ namespace Nuclex.Support.Collections {
/// <param name="array">Array the contents of the deque will be copied into</param> /// <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> /// <param name="arrayIndex">Array index the deque contents will begin at</param>
public void CopyTo(ItemType[] array, int arrayIndex) { public void CopyTo(ItemType[] array, int arrayIndex) {
throw new NotImplementedException(); if(this.count > (array.Length - arrayIndex)) {
throw new ArgumentException(
"Array too small to hold the collection items starting at the specified index"
);
}
if(this.blocks.Count == 1) { // Does only one block exist?
// Copy the one and only block there is
Array.Copy(
this.blocks[0], this.firstBlockStartIndex,
array, arrayIndex,
this.lastBlockEndIndex - this.firstBlockStartIndex
);
} else { // Multiple blocks exist
// Copy the first block which is filled from the start index to its end
int length = this.blockSize - this.firstBlockStartIndex;
Array.Copy(
this.blocks[0], this.firstBlockStartIndex,
array, arrayIndex,
length
);
arrayIndex += length;
// Copy all intermediate blocks (if there are any). These are completely filled
int lastBlock = this.blocks.Count - 1;
for(int index = 1; index < lastBlock; ++index) {
Array.Copy(
this.blocks[index], 0,
array, arrayIndex,
this.blockSize
);
arrayIndex += this.blockSize;
}
// Copy the final block which is filled from the beginning to the end index
Array.Copy(
this.blocks[lastBlock], 0,
array, arrayIndex,
this.lastBlockEndIndex
);
}
} }
/// <summary>Obtains a new enumerator for the contents of the deque</summary> /// <summary>Obtains a new enumerator for the contents of the deque</summary>
@ -238,7 +282,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 lastBlockCount; private int lastBlockEndIndex;
} }