Fixed handling of empty lists in RecyclableArrayList.

Some RecyclableList properties made public to allow use in RecyclableArrayList.

Added support for RecyclableList in RecyclableArrayList.AddRange.

Additional tests added for RecyclableArrayList.
This commit is contained in:
Marek Lemańczyk committed 2023-03-19 20:37:18 +01:00
1 parent e312accfe5
commit e6224ebe44
3 files changed
+209 -123

No files matched your search

+75 -3
View File
@@ -11,9 +11,9 @@ namespace Recyclable.Collections
protected T[] _memoryBlock;
private static void ThrowArgumentOutOfRangeException()
private static void ThrowArgumentOutOfRangeException(in string message)
{
throw new ArgumentOutOfRangeException("index");
throw new ArgumentOutOfRangeException(message, (Exception?)null);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -162,6 +162,11 @@ namespace Recyclable.Collections
public void AddRange(in T[] items)
{
if (items.LongLength == 0)
{
return;
}
int sourceItemsCount = items.Length;
int targetCapacity = _count + sourceItemsCount;
if (_capacity < targetCapacity)
@@ -177,6 +182,11 @@ namespace Recyclable.Collections
public void AddRange(List<T> items)
{
if (items.Count == 0)
{
return;
}
var sourceItemsCount = items.Count;
var targetCapacity = _count + sourceItemsCount;
if (_capacity < targetCapacity)
@@ -190,6 +200,11 @@ namespace Recyclable.Collections
public void AddRange(IList<T> items)
{
if (items.Count == 0)
{
return;
}
var sourceItemsCount = items.Count;
var targetCapacity = _count + sourceItemsCount;
if (_capacity < targetCapacity)
@@ -203,6 +218,11 @@ namespace Recyclable.Collections
public void AddRange(RecyclableArrayList<T> items)
{
if (items.Count == 0)
{
return;
}
var sourceItemsCount = items._count;
var targetCapacity = _count + sourceItemsCount;
if (_capacity < targetCapacity)
@@ -216,6 +236,58 @@ namespace Recyclable.Collections
_count = targetCapacity;
}
public void AddRange(RecyclableList<T> items)
{
if (items.LongCount == 0)
{
return;
}
var sourceItemsCount = items.LongCount;
if (sourceItemsCount > int.MaxValue)
{
ThrowArgumentOutOfRangeException($"The number of items exceeds the maximum capacity of {nameof(RecyclableArrayList<T>)}, equal {int.MaxValue}, equal {int.MaxValue}. Please consider using {nameof(RecyclableList<T>)}, instead");
}
if (_count + sourceItemsCount > int.MaxValue)
{
ThrowArgumentOutOfRangeException($"The total number of items in source and target table exceeds the maximum capacity of {nameof(RecyclableArrayList<T>)}, equal {int.MaxValue}. Please consider using {nameof(RecyclableList<T>)}, instead");
}
int targetCapacity = _count + (int)sourceItemsCount;
if (_capacity < targetCapacity)
{
_ = EnsureCapacity(targetCapacity);
}
Span<T> targetSpan = new Span<T>(_memoryBlock)[_count..],
itemsSpan;
int blockIndex,
sourceBlockSize = items.BlockSize,
lastBlockIndex = (int)(sourceItemsCount / sourceBlockSize) - 1;
for (blockIndex = 0; blockIndex <= lastBlockIndex; blockIndex++)
{
itemsSpan = new(items.MemoryBlocks[blockIndex], 0, sourceBlockSize);
itemsSpan.CopyTo(targetSpan);
targetSpan = targetSpan[sourceBlockSize..];
}
if (blockIndex == 0)
{
itemsSpan = new(items.MemoryBlocks[blockIndex], 0, (int)sourceItemsCount);
itemsSpan.CopyTo(targetSpan);
}
else if (blockIndex < items.BlockCount)
{
itemsSpan = new(items.MemoryBlocks[blockIndex], 0, items.NextItemIndex);
itemsSpan.CopyTo(targetSpan);
}
_count = targetCapacity;
}
public void AddRange(IEnumerable<T> source, int growByCount = RecyclableDefaults.MinPooledArrayLength)
{
if (source is T[] sourceArray)
@@ -375,7 +447,7 @@ namespace Recyclable.Collections
if (index >= oldCount)
{
ThrowArgumentOutOfRangeException();
ThrowArgumentOutOfRangeException($"Argument \"{index}\" is out of range. The current version supports removing the last element, only ");
return;
}
+7 -3
View File
@@ -22,6 +22,9 @@ namespace Recyclable.Collections
protected ArrayPool<T> _blockArrayPool;
protected T[][] _memoryBlocks;
public int BlockSize => _blockSize;
public int BlockCount => _memoryBlocks.Length;
private long _capacity;
public long Capacity
{
@@ -29,7 +32,8 @@ namespace Recyclable.Collections
protected set => _capacity = value;
}
public int Count => (int)_longCount;
public int Count => checked((int)_longCount);
public bool IsReadOnly { get; }
protected long _longCount;
@@ -39,8 +43,8 @@ namespace Recyclable.Collections
set => _longCount = value;
}
public bool IsReadOnly { get; }
public int BlockCount => _memoryBlocks.Length;
public int LastBlockIndex => _lastBlockIndex;
public int NextItemIndex => _nextItemIndex;
private static void RemoveAt(RecyclableList<T> list, long index, ArrayPool<T> blockArrayPool)
{
@@ -7,75 +7,163 @@ namespace Recyclable.CollectionsTests
{
private readonly static IEnumerable<int> _testData = Enumerable.Range(1, 20);
[Fact]
public void AddCountShouldSucceed()
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void AddCountShouldSucceed(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
var testData = _testData.ToArray();
var list = new RecyclableArrayList<int>();
var list = new RecyclableArrayList<long>();
// Act
foreach (var item in testData)
{
list.Add(item);
_ = list.Count.Should().Be(item);
_ = list.Count.Should().Be((int)item);
}
// Validate
_ = list.Capacity.Should().Be(32);
_ = list.Should().HaveCount(testData.Length)
.And.ContainInConsecutiveOrder(testData)
.And.BeEquivalentTo(testData);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount);
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Should().ContainInConsecutiveOrder(testData);
}
[Fact]
public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsArray()
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
var testData = _testData.ToArray();
var list = new RecyclableArrayList<int>();
using var list = new RecyclableArrayList<long>();
// Act
list.AddRange(testData);
if (testData is long[] testDataArray)
{
list.AddRange(testDataArray);
}
else if (testData is List<long> testDataList)
{
list.AddRange(testDataList);
}
else if (testData is RecyclableArrayList<long> testDataRecyclableArrayList)
{
list.AddRange(testDataRecyclableArrayList);
}
else if (testData is RecyclableList<long> testDataRecyclableList)
{
list.AddRange(testDataRecyclableList);
}
else if (testData is IList<long> testDataIList)
{
list.AddRange(testDataIList);
}
else if (testData is IEnumerable<long> testDataIEnumerable)
{
list.AddRange(testDataIEnumerable);
}
else
{
throw new InvalidCastException("Unknown type of test data");
}
// Validate
_ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only");
_ = list.Should().HaveCount(testData.Length)
.And.ContainInConsecutiveOrder(testData)
.And.BeEquivalentTo(testData);
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when capacity == 0, then we should allocate as much memory as needed, only");
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Should().ContainInConsecutiveOrder(testData);
}
[Fact]
public void AddRangeShouldAddItemsInCorrectOrderWhenSourceIsList()
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void AddShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
var testData = _testData.ToList();
var list = new RecyclableArrayList<int>();
// Act
list.AddRange(testData);
using var list = new RecyclableArrayList<long>((int)itemsCount * 2);
foreach (var index in testData)
{
list.Add(index);
}
foreach (var index in testData)
{
list.Add(index);
}
// Validate
_ = list.Capacity.Should().Be(20, "when capacity == 0, then we allocate as much memory as needed, only");
_ = list.Should().HaveCount(testData.Count)
.And.ContainInConsecutiveOrder(testData)
.And.BeEquivalentTo(testData);
_ = list.Count.Should().Be((int)itemsCount * 2);
_ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData));
}
[Fact]
public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable()
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void ClearShouldRemoveAllItems(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
var testData = _testData;
var list = new RecyclableArrayList<long>(testData);
_ = list.Count.Should().Be((int)itemsCount);
// Act
list.Clear();
// Validate
_ = list.Count.Should().Be(0);
_ = list.Should().BeEmpty();
}
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void ConstructorShouldAcceptDuplicates(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Act
var list = new RecyclableArrayList<int>(testData);
using var list = new RecyclableArrayList<long>(testData.Concat(testData), initialCapacity: (int)itemsCount * 2);
// Validate
_ = list.Capacity.Should().Be(32, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only");
_ = list.Should().HaveCount(testData.Count())
.And.ContainInConsecutiveOrder(testData)
.And.BeEquivalentTo(testData);
_ = list.Count.Should().Be((int)itemsCount * 2);
_ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData));
}
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void ConstructorShouldAddItemsInCorrectOrderWhenSourceIsIEnumerable(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Act
var list = new RecyclableArrayList<long>(testData);
// Validate
_ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount, "when we succeed in taking count without enumerating, then we allocate as much memory as needed, only");
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Should().ContainInConsecutiveOrder(testData);
}
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void ConstructorSourceShouldInitializeList(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Act
var list = new RecyclableArrayList<long>(testData);
// Validate
_ = list.Count.Should().Be((int)itemsCount);
_ = list.Should().ContainInConsecutiveOrder(testData);
}
[Theory]
[MemberData(nameof(RecyclableListTestData.SourceDataVariants), MemberType = typeof(RecyclableListTestData))]
public void ContainsShouldFindAllItems(string testCase, IEnumerable<long> testData, long itemsCount)
{
if (itemsCount == 0)
{
return;
}
// Prepare
using var list = new RecyclableArrayList<long>(testData);
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act
foreach (var item in testData)
{
// Validate
_ = list.Contains(item).Should().BeTrue();
}
}
[Fact]
@@ -203,21 +291,6 @@ namespace Recyclable.CollectionsTests
_ = list.Capacity.Should().Be(20);
}
[Fact]
public void ConstructorSourceShouldInitializeList()
{
// Prepare
var testData = _testData.ToArray();
// Act
var list = new RecyclableArrayList<int>(testData);
// Validate
_ = list.Should().HaveCount(testData.Length)
.And.ContainInConsecutiveOrder(testData)
.And.BeEquivalentTo(testData);
}
[Fact]
public void EnumerateShouldYieldAllItemsInCorrectOrder()
{
@@ -254,22 +327,6 @@ namespace Recyclable.CollectionsTests
.And.ContainInConsecutiveOrder(testData)
.And.BeEquivalentTo(testData);
}
[Fact]
public void ClearShouldRemoveAllItems()
{
// Prepare
var testData = _testData.ToArray();
var list = new RecyclableArrayList<int>(testData);
_ = list.Count.Should().NotBe(0);
// Act
list.Clear();
// Validate
_ = list.Count.Should().Be(0);
_ = list.Should().HaveCount(0);
}
[Fact]
public void IndexOfShouldFindTheIndexes()
@@ -286,22 +343,6 @@ namespace Recyclable.CollectionsTests
}
}
[Fact]
public void ContainsShouldFindAllItems()
{
// Prepare
var testData = _testData.ToArray();
using var list = new RecyclableArrayList<int>(testData);
_ = testData.Any().Should().BeTrue("we need items on the list that we can look for");
// Act
foreach (var item in testData)
{
// Validate
_ = list.Contains(item).Should().BeTrue();
}
}
[Fact]
public void ContainsShouldNotFindNonExistingItems()
{
@@ -333,36 +374,5 @@ namespace Recyclable.CollectionsTests
_ = list.Contains(1).Should().BeTrue();
}
[Fact]
public void AddShouldAcceptDuplicates()
{
// Prepare
int[] testNumbers = new[] { 1, 2, 2, 3 };
// Act
using var list = new RecyclableArrayList<int>();
foreach (var index in testNumbers)
{
list.Add(index);
}
// Validate
_ = list.Count.Should().Be(testNumbers.Length);
_ = list.Should().BeEquivalentTo(testNumbers);
}
[Fact]
public void ConstructorShouldAcceptDuplicates()
{
// Prepare
int[] testNumbers = new[] { 1, 2, 2, 3 };
// Act
using var list = new RecyclableArrayList<int>(testNumbers);
// Validate
_ = list.Count.Should().Be(testNumbers.Length);
_ = list.Should().BeEquivalentTo(testNumbers);
}
}
}
}
}