From 89626511d63be83d71fe8751827a934395470eaf Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Sun, 19 Nov 2023 20:26:57 +0100 Subject: [PATCH] RecyclableLongListCompatibilityList.ConvertAll & .CopyTo implemented. Tests added. --- ...ecyclableLongListCompatibilityListTests.cs | 33 ++++++++++++++ .../RecyclableLongList.Compatibility.List.cs | 45 ++++++++++++++++--- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs b/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs index 1b7307e..cb219e0 100644 --- a/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs +++ b/Recyclable.Collections.Compatibility.List.Tests/RecyclableLongListCompatibilityListTests.cs @@ -231,6 +231,39 @@ namespace Recyclable.CollectionsTests _ = actual.Should().Equal(expectedData); } + [Theory] + [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))] + public void CopyToShouldCopyAllItemsInTheCorrectOrderWhenConstrainedRange(string testCase, IEnumerable testData, int itemsCount, int minBlockSize, in long[] itemIndexes) + { + // Prepare + var expectedData = testData.ToList(); + foreach (var itemIndex in itemIndexes) + { + var rangedItemsCount = (int)Math.Max(itemIndex, 1); + TestCopyTo(0, rangedItemsCount); + + rangedItemsCount = (int)(itemsCount - itemIndex); + TestCopyTo((int)itemIndex, rangedItemsCount); + + rangedItemsCount = 1; + TestCopyTo((int)itemIndex, rangedItemsCount); + } + + void TestCopyTo(int itemIndex, int rangedItemsCount) + { + using var list = new RecyclableLongList(testData, minBlockSize, initialCapacity: itemsCount); + long[] expectedItems = new long[rangedItemsCount + itemIndex]; + expectedData.CopyTo(itemIndex, expectedItems, itemIndex, rangedItemsCount); + long[] actualItems = new long[rangedItemsCount + itemIndex]; + + // Act + list.CopyTo(itemIndex, actualItems, itemIndex, rangedItemsCount); + + // Validate + _ = actualItems.Should().Equal(expectedItems); + } + } + [Theory] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] public void CopyToShouldCopyAllItemsInTheCorrectOrderWhenFromStart(string testCase, IEnumerable testData, int itemsCount, int minBlockSize) diff --git a/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs b/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs index 8cab766..18d7548 100644 --- a/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs +++ b/Recyclable.Collections.Compatibility.List/RecyclableLongList.Compatibility.List.cs @@ -259,19 +259,50 @@ namespace Recyclable.Collections result._longCount = list._longCount; result._nextItemBlockIndex = list._nextItemBlockIndex; result._nextItemIndex = list._nextItemIndex; - result._lastBlockWithData = lastBlockWithData; + result._lastBlockWithData = list._lastBlockWithData; return result; } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static void CopyTo(this RecyclableLongList list, T[] array) - //=> Array.Copy(list._memoryBlock, array, list._count); - => throw new NotImplementedException(); + public static void CopyTo(this RecyclableLongList list, T[] array) => DoCopyTo(list._memoryBlocks, 0, list._blockSize, list._longCount, array, 0); + + private static void DoCopyTo(T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, Array destinationArray, int destinationArrayIndex) + { + if (itemsCount <= 0) + { + return; + } + + // TODO: Replace with bit-shifting + int startingItemIndex = (int)(startingIndex % blockSize); + int memoryBlockIndex = (int)(startingIndex / blockSize); + int lastItemIndex; + + ReadOnlySpan sourceMemoryBlocksSpan = new(sourceMemoryBlocks); + // We're using lastItemIndex as temp storage for copied count, so that we can move the destination index. That's to avoid additional var. + Array.Copy(sourceMemoryBlocks[memoryBlockIndex], startingItemIndex, destinationArray, destinationArrayIndex, lastItemIndex = (int)Math.Min(itemsCount, blockSize - startingItemIndex)); + memoryBlockIndex++; + destinationArrayIndex += lastItemIndex; + // TODO: Replace with bit-shifting + lastItemIndex = (int)((startingIndex + itemsCount) % blockSize); + int lastBlockIndex = (int)Math.Min((startingIndex + itemsCount) / blockSize, sourceMemoryBlocks.Length - 1); + + while(memoryBlockIndex < lastBlockIndex) + { + Array.Copy(sourceMemoryBlocksSpan[memoryBlockIndex], 0, destinationArray, destinationArrayIndex, blockSize); + destinationArrayIndex += blockSize; + memoryBlockIndex++; + } + + if (destinationArrayIndex < destinationArray.Length && memoryBlockIndex == lastBlockIndex) + { + Array.Copy(sourceMemoryBlocksSpan[lastBlockIndex], 0, destinationArray, destinationArrayIndex, lastItemIndex > 0 ? lastItemIndex : blockSize); + } + } [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] - public static void CopyTo(this RecyclableLongList list, int index, T[] array, int arrayIndex, int count) - //=> Array.Copy(list._memoryBlock, index, array, arrayIndex, count); - => throw new NotImplementedException(); + //public static void CopyTo(this RecyclableLongList list, int index, T[] array, int arrayIndex, int count) => RecyclableLongList.Helpers.CopyTo(list._memoryBlocks, index, list._blockSize, count, array, arrayIndex); + public static void CopyTo(this RecyclableLongList list, int index, T[] array, int arrayIndex, int count) => DoCopyTo(list._memoryBlocks, index, list._blockSize, count, array, arrayIndex); public static void ForEach(this RecyclableLongList list, Action action) {