From 5b961c73d114e370451c5eee1c0fa58b5f641416 Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sat, 10 Jun 2023 06:39:30 +0200 Subject: [PATCH] RecyclableLongList fixes to LastBlockWithData calculations. Tests extended. --- Recyclable.Collections/RecyclableLongList.cs | 28 +++++++++++++------ .../RecyclableLongListTests.cs | 18 ++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index 5b17dd3..14b7ffd 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -37,7 +37,7 @@ namespace Recyclable.Collections public int Count => checked((int)_longCount); public bool IsReadOnly { get; } - private int _lastBlockWithData; + private int _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; public int LastBlockWithData => _lastBlockWithData; internal long _longCount; @@ -50,6 +50,7 @@ namespace Recyclable.Collections public int ReservedBlockCount => _reservedBlockCount; public int BlockSize => _blockSize; + public int BlockSizeMinus1 => _blockSizeMinus1; public byte BlockSizePow2BitShift => _blockSizePow2BitShift; public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemIndex => _nextItemIndex; @@ -470,10 +471,15 @@ namespace Recyclable.Collections _memoryBlocks[_nextItemBlockIndex][_nextItemIndex++] = item; if (_nextItemIndex == _blockSize) { + if (_nextItemBlockIndex > _lastBlockWithData) + { + _lastBlockWithData++; + } + _nextItemBlockIndex++; _nextItemIndex = 0; } - else if (_nextItemIndex == 1) + else if (_nextItemIndex == 1 && _nextItemBlockIndex > _lastBlockWithData) { _lastBlockWithData++; } @@ -501,28 +507,32 @@ namespace Recyclable.Collections Span itemsSpan = new(items); Span memoryBlocksSpan = new(_memoryBlocks, 0, memoryBlockCount); Span targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], _nextItemIndex, blockSize - _nextItemIndex); - while (targetBlockArraySpan.Length <= itemsSpan.Length) + while (itemsSpan.Length >= targetBlockArraySpan.Length) { itemsSpan[..targetBlockArraySpan.Length].CopyTo(targetBlockArraySpan); itemsSpan = itemsSpan[targetBlockArraySpan.Length..]; - targetBlockIndex++; - if (targetBlockIndex == memoryBlockCount) + if (itemsSpan.Length == 0) { break; } + targetBlockIndex++; targetBlockArraySpan = new(memoryBlocksSpan[targetBlockIndex], 0, blockSize); } if (itemsSpan.Length > 0) { itemsSpan.CopyTo(targetBlockArraySpan); + _nextItemBlockIndex = targetBlockIndex; + } + else + { + _nextItemBlockIndex = targetBlockIndex + 1; } _nextItemIndex = itemsSpan.Length; _longCount = targetCapacity; - _nextItemBlockIndex = targetBlockIndex; - _lastBlockWithData = targetBlockIndex - (itemsSpan.Length > 0 ? 0 : 1); + _lastBlockWithData = targetBlockIndex; } public void AddRange(RecyclableList items) @@ -822,7 +832,7 @@ namespace Recyclable.Collections } Span memoryBlocksSpan = new(_memoryBlocks); - int lastBlockIndex = _nextItemBlockIndex; + int lastBlockIndex = _lastBlockWithData; for (int blockIndex = 0; blockIndex < lastBlockIndex; blockIndex++) { if (Array.IndexOf(memoryBlocksSpan[blockIndex], item, 0, _blockSize) >= 0) @@ -831,7 +841,7 @@ namespace Recyclable.Collections } } - return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; + return Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex > 0 ? _nextItemIndex : _blockSize) >= 0; } public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index 2fedc2c..3f2f53c 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -45,6 +45,7 @@ namespace Recyclable.CollectionsTests // Validate int expectedItemsCount = testData.Count(); _ = list.Capacity.Should().BeGreaterThanOrEqualTo(expectedItemsCount, "when capacity == 0, then we should allocate as much memory as needed, only"); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize != 0 ? 0 : 1)); _ = list.LongCount.Should().Be(expectedItemsCount); _ = list.Should().ContainInConsecutiveOrder(testData); } @@ -88,6 +89,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Capacity.Should().Be(0); + _ = list.LastBlockWithData.Should().Be(-1); _ = list.LongCount.Should().Be(0); _ = list.Should().BeEmpty(); } @@ -144,6 +146,7 @@ namespace Recyclable.CollectionsTests } _ = list.LongCount.Should().Be(expectedData.Length); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(expectedData); } @@ -163,6 +166,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData); } @@ -181,6 +185,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount, "we added so many items"); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount / list.BlockSize) - (itemsCount % list.BlockSize == 0 ? 1 : 0)); _ = list.Should().ContainInConsecutiveOrder(testData); } @@ -213,6 +218,7 @@ namespace Recyclable.CollectionsTests // Validate _ = list.LongCount.Should().Be(itemsCount * 2); + _ = list.LastBlockWithData.Should().Be((int)(itemsCount * 2 / list.BlockSize) - (itemsCount * 2 % list.BlockSize != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Concat(testData)); } @@ -572,11 +578,13 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); } _ = list.Should().BeEmpty(); _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } [Theory] @@ -594,11 +602,13 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Take((int)(itemsCount - deleted))); } _ = list.Should().BeEmpty(); _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } [Theory] @@ -616,8 +626,13 @@ namespace Recyclable.CollectionsTests // Validate _ = list.Count.Should().Be((int)(itemsCount - deleted)); + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.Should().ContainInConsecutiveOrder(testData.Skip(deleted)); } + + _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } [Theory] @@ -634,11 +649,14 @@ namespace Recyclable.CollectionsTests _ = list.Remove(item).Should().BeTrue($"we search for {item} which should exist"); removedCount++; var expectedList = expectedData[0..((int)itemsCount - removedCount)]; + _ = list.LastBlockWithData.Should().Be((int)(list.LongCount >> list.BlockSizePow2BitShift) - ((list.LongCount & list.BlockSizeMinus1) != 0 ? 0 : 1)); _ = list.LongCount.Should().Be(expectedList.Length); _ = list.Should().ContainInConsecutiveOrder(expectedList); } _ = list.Should().BeEmpty(); + _ = list.Capacity.Should().BeGreaterThanOrEqualTo((int)itemsCount); + _ = list.LastBlockWithData.Should().Be(RecyclableDefaults.ItemNotFoundIndex); } } } \ No newline at end of file