Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: replace CircularArray with ExposedQueue #51

Merged
merged 16 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions Aplib.Core/Belief/MemoryBelief.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,12 @@ public override void UpdateBelief()
/// <summary>
/// Gets the memorized observation at a specific index.
/// A higher index means a memory further back in time.
/// If the index is out of bounds, when clamped, returns the element closest to the index that is in bounds.
/// </summary>
/// <returns>The memory of the observation at the specified index.</returns>
public TObservation GetMemoryAt(int index, bool clamp = false)
public TObservation GetMemoryAt(int index)
{
int lastMemoryIndex = _memorizedObservations.Count;
if (clamp)
index = Math.Clamp(index, 0, lastMemoryIndex);
else if (index < 0 || index > lastMemoryIndex)
JensSteenmetz marked this conversation as resolved.
Show resolved Hide resolved
if (index < 0 || index > lastMemoryIndex)
throw new ArgumentOutOfRangeException(nameof(index), $"Index must be between 0 and {lastMemoryIndex}.");
return _memorizedObservations[index];
}
Expand Down
59 changes: 49 additions & 10 deletions Aplib.Core/ExposedQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ namespace Aplib.Core
/// <summary>
/// A queue with all elements exposed.
/// Functionally works like a queue with indexing.
/// It has a MaxCount and Count. MaxCount being the maximal length of the queue,
/// and Count being the actual number of elements in the queue.
/// </summary>
/// <remarks>
/// When adding an element to a full queue, all other elements are shifted one place like so:
/// [4, 3, 2, 1], Put(5) => [5, 4, 3, 2]
/// </remarks>
public class ExposedQueue<T> : ICollection<T>
{
/// <summary>
Expand All @@ -27,9 +33,9 @@ public class ExposedQueue<T> : ICollection<T>
private int _head;

/// <summary>
/// Initializes a new instance of the <see cref="ExposedQueue{T}"/> class.
/// Initializes a new empty instance of the <see cref="ExposedQueue{T}"/> class.
/// </summary>
/// <param name="size">The size of the array.</param>
/// <param name="size">The maximum size of the queue.</param>
public ExposedQueue(int size)
{
MaxCount = size;
JoenvandeVorle marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -39,11 +45,16 @@ public ExposedQueue(int size)
}

/// <summary>
/// Initializes a new instance of the <see cref="ExposedQueue{T}"/> class.
/// By default, assumes the array is filled.
/// Initializes a new instance of the <see cref="ExposedQueue{T}"/> class
/// with an array to use as basis for the queue.
/// By default, assumes the array is filled.
/// </summary>
/// <param name="array">An array to use as the circular array.</param>
/// <param name="count">The number of actual elements in the array.</param>
/// <remarks>
/// The MaxCount of the queue will be set to the length of the array.
/// If the array is not fully filled, the Count should be specified.
/// </remarks>
public ExposedQueue(T[] array, int? count = null)
{
if (count > array.Length)
Expand All @@ -62,6 +73,9 @@ public ExposedQueue(T[] array, int? count = null)
/// </summary>
/// <param name="index">The index of the element to get.</param>
/// <returns>The element at the specified index.</returns>
JoenvandeVorle marked this conversation as resolved.
Show resolved Hide resolved
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when the index is out of range.
/// </exception>
public T this[int index]
{
get
Expand All @@ -70,7 +84,7 @@ public T this[int index]
throw new ArgumentOutOfRangeException(nameof(index));
return _array[(index + _head + 1) % MaxCount];
}
private set
private set
JoenvandeVorle marked this conversation as resolved.
Show resolved Hide resolved
{
if (index < 0 || index >= Count)
throw new ArgumentOutOfRangeException(nameof(index));
Expand Down Expand Up @@ -172,15 +186,28 @@ public bool Contains(T item)
}

/// <summary>
/// DOES NOT DO ANYTHING.
/// Removes the specified item from the queue and shifts remaining elements to the left.
/// For example, given the queue [4, 3, 2, 1], if you call Remove(3), the resulting queue will be [4, 2, 1].
/// </summary>
/// <returns>False.</returns>
/// <param name="item">The item to remove.</param>
/// <returns>True if the item was successfully removed; otherwise, false.</returns>
/// <remarks>
/// Method is there to comply with the ICollection interface.
/// Does not actually do anything since this is a queue, and queues do not allow removal of specific elements.
/// The MaxCount will not change, but the Count will decrease by one.
/// </remarks>
public bool Remove(T item) => false;
public bool Remove(T item)
{
for (int i = 0; i < Count; i++)
{
if (this[i]!.Equals(item))
JensSteenmetz marked this conversation as resolved.
Show resolved Hide resolved
{
RemoveAt(i);
return true;
}
}

return false;
}

/// <inheritdoc/>
public IEnumerator<T> GetEnumerator()
{
Expand All @@ -194,5 +221,17 @@ public IEnumerator<T> GetEnumerator()
/// Decrements the head of the array.
/// </summary>
private void DecrementHead() => _head = (_head - 1 + MaxCount) % MaxCount;

/// <summary>
/// Removes the element at the specified index.
/// Shifts all other elements to the left.
/// </summary>
/// <param name="index">The index of the element to remove.</param>
private void RemoveAt(int index)
{
for (int i = index; i < Count - 1; i++)
this[i] = this[i + 1];
Count--;
}
}
}
152 changes: 110 additions & 42 deletions Aplib.Tests/Belief/ExposedQueueTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,66 +20,102 @@ public void ExposedQueue_WhenInitialized_ShouldHaveCorrectMaxCountAndCount()
Assert.Equal(0, count);
}

[Fact]
public void ExposedQueue_WhenInitializedWithArray_ShouldHaveCorrectMaxCountAndCount()
public static readonly object?[][] ArrayAndCountParams =
[
// array, count, expectedMaxCount, expectedCount
[new int[] {1, 2, 3}, null, 3, 3],
[new int[] {1, 2, 0, 0}, 2, 4, 2]
];

[Theory]
[MemberData(nameof(ArrayAndCountParams))]
public void ExposedQueue_WhenInitializedWithArray_ShouldHaveCorrectMaxCountAndCount(int[] array, int? countParam, int expectedMaxCount, int expectedCount)
{
// Arrange
ExposedQueue<int> queue = new([1, 2, 3]);
ExposedQueue<int> queue = new(array, countParam);

// Act
int maxCount = queue.MaxCount;
int count = queue.Count;

// Assert
Assert.Equal(3, maxCount);
Assert.Equal(3, count);
Assert.Equal(expectedMaxCount, maxCount);
Assert.Equal(expectedCount, count);
}

[Fact]
public void ExposedQueue_WhenInitializedWithArrayAndCount_ShouldThrowExceptions()
public void ExposedQueue_WhenInitializedWithArrayAndCount_ShouldSetHeadCorrectly()
{
// Arrange
void CountExceedsLength() => new ExposedQueue<int>([1, 2, 3], 4);
void NegativeCount() => new ExposedQueue<int>([1, 2, 3], -1);
ExposedQueue<int> queue = new([1,0,0], 1);
ExposedQueue<int> expected = new([3,2,1]);

// Act
queue.Put(2);
queue.Put(3);

// Assert
Assert.Equal(expected, queue);
}

[Fact]
public void ExposedQueue_WhenInitializedWithCountExceedingLength_ShouldThrowException()
{
// Arrange
void CountExceedsLength() => _ = new ExposedQueue<int>([1, 2, 3], 4);

// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(CountExceedsLength);
Assert.Throws<ArgumentOutOfRangeException>(NegativeCount);
}

[Fact]
public void AccessIndex_WhenIndexIsOutOfBounds_ThrowsException()
public void ExposedQueue_WhenInitializedWithNegativeCount_ShouldThrowException()
{
// Arrange
ExposedQueue<int> queue = new(3);
int elem;
void NegativeCount() => _ = new ExposedQueue<int>([1, 2, 3], -1);

// Act
void AccessNegativeIndex() => elem = queue[-1];
void AccessIndexGreaterThanCount() => elem = queue[queue.Count];
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(NegativeCount);
}

[Fact]
public void AccessIndex_WhenIndexIsNegative_ThrowsException()
{
// Arrange
void AccessNegativeIndex() => _ = new ExposedQueue<int>(3)[-1];

// Assert
// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(AccessNegativeIndex);
}

[Fact]
public void AccessIndex_WhenIndexIsGreaterThanCount_ThrowsException()
{
// Arrange
void AccessIndexGreaterThanCount() => _ = new ExposedQueue<int>(3)[4];

// Act & Assert
Assert.Throws<ArgumentOutOfRangeException>(AccessIndexGreaterThanCount);
}


[Fact]
public void Put_ArrayIsFull_WrapsAround()
{
// Arrange
ExposedQueue<int> queue = new([4, 3, 2, 1]);
ExposedQueue<int> expected = new([5, 4, 3, 2]);

// Act
queue.Put(5);

// Assert
Assert.Equal(5, queue[0]);
Assert.Equal(4, queue[1]);
Assert.Equal(2, queue[3]);
Assert.Equal(expected.Count, queue.Count);
Assert.Equal(expected, queue);
}

[Fact]
public void GetHead_HeadIsUpdated_ReturnsCorrectElement()
public void GetLast_HeadIsUpdated_ReturnsCorrectElement()
{
// Arrange
ExposedQueue<int> queue = new([3, 2, 1]);
Expand All @@ -105,22 +141,33 @@ public void GetFirst_HeadIsUpdated_ReturnsFirstElement()
}

[Fact]
public void CopyTo_WrongArguments_ThrowsException()
public void CopyTo_WhenStartIndexIsNegative_ThrowsException()
{
// Arrange
ExposedQueue<int> queue = new([3, 2, 1]);
ExposedQueue<int> queue = new([3,2,1]);

// Act
void StartOutOfBounds() => queue.CopyTo(new int[3], -1, 2);
void EndOutOfOunds() => queue.CopyTo(new int[3], 0, 3);
void StartAfterEnd() => queue.CopyTo(new int[3], 2, 1);
void TargetArrayTooSmall() => queue.CopyTo(new int[2], 0, 2);
// Assert
Assert.Throws<ArgumentOutOfRangeException>(() => queue.CopyTo(new int[3], -1, 2));
}

[Fact]
public void CopyTo_WhenEndIndexIsOutOfBounds_ThrowsException()
{
// Arrange
ExposedQueue<int> queue = new([3,2,1]);

// Assert
Assert.Throws<ArgumentOutOfRangeException>(StartOutOfBounds);
Assert.Throws<ArgumentOutOfRangeException>(EndOutOfOunds);
Assert.Throws<ArgumentException>(StartAfterEnd);
Assert.Throws<ArgumentException>(TargetArrayTooSmall);
Assert.Throws<ArgumentOutOfRangeException>(() => queue.CopyTo(new int[3], 0, 3));
}

[Fact]
public void CopyTo_WhenStartIndexIsAfterEndIndex_ThrowsException()
{
// Arrange
ExposedQueue<int> queue = new([3,2,1]);

// Assert
Assert.Throws<ArgumentException>(() => queue.CopyTo(new int[3], 2, 1));
}

[Fact]
Expand Down Expand Up @@ -177,43 +224,64 @@ public void Clear_QueueFilled_ClearsQueue()
Assert.Empty(queue);
}

[Fact]
public void Contains_ElementExists_ReturnsTrue()
[Theory]
[InlineData(2, true)]
[InlineData(4, false)]
public void Contains_OnElement_ReturnsCorrectAnswer(int element, bool expected)
{
// Arrange
ExposedQueue<int> queue = new([3, 2, 1]);

// Act
bool contains = queue.Contains(2);
bool contains = queue.Contains(element);

// Assert
Assert.True(contains);
Assert.Equal(expected, contains);
}

[Fact]
public void Contains_ElementDoesNotExist_ReturnsFalse()
public void Remove_WhenCalledOnMissingElement_ReturnsFalse()
{
// Arrange
ExposedQueue<int> queue = new([3, 2, 1]);

// Act
bool contains = queue.Contains(4);
bool removed = queue.Remove(4);

// Assert
Assert.False(contains);
Assert.False(removed);
}

[Fact]
public void Remove_WhenCalled_ReturnsFalse()
public void Remove_WhenCalledOnExistingElement_RemovesElement()
{
// Arrange
ExposedQueue<int> queue = new([3, 2, 1]);
ExposedQueue<int> queue = new([4, 3, 2, 1]);
ExposedQueue<int> expected = new([4, 2, 1]);

// Act
bool removed = queue.Remove(4);
bool removed = queue.Remove(3);

// Assert
Assert.False(removed);
Assert.True(removed);
Assert.Equal(expected.Count, queue.Count);
Assert.Equal(expected, queue);
}

[Fact]
public void Put_AfterRemoval_StillHasCorrectHead()
{
// Arrange
ExposedQueue<int> queue = new([4, 3, 1, 2]);
ExposedQueue<int> expected = new([5, 4, 3, 2]);

// Act
queue.Remove(1);
queue.Put(5);

// Assert
Assert.Equal(expected.Count, queue.Count);
Assert.Equal(expected, queue);
}

[Fact]
Expand Down
Loading