diff --git a/README.md b/README.md index 09e1570..0dd7340 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,17 @@ 1. 🅿️ `Remove` 1. 🅿️ `RemoveAt` 1. 🅿️ `RemoveBlock` + 1. 🅿️ Add `.ToRecyclableList` / `.ToRecyclableLongList` variants for all supported collection types + 1. 🅿️ `RecyclableList` + 1. 🅿️ `RecyclableLongList` + 1. 🅿️ `IList` + 1. 🅿️ `ICollection` + 1. 🅿️ `IEnumerable` + 1. 🅿️ `IEnumerable` + 1. 🅿️ `IReadOnlyList` + 1. 🅿️ `IReadOnlyCollection` + 1. 🅿️ `IList` + 1. 🅿️ `ICollection` 1. 🅿️ Release 0.0.3-beta 1. 🅿️ Add support for `ulong` indexing 1. 🅿️ Convert `_memoryBlocks` to `Array` to allow `ulong` lengths @@ -155,8 +166,8 @@ 1. 🅿️ `RecyclableLongListExtensions.CopyTo` 1. 🅿️ Cleanup 1. 🅿️ Replace `LastBlockWithData` property with `_lastBlockWithData` field - 1. 🅿️ Cleanup `RecyclableLongListExtensions` - 1. 🅿️ `ListExtensions` + 1. ✅ Cleanup `RecyclableLongListExtensions` + 1. ✅ `ListExtensions` 1. 🅿️ `MathUtils` 1. 🅿️ Optimize 1. 🅿️ `ListExtensions` diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs index 3f0146e..c3b32d8 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.EnsureCapacity.cs @@ -775,9 +775,9 @@ namespace Recyclable.Collections.Benchmarks.POC return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 @@ -847,7 +847,7 @@ namespace Recyclable.Collections.Benchmarks.POC DoRemoveAt(index); } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongListV1() { @@ -1675,9 +1675,9 @@ namespace Recyclable.Collections.Benchmarks.POC return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 @@ -1816,7 +1816,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongListV2() { @@ -2638,9 +2638,9 @@ namespace Recyclable.Collections.Benchmarks.POC return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 @@ -2779,7 +2779,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongListV3() { diff --git a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs index 9fb3289..06ab5dd 100644 --- a/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs +++ b/Recyclable.Collections.Benchmarks/POC/RecyclableLongListPoc.Benchmarks.IndexOf.cs @@ -1068,9 +1068,9 @@ namespace Recyclable.Collections.Benchmarks.POC return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 @@ -1217,7 +1217,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongListIndexOfV1() { @@ -2213,9 +2213,9 @@ namespace Recyclable.Collections.Benchmarks.POC return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 @@ -2362,7 +2362,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongListIndexOfV2() { @@ -3330,9 +3330,9 @@ namespace Recyclable.Collections.Benchmarks.POC return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 @@ -3476,7 +3476,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongListIndexOfV3() { @@ -4484,9 +4484,9 @@ namespace Recyclable.Collections.Benchmarks.POC return lastBlockIndex < memoryBlocksSpan.Length && Array.IndexOf(memoryBlocksSpan[lastBlockIndex], item, 0, _nextItemIndex) >= 0; } - public void CopyTo(T[] array, int arrayIndex) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, arrayIndex); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining)] public int IndexOf(T item) => _longCount == 0 @@ -4630,7 +4630,7 @@ namespace Recyclable.Collections.Benchmarks.POC } } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongListIndexOfV4() { diff --git a/Recyclable.Collections/ListExtensions.cs b/Recyclable.Collections/ListExtensions.cs deleted file mode 100644 index 866a598..0000000 --- a/Recyclable.Collections/ListExtensions.cs +++ /dev/null @@ -1,211 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace Recyclable.Collections -{ - // The source code was taken from https://codereview.stackexchange.com/questions/205407/quicksort-without-recursion. - // It was only beautified for .Net 6.0+. - internal static class ListExtensions - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool Contains(this RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void CopyTo(this RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) - { - Span arrayMemory = destinationArray.AsSpan(); - arrayMemory = arrayMemory[destinationArrayIndex..]; - int startingArrayIndex = (int)(startingIndex / blockSize); - startingIndex %= blockSize; - if (arrays.Count > 0) - { - ReadOnlySpan sourceMemory = arrays[0].AsSpan(); - var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); - if (arrays.Count == 1) - { - maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); - } - - sourceMemory = sourceMemory[(int)startingIndex..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - arrayMemory = arrayMemory[maxToCopy..]; - } - - for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++) - { - ReadOnlySpan sourceMemory = arrays[arrayIdx].AsSpan(); - var maxToCopy = Math.Min(blockSize, arrayMemory.Length); - sourceMemory = sourceMemory[..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - arrayMemory = arrayMemory[maxToCopy..]; - } - - if (arrays.Count > 1 && arrayMemory.Length > 0) - { - ReadOnlySpan sourceMemory = arrays[^1].AsSpan(); - var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length); - sourceMemory = sourceMemory[..maxToCopy]; - sourceMemory.CopyTo(arrayMemory); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static IEnumerable Enumerate(this RecyclableList arrays, int chunkSize, long totalCount) - { - long currentCount = 0; - for (var arrayIdx = 0; (arrayIdx < arrays.Count) && (currentCount < totalCount); arrayIdx++) - { - var array = arrays[arrayIdx]; - currentCount += Math.Min(chunkSize, totalCount); - switch (currentCount < totalCount) - { - case true: - for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - - case false: - var partialCount = (int)(totalCount % chunkSize); - int maxCount = partialCount > 0 ? partialCount : chunkSize; - for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - } - } - } - - public static void InsertionSort(this IList values, int startIndex, int endIndex, IComparer comparer) - { - var left = startIndex; - while (left < endIndex) - { - int right = left; - T? temp = values[++left]; - while (right >= startIndex && 0 < comparer.Compare(values[right], temp)) - { - values[right + 1] = values[right--]; - } - - values[right + 1] = temp; - } - } - public static void InsertionSort(this IList values, IComparer comparer) - { - values.InsertionSort(0, values.Count - 1, comparer); - } - public static void InsertionSort(this IList values) - { - values.InsertionSort(Comparer.Default); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) - { - for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) - { - var array = arrays[arrayIdx]; - Span arrayMemory = array.AsSpan(); - for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++) - { - if (comparer.Equals(arrayMemory[memoryIdx], item)) - { - return (arrayIdx * (long)blockSize) + memoryIdx; - } - } - } - - return -1; - } - - public static void QuickSort(this IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) - { - var range = (startIndex, endIndex); - var stack = new Stack<(int, int)>(); - - do - { - startIndex = range.startIndex; - endIndex = range.endIndex; - - if (endIndex - startIndex + 1 < 31) - { - values.InsertionSort(startIndex, endIndex, comparer); - - continue; - } - - var pivot = values.SampleMedian(startIndex, endIndex, comparer, randomNumberGenerator); - var left = startIndex; - var right = endIndex; - - while (left <= right) - { - while (0 > comparer.Compare(values[left], pivot)) - { - left++; - } - - while (0 > comparer.Compare(pivot, values[right])) - { - right--; - } - - if (left <= right) - { - values.Swap(left++, right--); - } - } - - if (startIndex < right) - { - stack.Push((startIndex, right)); - } - - if (left < endIndex) - { - stack.Push((left, endIndex)); - } - } - while (stack.TryPop(out range)); - } - public static void QuickSort(this IList values, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) - => values.QuickSort(0, values.Count - 1, comparer, randomNumberGenerator); - - public static void QuickSort(this IList values) - => values.QuickSort(Comparer.Default, new SystemRandomNumberGenerator()); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T SampleMedian(this IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) - { - var left = randomNumberGenerator.NextInt32(startIndex, endIndex); - var middle = randomNumberGenerator.NextInt32(startIndex, endIndex); - var right = randomNumberGenerator.NextInt32(startIndex, endIndex); - - if (0 > comparer.Compare(values[right], values[left])) - { - values.Swap(right, left); - } - - if (0 > comparer.Compare(values[middle], values[left])) - { - values.Swap(middle, left); - } - - if (0 > comparer.Compare(values[right], values[middle])) - { - values.Swap(right, middle); - } - - return values[middle]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void Swap(this IList values, int xIndex, int yIndex) - => (values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]); - } -} diff --git a/Recyclable.Collections/RecyclableList.Extensions.cs b/Recyclable.Collections/RecyclableList.Extensions.cs new file mode 100644 index 0000000..494e941 --- /dev/null +++ b/Recyclable.Collections/RecyclableList.Extensions.cs @@ -0,0 +1,19 @@ +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + public static class RecyclableListExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this IEnumerable values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this T[] values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this List values) => new(values); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableList ToRecyclableList(this RecyclableList values) => new(values); + } +} diff --git a/Recyclable.Collections/RecyclableList.Helpers.cs b/Recyclable.Collections/RecyclableList.Helpers.cs new file mode 100644 index 0000000..121e122 --- /dev/null +++ b/Recyclable.Collections/RecyclableList.Helpers.cs @@ -0,0 +1,216 @@ +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + public partial class RecyclableList + { + internal static class RecyclableListHelpers + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool Contains(RecyclableList arrays, T item) => arrays.Any(x => x.Contains(item)); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void CopyTo(RecyclableList arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex) + { + Span arrayMemory = destinationArray.AsSpan(); + arrayMemory = arrayMemory[destinationArrayIndex..]; + int startingArrayIndex = (int)(startingIndex / blockSize); + startingIndex %= blockSize; + if (arrays.Count > 0) + { + ReadOnlySpan sourceMemory = arrays[0].AsSpan(); + var maxToCopy = (int)Math.Min(blockSize - startingIndex, arrayMemory.Length); + if (arrays.Count == 1) + { + maxToCopy = (int)Math.Min(maxToCopy, lastBlockSize); + } + + sourceMemory = sourceMemory[(int)startingIndex..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + arrayMemory = arrayMemory[maxToCopy..]; + } + + for (var arrayIdx = startingArrayIndex + 1; arrayIdx < arrays.Count - 1 && arrayMemory.Length > 0; arrayIdx++) + { + ReadOnlySpan sourceMemory = arrays[arrayIdx].AsSpan(); + var maxToCopy = Math.Min(blockSize, arrayMemory.Length); + sourceMemory = sourceMemory[..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + arrayMemory = arrayMemory[maxToCopy..]; + } + + if (arrays.Count > 1 && arrayMemory.Length > 0) + { + ReadOnlySpan sourceMemory = arrays[^1].AsSpan(); + var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length); + sourceMemory = sourceMemory[..maxToCopy]; + sourceMemory.CopyTo(arrayMemory); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static IEnumerable Enumerate(RecyclableList arrays, int chunkSize, long totalCount) + { + long currentCount = 0; + for (var arrayIdx = 0; (arrayIdx < arrays.Count) && (currentCount < totalCount); arrayIdx++) + { + var array = arrays[arrayIdx]; + currentCount += Math.Min(chunkSize, totalCount); + switch (currentCount < totalCount) + { + case true: + for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + + case false: + var partialCount = (int)(totalCount % chunkSize); + int maxCount = partialCount > 0 ? partialCount : chunkSize; + for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + } + } + } + + public static void InsertionSort(IList values, int startIndex, int endIndex, IComparer comparer) + { + var left = startIndex; + while (left < endIndex) + { + int right = left; + T? temp = values[++left]; + while (right >= startIndex && 0 < comparer.Compare(values[right], temp)) + { + values[right + 1] = values[right--]; + } + + values[right + 1] = temp; + } + } + + public static void InsertionSort(IList values, IComparer comparer) + { + InsertionSort(values, 0, values.Count - 1, comparer); + } + + public static void InsertionSort(IList values) + { + InsertionSort(values, Comparer.Default); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static long LongIndexOf(RecyclableList arrays, int blockSize, T item, IEqualityComparer comparer) + { + for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++) + { + var array = arrays[arrayIdx]; + Span arrayMemory = array.AsSpan(); + for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++) + { + if (comparer.Equals(arrayMemory[memoryIdx], item)) + { + return (arrayIdx * (long)blockSize) + memoryIdx; + } + } + } + + return -1; + } + + // The source code was taken from https://codereview.stackexchange.com/questions/205407/quicksort-without-recursion. + // It was only beautified for .Net 6.0+. + public static void QuickSort(IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) + { + var range = (startIndex, endIndex); + var stack = new Stack<(int, int)>(); + + do + { + startIndex = range.startIndex; + endIndex = range.endIndex; + + if (endIndex - startIndex + 1 < 31) + { + InsertionSort(values, startIndex, endIndex, comparer); + + continue; + } + + var pivot = SampleMedian(values, startIndex, endIndex, comparer, randomNumberGenerator); + var left = startIndex; + var right = endIndex; + + while (left <= right) + { + while (0 > comparer.Compare(values[left], pivot)) + { + left++; + } + + while (0 > comparer.Compare(pivot, values[right])) + { + right--; + } + + if (left <= right) + { + Swap(values, left++, right--); + } + } + + if (startIndex < right) + { + stack.Push((startIndex, right)); + } + + if (left < endIndex) + { + stack.Push((left, endIndex)); + } + } + while (stack.TryPop(out range)); + } + public static void QuickSort(IList values, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) + => QuickSort(values, 0, values.Count - 1, comparer, randomNumberGenerator); + + public static void QuickSort(IList values) + => QuickSort(values, Comparer.Default, new SystemRandomNumberGenerator()); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T SampleMedian(IList values, int startIndex, int endIndex, IComparer comparer, IRandomNumberGenerator randomNumberGenerator) + { + var left = randomNumberGenerator.NextInt32(startIndex, endIndex); + var middle = randomNumberGenerator.NextInt32(startIndex, endIndex); + var right = randomNumberGenerator.NextInt32(startIndex, endIndex); + + if (0 > comparer.Compare(values[right], values[left])) + { + Swap(values, right, left); + } + + if (0 > comparer.Compare(values[middle], values[left])) + { + Swap(values, middle, left); + } + + if (0 > comparer.Compare(values[right], values[middle])) + { + Swap(values, right, middle); + } + + return values[middle]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void Swap(IList values, int xIndex, int yIndex) + => (values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]); + } + } +} diff --git a/Recyclable.Collections/RecyclableList.IList.cs b/Recyclable.Collections/RecyclableList.IList.cs new file mode 100644 index 0000000..158421a --- /dev/null +++ b/Recyclable.Collections/RecyclableList.IList.cs @@ -0,0 +1,118 @@ +using System.Collections; + +namespace Recyclable.Collections +{ + public partial class RecyclableList : IList + { + public bool IsFixedSize => false; + public bool IsSynchronized => false; + + private object? _syncRoot; + public object SyncRoot + { + get + { + if (_syncRoot == null) + { + _ = Interlocked.CompareExchange(ref _syncRoot, new object(), null); + } + + return _syncRoot; + } + } + + object? IList.this[int index] + { + get => _memoryBlock[index]; + + set + { + if (value is T typedValue) + { + new Span(_memoryBlock)[index] = typedValue; + } + else if (value == null && _defaultIsNull) + { +#nullable disable + new Span(_memoryBlock)[index] = default; +#nullable restore + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + } + + public int Add(object? value) + { + if (value is T typeValue) + { + Add(typeValue); + return _count - 1; + } + else if (value == null && _defaultIsNull) + { + Add(null); + return _count - 1; + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + return RecyclableDefaults.ItemNotFoundIndex; + } + } + + public bool Contains(object? value) => value is T typeValue + ? Contains(typeValue) +#nullable disable + : value == null && _defaultIsNull && Contains(default); +#nullable restore + + public int IndexOf(object? value) => value is T typeValue + ? IndexOf(typeValue) + : value == null && _defaultIsNull +#nullable disable + ? IndexOf(default) +#nullable restore + : RecyclableDefaults.ItemNotFoundIndex; + + public void Insert(int index, object? value) + { + if (value is T typeValue) + { + Insert(index, typeValue); + } + else if (value == null && _defaultIsNull) + { +#nullable disable + Insert(index, default); +#nullable restore + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + + public void Remove(object? value) + { + if (value is T typeValue) + { + _ = Remove(typeValue); + } + else if (value == null && _defaultIsNull) + { +#nullable disable + _ = Remove(default); +#nullable restore + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + + public void CopyTo(Array array, int index) => Array.Copy(_memoryBlock, 0, array, index, _count); + } +} diff --git a/Recyclable.Collections/RecyclableList.cs b/Recyclable.Collections/RecyclableList.cs index 95ac2b6..4f0366f 100644 --- a/Recyclable.Collections/RecyclableList.cs +++ b/Recyclable.Collections/RecyclableList.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Recyclable.Collections { - public class RecyclableList : IList, IReadOnlyList, IList, IDisposable + public partial class RecyclableList : IList, IReadOnlyList, IDisposable { private static readonly ArrayPool _arrayPool = ArrayPool.Create(); private static readonly bool _defaultIsNull = default(T) == null; @@ -517,124 +517,13 @@ namespace Recyclable.Collections { Clear(); _capacity = 0; - _memoryBlock.ReturnToPool(_arrayPool, NeedsClearing); + if (_count is >= RecyclableDefaults.MinPooledArrayLength) + { + _arrayPool.Return(_memoryBlock, NeedsClearing); + } + GC.SuppressFinalize(this); } } - - #region IList implementation - - public bool IsFixedSize => false; - public bool IsSynchronized => false; - - private object? _syncRoot; - public object SyncRoot - { - get - { - if (_syncRoot == null) - { - _ = Interlocked.CompareExchange(ref _syncRoot, new object(), null); - } - - return _syncRoot; - } - } - - object? IList.this[int index] - { - get => _memoryBlock[index]; - - set - { - if (value is T typedValue) - { - new Span(_memoryBlock)[index] = typedValue; - } - else if (value == null && _defaultIsNull) - { -#nullable disable - new Span(_memoryBlock)[index] = default; -#nullable restore - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); - } - } - } - - public int Add(object? value) - { - if (value is T typeValue) - { - Add(typeValue); - return _count - 1; - } - else if (value == null && _defaultIsNull) - { - Add(null); - return _count - 1; - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); - return RecyclableDefaults.ItemNotFoundIndex; - } - } - - public bool Contains(object? value) => value is T typeValue - ? Contains(typeValue) -#nullable disable - : value == null && _defaultIsNull && Contains(default); -#nullable restore - - public int IndexOf(object? value) => value is T typeValue - ? IndexOf(typeValue) - : value == null && _defaultIsNull -#nullable disable - ? IndexOf(default) -#nullable restore - : RecyclableDefaults.ItemNotFoundIndex; - - public void Insert(int index, object? value) - { - if (value is T typeValue) - { - Insert(index, typeValue); - } - else if (value == null && _defaultIsNull) - { -#nullable disable - Insert(index, default); -#nullable restore - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); - } - } - - public void Remove(object? value) - { - if (value is T typeValue) - { - _ = Remove( typeValue); - } - else if (value == null && _defaultIsNull) - { -#nullable disable - _ = Remove(default); -#nullable restore - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); - } - } - - public void CopyTo(Array array, int index) => Array.Copy(_memoryBlock, 0, array, index, _count); - - #endregion } } diff --git a/Recyclable.Collections/RecyclableLongList.Extensions.cs b/Recyclable.Collections/RecyclableLongList.Extensions.cs new file mode 100644 index 0000000..c899f5a --- /dev/null +++ b/Recyclable.Collections/RecyclableLongList.Extensions.cs @@ -0,0 +1,16 @@ +using System.Runtime.CompilerServices; + +namespace Recyclable.Collections +{ + public static class RecyclableLongListExtensions + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static RecyclableLongList ToRecyclableLongList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); + } +} diff --git a/Recyclable.Collections/RecyclableLongList.Helpers.cs b/Recyclable.Collections/RecyclableLongList.Helpers.cs new file mode 100644 index 0000000..b1713c1 --- /dev/null +++ b/Recyclable.Collections/RecyclableLongList.Helpers.cs @@ -0,0 +1,90 @@ +namespace Recyclable.Collections +{ + public partial class RecyclableLongList + { + internal class RecyclableLongListHelpers + { + public static void CopyTo(T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex) + { + if (itemsCount <= 0) + { + return; + } + + Span sourceBlockMemory; + Span destinationArrayMemory; + int startingBlockIndex = (int)(startingIndex / blockSize); + int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1; + Span sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); + destinationArrayMemory = new Span(destinationArray, destinationArrayIndex, (int)Math.Min(destinationArray.Length - destinationArrayIndex, itemsCount)); + int memoryBlockIndex; + for (memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) + { + sourceBlockMemory = new(sourceMemoryBlocksSpan[memoryBlockIndex], 0, blockSize); + sourceBlockMemory.CopyTo(destinationArrayMemory); + destinationArrayMemory = destinationArrayMemory[blockSize..]; + } + + if (itemsCount - (lastBlockIndex * blockSize) > 0) + { + sourceBlockMemory = new(sourceMemoryBlocksSpan[lastBlockIndex], 0, (int)(itemsCount - (lastBlockIndex * blockSize))); + sourceBlockMemory.CopyTo(destinationArrayMemory); + } + } + + public static void CopyTo(T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, Array destinationArray, int destinationArrayIndex) + { + if (itemsCount <= 0) + { + return; + } + + int startingBlockIndex = (int)(startingIndex / blockSize); + int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1; + + var destinationIndex = destinationArrayIndex; + Span sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); + for (int memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) + { + Array.ConstrainedCopy(sourceMemoryBlocksSpan[memoryBlockIndex], 0, destinationArray, destinationIndex, blockSize); + destinationIndex += blockSize; + } + + if (itemsCount - destinationIndex > 0) + { + Array.ConstrainedCopy(sourceMemoryBlocksSpan[lastBlockIndex], 0, destinationArray, destinationIndex, (int)(itemsCount - destinationIndex)); + } + } + + public static IEnumerable Enumerate(T[][] arrays, int chunkSize, long totalCount) + { + long currentCount = 0; + for (var arrayIdx = 0; arrayIdx < arrays.Length && currentCount < totalCount; arrayIdx++) + { + var array = arrays[arrayIdx]; + currentCount += chunkSize; + switch (currentCount < totalCount) + { + case true: + for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + + case false: + var partialCount = (int)(totalCount % chunkSize); + int maxCount = partialCount > 0 ? partialCount : chunkSize; + for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) + { + yield return array[valueIdx]; + } + + break; + } + } + } + } + } +} diff --git a/Recyclable.Collections/RecyclableLongList.IList.cs b/Recyclable.Collections/RecyclableLongList.IList.cs new file mode 100644 index 0000000..1e7d22e --- /dev/null +++ b/Recyclable.Collections/RecyclableLongList.IList.cs @@ -0,0 +1,118 @@ +using System.Collections; + +namespace Recyclable.Collections +{ + public partial class RecyclableLongList : IList + { + public bool IsFixedSize => false; + public bool IsSynchronized => false; + + private object? _syncRoot; + public object SyncRoot + { + get + { + if (_syncRoot == null) + { + _ = Interlocked.CompareExchange(ref _syncRoot, new object(), null); + } + + return _syncRoot; + } + } + + object? IList.this[int index] + { + get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; + + set + { + if (value is T typedValue) + { + new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = typedValue; + } + else if (value == null && _defaultIsNull) + { +#nullable disable + new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = default; +#nullable restore + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + } + + public int Add(object? value) + { + if (value is T typeValue) + { + Add(typeValue); + return (int)(_longCount - 1); + } + else if (value == null && _defaultIsNull) + { + Add(null); + return (int)(_longCount - 1); + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + return RecyclableDefaults.ItemNotFoundIndex; + } + } + + public bool Contains(object? value) => value is T typeValue + ? Contains(typeValue) +#nullable disable + : value == null && _defaultIsNull && Contains(default); +#nullable restore + + public int IndexOf(object? value) => value is T typeValue + ? IndexOf(typeValue) + : value == null && _defaultIsNull +#nullable disable + ? IndexOf(default) +#nullable restore + : RecyclableDefaults.ItemNotFoundIndex; + + public void Insert(int index, object? value) + { + if (value is T typeValue) + { + Insert(index, typeValue); + } + else if (value == null && _defaultIsNull) + { +#nullable disable + Insert(index, default); +#nullable restore + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + + public void Remove(object? value) + { + if (value is T typeValue) + { + _ = Remove(typeValue); + } + else if (value == null && _defaultIsNull) + { +#nullable disable + _ = Remove(default); +#nullable restore + } + else + { + ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); + } + } + + public void CopyTo(Array array, int index) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, index); + } +} diff --git a/Recyclable.Collections/RecyclableLongList.cs b/Recyclable.Collections/RecyclableLongList.cs index d5edaba..489fe61 100644 --- a/Recyclable.Collections/RecyclableLongList.cs +++ b/Recyclable.Collections/RecyclableLongList.cs @@ -9,7 +9,7 @@ using Recyclable.Collections.Pools; namespace Recyclable.Collections { - public partial class RecyclableLongList : IList, IReadOnlyList, IList, IDisposable + public partial class RecyclableLongList : IList, IReadOnlyList, IDisposable { private static readonly ArrayPool _defaultMemoryBlocksPool = ArrayPool.Create(); private static readonly ArrayPool _defaultBlockArrayPool = ArrayPool.Create(); @@ -1073,9 +1073,9 @@ namespace Recyclable.Collections 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); + public void CopyTo(T[] array, int arrayIndex) => RecyclableLongList.RecyclableLongListHelpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex); - public IEnumerator GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + public IEnumerator GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public int IndexOf(T item) @@ -1296,7 +1296,7 @@ namespace Recyclable.Collections } } - IEnumerator IEnumerable.GetEnumerator() => _memoryBlocks.Enumerate(_blockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableLongList.RecyclableLongListHelpers.Enumerate(_memoryBlocks, _blockSize, LongCount).GetEnumerator(); ~RecyclableLongList() { @@ -1318,120 +1318,5 @@ namespace Recyclable.Collections GC.SuppressFinalize(this); } } - - #region IList implementation - - public bool IsFixedSize => false; - public bool IsSynchronized => false; - - private object? _syncRoot; - public object SyncRoot - { - get - { - if (_syncRoot == null) - { - _ = Interlocked.CompareExchange(ref _syncRoot, new object(), null); - } - - return _syncRoot; - } - } - - object? IList.this[int index] - { - get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; - - set - { - if (value is T typedValue) - { - new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = typedValue; - } - else if (value == null && _defaultIsNull) - { -#nullable disable - new Span(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = default; -#nullable restore - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); - } - } - } - - public int Add(object? value) - { - if (value is T typeValue) - { - Add(typeValue); - return (int)(_longCount - 1); - } - else if (value == null && _defaultIsNull) - { - Add(null); - return (int)(_longCount - 1); - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. { (value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.") }"); - return RecyclableDefaults.ItemNotFoundIndex; - } - } - - public bool Contains(object? value) => value is T typeValue - ? Contains(typeValue) -#nullable disable - : value == null && _defaultIsNull && Contains(default); -#nullable restore - - public int IndexOf(object? value) => value is T typeValue - ? IndexOf(typeValue) - : value == null && _defaultIsNull -#nullable disable - ? IndexOf(default) -#nullable restore - : RecyclableDefaults.ItemNotFoundIndex; - - public void Insert(int index, object? value) - { - if (value is T typeValue) - { - Insert(index, typeValue); - } - else if (value == null && _defaultIsNull) - { -#nullable disable - Insert(index, default); -#nullable restore - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); - } - } - - public void Remove(object? value) - { - if (value is T typeValue) - { - _ = Remove(typeValue); - } - else if (value == null && _defaultIsNull) - { -#nullable disable - _ = Remove(default); -#nullable restore - } - else - { - ThrowArgumentOutOfRangeException(nameof(value), $"Argument is invalid. {(value != null ? "List element type is incompatible with the given value" : "Element is null and the list doesn't allow null values.")}"); - } - } - - public void CopyTo(Array array, int index) => _memoryBlocks.CopyTo(0, _blockSize, _longCount, array, index); - - #endregion } } diff --git a/Recyclable.Collections/RecyclableLongListExtensions.cs b/Recyclable.Collections/RecyclableLongListExtensions.cs deleted file mode 100644 index da6abdc..0000000 --- a/Recyclable.Collections/RecyclableLongListExtensions.cs +++ /dev/null @@ -1,200 +0,0 @@ -using System.Buffers; -using System.Runtime.CompilerServices; - -namespace Recyclable.Collections -{ - internal static class RecyclableLongListExtensions - { - private const int _minPooledArraySize = RecyclableDefaults.MinPooledArrayLength; - - public static void CopyTo(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, T[] destinationArray, int destinationArrayIndex) - { - if (itemsCount <= 0) - { - return; - } - - Span sourceBlockMemory; - Span destinationArrayMemory; - int startingBlockIndex = (int)(startingIndex / blockSize); - int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1; - Span sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); - destinationArrayMemory = new Span(destinationArray, destinationArrayIndex, (int)Math.Min(destinationArray.Length - destinationArrayIndex, itemsCount)); - int memoryBlockIndex; - for (memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) - { - sourceBlockMemory = new(sourceMemoryBlocksSpan[memoryBlockIndex], 0, blockSize); - sourceBlockMemory.CopyTo(destinationArrayMemory); - destinationArrayMemory = destinationArrayMemory[blockSize..]; - } - - if (itemsCount - (lastBlockIndex * blockSize) > 0) - { - sourceBlockMemory = new(sourceMemoryBlocksSpan[lastBlockIndex], 0, (int)(itemsCount - (lastBlockIndex * blockSize))); - sourceBlockMemory.CopyTo(destinationArrayMemory); - } - } - - public static void CopyTo(this T[][] sourceMemoryBlocks, long startingIndex, int blockSize, long itemsCount, Array destinationArray, int destinationArrayIndex) - { - if (itemsCount <= 0) - { - return; - } - - int startingBlockIndex = (int)(startingIndex / blockSize); - int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize > 0 ? 1 : 0)) - 1; - - var destinationIndex = destinationArrayIndex; - Span sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); - for (int memoryBlockIndex = 0; memoryBlockIndex < lastBlockIndex; memoryBlockIndex++) - { - Array.ConstrainedCopy(sourceMemoryBlocksSpan[memoryBlockIndex], 0, destinationArray, destinationIndex, blockSize); - destinationIndex += blockSize; - } - - if (itemsCount - destinationIndex > 0) - { - Array.ConstrainedCopy(sourceMemoryBlocksSpan[lastBlockIndex], 0, destinationArray, destinationIndex, (int)(itemsCount - destinationIndex)); - } - } - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static bool Contains(this RecyclableLongList arrays, T item) => arrays.Any(x => x.Contains(item)); - - public static IEnumerable Enumerate(this T[][] arrays, int chunkSize, long totalCount) - { - long currentCount = 0; - for (var arrayIdx = 0; arrayIdx < arrays.Length && currentCount < totalCount; arrayIdx++) - { - var array = arrays[arrayIdx]; - currentCount += chunkSize; - switch (currentCount < totalCount) - { - case true: - for (var valueIdx = 0; valueIdx < chunkSize; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - - case false: - var partialCount = (int)(totalCount % chunkSize); - int maxCount = partialCount > 0 ? partialCount : chunkSize; - for (var valueIdx = 0; valueIdx < maxCount; valueIdx++) - { - yield return array[valueIdx]; - } - - break; - } - } - } - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - //public static T LimitTo(this T value, T maxValue, IComparer comparer) - // => comparer.Compare(value, maxValue) > 0 ? maxValue : value; - - //[MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LongIndexOf(this RecyclableLongList arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) - { - for (var itemIdx = 0L; itemIdx < itemsCount; itemIdx++) - { - var item = arrays[itemIdx]; - if (comparer.Equals(itemToFind, item)) - { - return itemIdx; - } - - itemIdx++; - } - - return -1; - } - - public static long LongIndexOf(this T[][] arrays, long itemsCount, T itemToFind, IEqualityComparer comparer) - { - var arraysSpan = new Span(arrays); - var arraysCount = arraysSpan.Length; - for (int arrayIdx = 0, scannedCount = 0; arrayIdx < arraysCount; arrayIdx++) - { - var itemsSpan = new Span(arraysSpan[arrayIdx]); - var arrayItemsCount = itemsSpan.Length; - for (var itemIdx = 0; itemIdx < arrayItemsCount; itemIdx++) - { - if (comparer.Equals(itemToFind, itemsSpan[itemIdx])) - { - return scannedCount; - } - - scannedCount++; - if (scannedCount == itemsCount) - { - return -1; - } - } - } - - return -1; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableLongList ToRecyclableLongList(this IList values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableLongList ToRecyclableLongList(this List values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this IEnumerable values) => new(values); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this T[] values) => new(values); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this List values) => new(values); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableList ToRecyclableList(this RecyclableList values) => new(values); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RecyclableLongList ToRecyclableLongList(this IEnumerable values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize: minBlockSize); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this int minSize, ArrayPool arrayPool) => (minSize >= _minPooledArraySize) - ? arrayPool.Rent(minSize) - : new T[minSize]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static T[] RentArrayFromPool(this long minSize, ArrayPool arrayPool) => (minSize is >= _minPooledArraySize and <= int.MaxValue) - ? arrayPool.Rent((int)minSize) - : new T[minSize]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnToPool(this T[] array, ArrayPool arrayPool, bool needsClearing) - { - if (array.LongLength is > _minPooledArraySize and <= int.MaxValue) - { - arrayPool.Return(array, needsClearing); - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static long LimitTo(this long value, int limit) - => (value <= limit) ? value : limit; - - public static int IndexOf(this T[] memory, int itemCount, T? itemToFind, IEqualityComparer equalityComparer) - { - for (var itemIdx = 0; itemIdx < itemCount; itemIdx++) - { - var item = memory[itemIdx]; - if (equalityComparer.Equals(item, itemToFind)) - { - return itemIdx; - } - } - - return -1; - } - } -} diff --git a/Recyclable.Collections/RecyclableQueue.cs b/Recyclable.Collections/RecyclableQueue.cs index 070ebcd..b5c8653 100644 --- a/Recyclable.Collections/RecyclableQueue.cs +++ b/Recyclable.Collections/RecyclableQueue.cs @@ -133,7 +133,10 @@ namespace Recyclable.Collections public void AddBlock() { - T[] newArray = BlockSize.RentArrayFromPool(_arrayPool); + T[] newArray = (BlockSize >= RecyclableDefaults.MinPooledArrayLength) + ? _arrayPool.Rent(BlockSize) + : new T[BlockSize]; + Memory.Add(newArray); Capacity += BlockSize; } @@ -168,13 +171,13 @@ namespace Recyclable.Collections } } - public bool Contains(T item) => Memory.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => Memory.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex); - public IEnumerable Enumerate() => Memory.Enumerate(BlockSize, LongCount); - public IEnumerator GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => Memory.Enumerate(BlockSize, LongCount).GetEnumerator(); - public int IndexOf(T item) => (int)LongIndexOf(item); - public long LongIndexOf(T item) => GetRelativeIndex(Memory.LongIndexOf(BlockSize, item, _equalityComparer)); + public bool Contains(T item) => RecyclableList.RecyclableListHelpers.Contains(Memory, item); + public void CopyTo(T[] array, int arrayIndex) => RecyclableList.RecyclableListHelpers.CopyTo(Memory, RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex); + public IEnumerable Enumerate() => RecyclableList.RecyclableListHelpers.Enumerate(Memory, BlockSize, LongCount); + public IEnumerator GetEnumerator() => RecyclableList.RecyclableListHelpers.Enumerate(Memory, BlockSize, LongCount).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => RecyclableList.RecyclableListHelpers.Enumerate(Memory, BlockSize, LongCount).GetEnumerator(); + public int IndexOf(T item) => (int)GetRelativeIndex(RecyclableList.RecyclableListHelpers.LongIndexOf(Memory, BlockSize, item, _equalityComparer)); + public long LongIndexOf(T item) => GetRelativeIndex(RecyclableList.RecyclableListHelpers.LongIndexOf(Memory, BlockSize, item, _equalityComparer)); public void Insert(int index, T item) => throw new NotSupportedException(); public void RemoveAt(int index) => throw new NotSupportedException(); public bool TryDequeue(out T? item) => TryDequeue(this, out item); @@ -183,7 +186,11 @@ namespace Recyclable.Collections { try { - Memory[index].ReturnToPool(_arrayPool, NeedsClearing); + if (Memory[index].LongLength is > RecyclableDefaults.MinPooledArrayLength and <= int.MaxValue) + { + _arrayPool.Return(Memory[index], NeedsClearing); + } + Memory.RemoveAt(index); } finally diff --git a/Recyclable.Collections/RecyclableSortedList.cs b/Recyclable.Collections/RecyclableSortedList.cs index d1d9ede..3dfdc20 100644 --- a/Recyclable.Collections/RecyclableSortedList.cs +++ b/Recyclable.Collections/RecyclableSortedList.cs @@ -68,7 +68,7 @@ BeginUpdate(); try { - this.QuickSort(_comparer, _randomNumberGenerator); + RecyclableList<(TKey Key, TValue Value)>.RecyclableListHelpers.QuickSort(this, _comparer, _randomNumberGenerator); } finally { diff --git a/Recyclable.CollectionsTests/RecyclableListTests.cs b/Recyclable.CollectionsTests/RecyclableListTests.cs index fb77c5a..b5a4dba 100644 --- a/Recyclable.CollectionsTests/RecyclableListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableListTests.cs @@ -419,7 +419,7 @@ namespace Recyclable.CollectionsTests // Act using RecyclableList list = new(testData); - list.QuickSort(); + RecyclableList.RecyclableListHelpers.QuickSort(list); // Validate _ = list.Count.Should().Be((int)itemsCount); diff --git a/Recyclable.CollectionsTests/RecyclableLongListTests.cs b/Recyclable.CollectionsTests/RecyclableLongListTests.cs index 3f2f53c..e2d610b 100644 --- a/Recyclable.CollectionsTests/RecyclableLongListTests.cs +++ b/Recyclable.CollectionsTests/RecyclableLongListTests.cs @@ -461,7 +461,7 @@ namespace Recyclable.CollectionsTests // Act using RecyclableLongList list = new(testData, minBlockSize: targetBlockSize); - list.QuickSort(); + RecyclableList.RecyclableListHelpers.QuickSort(list); // Validate _ = list.LongCount.Should().Be(itemsCount);