Performance & standardization improvements.
Business logic fixes. RecyclableQueue & RecyclableStack added - pending improvements.
This commit is contained in:
1 parent
4e5835b20e
commit
aba28028b1
17 files changed
+1269
-190
No files matched your search
@@ -0,0 +1,253 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using MiscUtil;
|
||||
using MiscUtil.Extensions;
|
||||
|
||||
namespace Recyclable.Collections.Benchmarks
|
||||
{
|
||||
public delegate T Compare<T>(T x, T y);
|
||||
|
||||
public readonly struct MyComparerReadOnlyStruct<T> : IComparer<T>
|
||||
{
|
||||
public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
public struct MyComparerStruct<T> : IComparer<T>
|
||||
{
|
||||
public int Compare(T? x, T? y) => Operator.Subtract(x, y) switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
public class MyComparer : IComparer<long>
|
||||
{
|
||||
public int Compare(long x, long y) => (x - y) switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0,
|
||||
};
|
||||
}
|
||||
|
||||
[MemoryDiagnoser]
|
||||
public class DelegateVsComparerBenchmark
|
||||
{
|
||||
private static readonly Comparer<long> _comparer = Comparer<long>.Default;
|
||||
|
||||
private const int _objectCount = 1_000_000;
|
||||
private static readonly IEnumerable<long> _testObjects = Enumerable.Range(1, _objectCount).Select(x => (long)x);
|
||||
private static readonly IEnumerable<Func<long, long, int>> _testListOfFunctions = _testObjects.Select<long, Func<long, long, int>>(static x => new MyComparer().Compare);
|
||||
private static readonly IEnumerable<IComparer<long>> _testListOfComparers = _testObjects.Select(x => (IComparer<long>)new MyComparer());
|
||||
private static readonly IEnumerable<Compare<long>> _testListOfDelegates = _testObjects.Select(x => new Compare<long>((x, y) => new MyComparer().Compare(x, y)));
|
||||
private static readonly IEnumerable<Func<long, long, int>> _testListOfLocalFunctions = GetLocalFunctions(_testObjects);
|
||||
private static readonly IEnumerable<MyComparer> _testListOfClasses = _testObjects.Select(x => new MyComparer());
|
||||
private static readonly IEnumerable<MyComparerStruct<long>> _testListOfStructs = _testObjects.Select(x => new MyComparerStruct<long>());
|
||||
private static readonly IEnumerable<IComparer<long>> _testListOfBoxedStructs = _testObjects.Select(x => (IComparer<long>)new MyComparerStruct<long>());
|
||||
private static IEnumerable<TComparer> _testListOfUnboxedStructs<TComparer, TValue>() where TComparer : IComparer<TValue>, new()
|
||||
{
|
||||
foreach (var item in _testObjects)
|
||||
{
|
||||
yield return new TComparer();
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Func<long, long, int>> GetLocalFunctions(IEnumerable<long> testObjects)
|
||||
{
|
||||
static int compareFunc(long x, long y)
|
||||
{
|
||||
return (x - y) switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0
|
||||
};
|
||||
}
|
||||
|
||||
foreach (var item in testObjects)
|
||||
{
|
||||
yield return compareFunc;
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestWithComparer(IComparer<long> comparer)
|
||||
{
|
||||
var testData = _testObjects.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++)
|
||||
{
|
||||
_ = comparer.Compare(testData[itemIdx], testData[itemIdx + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void Comparer()
|
||||
{
|
||||
TestWithComparer(_comparer);
|
||||
}
|
||||
|
||||
private static void TestWithFunc(Func<long, long, int> compareFunc)
|
||||
{
|
||||
var testData = _testObjects.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++)
|
||||
{
|
||||
_ = compareFunc(testData[itemIdx], testData[itemIdx + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Func()
|
||||
{
|
||||
TestWithFunc((x, y) => (x - y) switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void StaticFunc()
|
||||
{
|
||||
TestWithFunc(static (x, y) => (x - y) switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0
|
||||
});
|
||||
}
|
||||
|
||||
private static void TestWithDelegate<T>(Compare<long> compareFunc)
|
||||
{
|
||||
var testData = _testObjects.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length - 1; itemIdx++)
|
||||
{
|
||||
_ = compareFunc(testData[itemIdx], testData[itemIdx + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Delegate()
|
||||
{
|
||||
TestWithDelegate<long>((x, y) => (x - y) switch
|
||||
{
|
||||
> 0 => 1,
|
||||
< 0 => -1,
|
||||
_ => 0
|
||||
});
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfFuncs()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfFunctions.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var compareFunc = testData[itemIdx];
|
||||
_ = compareFunc(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfLocalFuncs()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfLocalFunctions.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var compareFunc = testData[itemIdx];
|
||||
_ = compareFunc(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfComparers()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfComparers.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var comparer = testData[itemIdx];
|
||||
_ = comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfClasses()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfClasses.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var comparer = testData[itemIdx];
|
||||
_ = comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfStructs()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfStructs.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var comparer = testData[itemIdx];
|
||||
_ = comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfBoxedStructs()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfBoxedStructs.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var comparer = testData[itemIdx];
|
||||
_ = comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfUnboxedStructs()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfUnboxedStructs<MyComparerStruct<long>, long>().ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var comparer = testData[itemIdx];
|
||||
_ = comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfUnboxedReadOnlyStructs()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfUnboxedStructs<MyComparerReadOnlyStruct<long>, long>().ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var comparer = testData[itemIdx];
|
||||
_ = comparer.Compare(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void ListOfDelegates()
|
||||
{
|
||||
const long x = 2, y = 5;
|
||||
var testData = _testListOfDelegates.ToArray();
|
||||
for (var itemIdx = 0; itemIdx < testData.Length; itemIdx++)
|
||||
{
|
||||
var compareDelegate = testData[itemIdx];
|
||||
_ = compareDelegate(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,223 @@
|
||||
using BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Recyclable.Collections.Benchmarks
|
||||
{
|
||||
[MemoryDiagnoser]
|
||||
public class ListVsLinkedListBenchmark
|
||||
{
|
||||
private const int _objectCount = 1_000_000;
|
||||
private const int _blockSize = 100_000;
|
||||
private static readonly object[] _testObjects = Enumerable.Range(1, _objectCount).Select(x => new object()).ToArray();
|
||||
|
||||
private static void DoNothing<T>(T obj)
|
||||
{
|
||||
_ = obj;
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Array()
|
||||
{
|
||||
var list = new object[_testObjects.LongLength];
|
||||
for (var i = 0; i < _testObjects.Length; i++)
|
||||
{
|
||||
list[i] = _testObjects[i];
|
||||
}
|
||||
|
||||
for (var i = 0; i < list.LongLength; i++)
|
||||
{
|
||||
DoNothing(list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void RecyclableList()
|
||||
{
|
||||
using var list = new RecyclableList<object>(_blockSize);
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Add(_testObjects[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
DoNothing(_testObjects[i]);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void List()
|
||||
{
|
||||
var list = new List<object>();
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Add(_testObjects[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
DoNothing(list[i]);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void LinkedList()
|
||||
{
|
||||
var list = new LinkedList<object>();
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
_ = list.AddLast(_testObjects[i]);
|
||||
}
|
||||
|
||||
foreach (var node in list)
|
||||
{
|
||||
DoNothing(node);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void RecyclableQueue()
|
||||
{
|
||||
var list = new RecyclableQueue<object>(_blockSize);
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Enqueue(_testObjects[i]);
|
||||
}
|
||||
|
||||
foreach (var node in list)
|
||||
{
|
||||
DoNothing(node);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void Queue()
|
||||
{
|
||||
var list = new Queue<object>();
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Enqueue(_testObjects[i]);
|
||||
}
|
||||
|
||||
foreach (var node in list)
|
||||
{
|
||||
DoNothing(node);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void PriorityQueue()
|
||||
{
|
||||
var list = new PriorityQueue<object, long>();
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Enqueue(_testObjects[i], i);
|
||||
}
|
||||
|
||||
while (list.Count > 0)
|
||||
{
|
||||
var node = list.Dequeue();
|
||||
DoNothing(node);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void RecyclableSortedList_InUpdateMode()
|
||||
{
|
||||
var list = new RecyclableSortedList<long, object>(_blockSize);
|
||||
list.BeginUpdate();
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Add(i, _testObjects[i]);
|
||||
}
|
||||
|
||||
list.EndUpdate();
|
||||
|
||||
for (var i = 0; i < list.LongCount; i++)
|
||||
{
|
||||
DoNothing(_testObjects[i]);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void RecyclableSortedList()
|
||||
{
|
||||
var list = new RecyclableSortedList<long, object>(_blockSize);
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Add(i, _testObjects[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < list.LongCount; i++)
|
||||
{
|
||||
DoNothing(_testObjects[i]);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void SortedList()
|
||||
{
|
||||
var list = new SortedList<long, object>();
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Add(i, _testObjects[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < list.Count; i++)
|
||||
{
|
||||
DoNothing(_testObjects[i]);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void RecyclableStack()
|
||||
{
|
||||
var list = new RecyclableStack<object>(_blockSize);
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Push(_testObjects[i]);
|
||||
}
|
||||
|
||||
foreach (var node in list)
|
||||
{
|
||||
DoNothing(node);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
//[Benchmark]
|
||||
public void Stack()
|
||||
{
|
||||
var list = new Stack<object>();
|
||||
for (var i = 0; i < _testObjects.LongLength; i++)
|
||||
{
|
||||
list.Push(_testObjects[i]);
|
||||
}
|
||||
|
||||
foreach (var node in list)
|
||||
{
|
||||
DoNothing(node);
|
||||
}
|
||||
|
||||
list.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using BenchmarkDotNet.Running;
|
||||
using Recyclable.Collections.Benchmarks;
|
||||
|
||||
BenchmarkRunner.Run<ListVsLinkedListBenchmark>();
|
||||
//BenchmarkRunner.Run<DelegateVsComparerBenchmark>();
|
||||
|
||||
//var tests = new ListVsLinkedListBenchmark();
|
||||
//tests.RecyclableList();
|
||||
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.3" />
|
||||
<PackageReference Include="JonSkeet.MiscUtil" Version="0.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Recyclable.Collections\Recyclable.Collections.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.Collections", "R
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Recyclable.CollectionsTests", "Recyclable.CollectionsTests\Recyclable.CollectionsTests.csproj", "{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Recyclable.Collections.Benchmarks", "Recyclable.Collections.Benchmarks\Recyclable.Collections.Benchmarks.csproj", "{CDC0CF78-FC11-43E3-9059-3261DA3639D1}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -21,6 +23,10 @@ Global
|
||||
{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2B7223FA-2D5B-40BF-9BD9-9D6B05DC18E2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CDC0CF78-FC11-43E3-9059-3261DA3639D1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public interface IRecyclableOwner<T> : IList<T>
|
||||
{
|
||||
T this[long index] { get; set; }
|
||||
int BlockSize { get; }
|
||||
long Capacity { get; set; }
|
||||
long LongCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -81,13 +81,10 @@ namespace Recyclable.Collections
|
||||
while (stack.TryPop(out range));
|
||||
}
|
||||
public static void QuickSort<T>(this IList<T> values, IComparer<T> comparer, IRandomNumberGenerator randomNumberGenerator)
|
||||
{
|
||||
values.QuickSort(0, values.Count - 1, comparer, randomNumberGenerator);
|
||||
}
|
||||
=> values.QuickSort(0, values.Count - 1, comparer, randomNumberGenerator);
|
||||
|
||||
public static void QuickSort<T>(this IList<T> values)
|
||||
{
|
||||
values.QuickSort(Comparer<T>.Default, new SystemRandomNumberGenerator());
|
||||
}
|
||||
=> values.QuickSort(Comparer<T>.Default, new SystemRandomNumberGenerator());
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static T SampleMedian<T>(this IList<T> values, int startIndex, int endIndex, IComparer<T> comparer, IRandomNumberGenerator randomNumberGenerator)
|
||||
@@ -116,8 +113,6 @@ namespace Recyclable.Collections
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Swap<T>(this IList<T> values, int xIndex, int yIndex)
|
||||
{
|
||||
(values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]);
|
||||
}
|
||||
=> (values[yIndex], values[xIndex]) = (values[xIndex], values[yIndex]);
|
||||
}
|
||||
}
|
||||
@@ -1,132 +1,81 @@
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public class RecyclableList<T> : IDisposable, IList<T>
|
||||
{
|
||||
private static readonly IEqualityComparer<T> _comparer = EqualityComparer<T>.Default;
|
||||
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
||||
|
||||
private readonly int _blockSize;
|
||||
private readonly List<T[]> _arrays = new();
|
||||
private bool _disposedValue;
|
||||
private long _capacity;
|
||||
protected uint _isUpdating;
|
||||
|
||||
private static IEnumerable<T> EnumerateElements(List<T[]> arrays, int chunkSize, long totalCount)
|
||||
{
|
||||
long currentCount = 0;
|
||||
for (var arrayIdx = 0; arrayIdx < arrays.Count; 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static T[] RentArray(int minSize) => ArrayPool<T>.Shared.Rent(minSize);
|
||||
private static void ReturnArray(in T[] array) => ArrayPool<T>.Shared.Return(array);
|
||||
|
||||
public RecyclableList(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
BlockSize = blockSize;
|
||||
}
|
||||
|
||||
public RecyclableList(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
BeginUpdate();
|
||||
try
|
||||
BlockSize = blockSize;
|
||||
foreach (var item in source)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndUpdate();
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
|
||||
public int BlockSize { get; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetItem(List<T[]> arrays, in int blockSize, in long index, in T value)
|
||||
=> arrays[(int)(index / blockSize)][index % blockSize] = value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T GetItem(List<T[]> arrays, in int blockSize, in long index)
|
||||
=> arrays[(int)(index / blockSize)][index % blockSize];
|
||||
|
||||
public T this[long index]
|
||||
{
|
||||
get => _arrays[(int)(index / _blockSize)][index % _blockSize];
|
||||
|
||||
set
|
||||
{
|
||||
_arrays[(int)(index / _blockSize)][index % _blockSize] = value;
|
||||
if (!IsUpdating)
|
||||
{
|
||||
ListUpdated();
|
||||
}
|
||||
}
|
||||
get => GetItem(_arrays, BlockSize, index);
|
||||
set => SetItem(_arrays, BlockSize, index, value);
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => this[(long)index];
|
||||
set => this[(long)index] = value;
|
||||
get => GetItem(_arrays, BlockSize, index);
|
||||
set => SetItem(_arrays, BlockSize, index, value);
|
||||
}
|
||||
|
||||
public int Count => LongCount <= int.MaxValue ? (int)LongCount : int.MaxValue;
|
||||
public long LongCount { get; protected set; }
|
||||
public long Capacity { get; set; }
|
||||
public int Count => (int)LongCount;
|
||||
public long LongCount { get; set; }
|
||||
public bool IsReadOnly { get; } = false;
|
||||
public bool IsUpdating => _isUpdating > 0;
|
||||
public int BlockCount => _arrays.Count;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(T item)
|
||||
{
|
||||
if (LongCount == _capacity)
|
||||
if (LongCount == Capacity)
|
||||
{
|
||||
var newArray = RentArray(_blockSize);
|
||||
T[] newArray = BlockSize.RentArrayFromPool<T>();
|
||||
_arrays.Add(newArray);
|
||||
_capacity += _blockSize;
|
||||
Capacity += BlockSize;
|
||||
}
|
||||
|
||||
var newIndex = LongCount;
|
||||
// We don't want this[newIndex] set to raise the update event, because
|
||||
// the count would be wrong. We need to increase it, first.
|
||||
BeginUpdate();
|
||||
try
|
||||
{
|
||||
this[newIndex] = item;
|
||||
LongCount++;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndUpdate();
|
||||
}
|
||||
this[LongCount] = item;
|
||||
LongCount++;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
BeginUpdate();
|
||||
try
|
||||
{
|
||||
for (var arrayIdx = 0; arrayIdx < _arrays.Count; arrayIdx++)
|
||||
// Remove in reversed order for performance savings
|
||||
while (_arrays.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReturnArray(_arrays[arrayIdx]);
|
||||
RemoveBlock(_arrays.Count - 1);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -137,81 +86,30 @@ namespace Recyclable.Collections
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
// Make sure the list is cleared so that we no longer use any of the arrays
|
||||
LongCount = 0;
|
||||
_arrays.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndUpdate();
|
||||
}
|
||||
LongCount = 0;
|
||||
_arrays.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) => _arrays.Any(x => x.Contains(item));
|
||||
public bool Contains(T item) => _arrays.Contains(item);
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
Span<T> arrayMemory = array.AsSpan()[arrayIndex..];
|
||||
for (var arrayIdx = 0; arrayIdx < _arrays.Count - 1; arrayIdx++)
|
||||
{
|
||||
T[] partialArray = _arrays[arrayIdx];
|
||||
ReadOnlySpan<T> sourceMemory = partialArray.AsSpan();
|
||||
sourceMemory = sourceMemory[.._blockSize];
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
arrayMemory = arrayMemory[_blockSize..];
|
||||
}
|
||||
public void CopyTo(T[] array, int arrayIndex) => _arrays.CopyTo(0, BlockSize, (int)LongCount % BlockSize, array, arrayIndex);
|
||||
|
||||
if (_arrays.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<T> sourceMemory = _arrays[^1].AsSpan();
|
||||
var maxCount = Count % _blockSize;
|
||||
sourceMemory = maxCount > 0
|
||||
? sourceMemory[..maxCount]
|
||||
: sourceMemory[.._blockSize];
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IEnumerator<T> GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator();
|
||||
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
}
|
||||
}
|
||||
public int IndexOf(T item) => (int)_arrays.LongIndexOf(BlockSize, item, _equalityComparer);
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => EnumerateElements(_arrays, _blockSize, LongCount).GetEnumerator();
|
||||
public void Insert(int index, T item) => throw new NotSupportedException();
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
for (var arrayIdx = 0; arrayIdx < _arrays.Count; arrayIdx++)
|
||||
{
|
||||
var array = _arrays[arrayIdx];
|
||||
Span<T> arrayMemory = ((T[]?)array).AsSpan();
|
||||
for (var memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++)
|
||||
{
|
||||
if (_comparer.Equals(arrayMemory[memoryIdx], item))
|
||||
{
|
||||
return (arrayIdx * _blockSize) + memoryIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public long LongIndexOf(T item) => _arrays.LongIndexOf(BlockSize, item, _equalityComparer);
|
||||
|
||||
return -1;
|
||||
}
|
||||
public bool Remove(T item) => throw new NotSupportedException();
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
public void RemoveAt(int index) => throw new NotSupportedException();
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _arrays.Enumerate(BlockSize, LongCount).GetEnumerator();
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
@@ -226,10 +124,6 @@ namespace Recyclable.Collections
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void ListUpdated()
|
||||
{
|
||||
}
|
||||
|
||||
// override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
|
||||
// ~RecyclableList()
|
||||
// {
|
||||
@@ -244,17 +138,17 @@ namespace Recyclable.Collections
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void BeginUpdate() => _isUpdating++;
|
||||
|
||||
public void EndUpdate()
|
||||
public void RemoveBlock(int index)
|
||||
{
|
||||
if (_isUpdating > 0)
|
||||
try
|
||||
{
|
||||
_isUpdating--;
|
||||
if (_isUpdating == 0)
|
||||
{
|
||||
ListUpdated();
|
||||
}
|
||||
_arrays[index].ReturnToPool();
|
||||
_arrays.RemoveAt(index);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Capacity -= BlockSize;
|
||||
LongCount -= BlockSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,131 @@
|
||||
namespace Recyclable.Collections
|
||||
using System.Buffers;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public static class RecyclableListExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void CopyTo<T>(this List<T[]> arrays, long startingIndex, int blockSize, int lastBlockSize, T[] destinationArray, int destinationArrayIndex)
|
||||
{
|
||||
Span<T> arrayMemory = destinationArray.AsSpan();
|
||||
arrayMemory = arrayMemory[destinationArrayIndex..];
|
||||
int startingArrayIndex = (int)(startingIndex / blockSize);
|
||||
startingIndex %= blockSize;
|
||||
if (arrays.Count > 0)
|
||||
{
|
||||
ReadOnlySpan<T> 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<T> 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<T> sourceMemory = arrays[^1].AsSpan();
|
||||
var maxToCopy = Math.Min(lastBlockSize, arrayMemory.Length);
|
||||
sourceMemory = sourceMemory[..maxToCopy];
|
||||
sourceMemory.CopyTo(arrayMemory);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool Contains<T>(this List<T[]> arrays, T item) => arrays.Any(x => x.Contains(item));
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static IEnumerable<T> Enumerate<T>(this List<T[]> arrays, int chunkSize, long totalCount)
|
||||
{
|
||||
long currentCount = 0;
|
||||
for (var arrayIdx = 0; arrayIdx < arrays.Count; 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<T>(this T value, T maxValue, IComparer<T> comparer)
|
||||
=> comparer.Compare(value, maxValue) > 0 ? maxValue : value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static long LongIndexOf<T>(this List<T[]> arrays, int blockSize, T item, IEqualityComparer<T> comparer)
|
||||
{
|
||||
for (var arrayIdx = 0; arrayIdx < arrays.Count; arrayIdx++)
|
||||
{
|
||||
var array = arrays[arrayIdx];
|
||||
Span<T> arrayMemory = array.AsSpan();
|
||||
for (int memoryIdx = 0; memoryIdx < arrayMemory.Length; memoryIdx++)
|
||||
{
|
||||
if (comparer.Equals(arrayMemory[memoryIdx], item))
|
||||
{
|
||||
return (arrayIdx * (long)blockSize) + memoryIdx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int ToArrayIndex(this long index, int blockSize) => (int)(index / blockSize);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static long ToItemIndex(this long index, int blockSize) => index % blockSize;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RecyclableList<T> ToRecyclableList<T>(this IList<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RecyclableList<T> ToRecyclableList<T>(this IEnumerable<T> values, int blockSize = RecyclableDefaults.BlockSize) => new(values, blockSize);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T[] RentArrayFromPool<T>(this int minSize) => ArrayPool<T>.Shared.Rent(minSize);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReturnToPool<T>(this T[] array) => ArrayPool<T>.Shared.Return(array);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void SetItem<T>(this List<T[]> arrays, in int blockSize, in long index, in T value)
|
||||
=> arrays[(int)(index / blockSize)][index % blockSize] = value;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static T GetItem<T>(this List<T[]> arrays, in int blockSize, in long index)
|
||||
=> arrays[(int)(index / blockSize)][index % blockSize];
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable
|
||||
{
|
||||
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
||||
private static readonly IComparer<long> _comparer = Comparer<long>.Default;
|
||||
|
||||
private bool _disposedValue;
|
||||
|
||||
protected List<T[]> Arrays { get; }
|
||||
protected long GetAbsoluteIndex(long index) => index + RemovedCount;
|
||||
protected long GetRelativeIndex(long index) => index - RemovedCount;
|
||||
protected long RemovedCount { get; set; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static T[] RentArrayFromPool(int minSize) => ArrayPool<T>.Shared.Rent(minSize);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ReturnToPool(T[] array) => ArrayPool<T>.Shared.Return(array);
|
||||
|
||||
public RecyclableQueue(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
BlockSize = blockSize;
|
||||
Arrays = new();
|
||||
foreach (var item in source)
|
||||
{
|
||||
Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
BlockSize = blockSize;
|
||||
Arrays = new();
|
||||
}
|
||||
|
||||
public int BlockSize { get; protected set; }
|
||||
public long Capacity { get; set; }
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => (int)LongCount.LimitTo(int.MaxValue, _comparer);
|
||||
set => LongCount = value;
|
||||
}
|
||||
|
||||
public long LongCount { get; set; }
|
||||
public bool IsReadOnly { get; } = false;
|
||||
|
||||
public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; }
|
||||
public T this[long index]
|
||||
{
|
||||
get
|
||||
{
|
||||
long absoluteIndex = GetAbsoluteIndex(index);
|
||||
int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize);
|
||||
long itemIndex = absoluteIndex.ToItemIndex(BlockSize);
|
||||
return Arrays[arrayIndex][itemIndex];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
long absoluteIndex = GetAbsoluteIndex(index);
|
||||
int arrayIndex = absoluteIndex.ToArrayIndex(BlockSize);
|
||||
long itemIndex = absoluteIndex.ToItemIndex(BlockSize);
|
||||
Arrays[arrayIndex][itemIndex] = value;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (LongCount == Capacity)
|
||||
{
|
||||
T[] newArray = BlockSize.RentArrayFromPool<T>();
|
||||
Arrays.Add(newArray);
|
||||
Capacity += BlockSize;
|
||||
}
|
||||
|
||||
var newIndex = LongCount;
|
||||
// We don't want this[newIndex] set to raise the update event, because
|
||||
// the count would be wrong. We need to increase it, first.
|
||||
this[newIndex] = item;
|
||||
LongCount++;
|
||||
}
|
||||
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
var toRemove = this[0];
|
||||
RemovedCount++;
|
||||
LongCount--;
|
||||
if (Capacity - LongCount == BlockSize)
|
||||
{
|
||||
RemoveBlock(0);
|
||||
RemovedCount -= BlockSize;
|
||||
}
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
public void Add(T item) => Enqueue(item);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove in reversed order for performance savings
|
||||
while (Arrays.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveBlock(Arrays.Count - 1);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// We want to try returning as many arrays, as possible, before
|
||||
// the list is cleared.
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LongCount = 0;
|
||||
RemovedCount = 0;
|
||||
Arrays.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item) => Arrays.Contains(item);
|
||||
public void CopyTo(T[] array, int arrayIndex) => Arrays.CopyTo(RemovedCount, BlockSize, (int)(LongCount % BlockSize), array, arrayIndex);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public IEnumerable<T> Enumerate() => Arrays.Enumerate(BlockSize, LongCount);
|
||||
public IEnumerator<T> GetEnumerator() => Enumerate().GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
public int IndexOf(T item) => (int)LongIndexOf(item).LimitTo(int.MaxValue, _comparer);
|
||||
public long LongIndexOf(T item) => GetRelativeIndex(Arrays.LongIndexOf(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)
|
||||
{
|
||||
if (LongCount > 0)
|
||||
{
|
||||
item = Dequeue();
|
||||
return true;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Remove(T item) => throw new NotSupportedException();
|
||||
public void RemoveBlock(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
ReturnToPool(Arrays[index]);
|
||||
Arrays.RemoveAt(index);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Capacity -= BlockSize;
|
||||
LongCount -= BlockSize;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
Clear();
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,11 @@
|
||||
where TKey : notnull
|
||||
{
|
||||
private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer<TKey, TValue>();
|
||||
private readonly SystemRandomNumberGenerator _randomNumberGenerator = new SystemRandomNumberGenerator();
|
||||
private uint _isUpdating;
|
||||
private readonly SystemRandomNumberGenerator _randomNumberGenerator = new();
|
||||
|
||||
protected override void ListUpdated()
|
||||
protected void ListUpdated()
|
||||
{
|
||||
base.ListUpdated();
|
||||
QuickSort();
|
||||
}
|
||||
|
||||
@@ -27,17 +27,53 @@
|
||||
QuickSort();
|
||||
}
|
||||
|
||||
public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize)
|
||||
: base(blockSize)
|
||||
{
|
||||
}
|
||||
|
||||
public bool IsUpdating => _isUpdating > 0;
|
||||
|
||||
public void Add(TKey key, TValue value) => Add((key, value));
|
||||
|
||||
public void BeginUpdate() => _isUpdating++;
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
BeginUpdate();
|
||||
try
|
||||
{
|
||||
base.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void EndUpdate(bool raiseListUpdated = true)
|
||||
{
|
||||
if (_isUpdating > 0)
|
||||
{
|
||||
_isUpdating--;
|
||||
if (raiseListUpdated && _isUpdating == 0)
|
||||
{
|
||||
ListUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void QuickSort()
|
||||
{
|
||||
_isUpdating++;
|
||||
BeginUpdate();
|
||||
try
|
||||
{
|
||||
this.QuickSort(_comparer, _randomNumberGenerator);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isUpdating--;
|
||||
EndUpdate(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Collections;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public class RecyclableStack<T> : IList<T>, IDisposable
|
||||
{
|
||||
private bool _disposedValue;
|
||||
private readonly int _blockSize;
|
||||
|
||||
protected RecyclableList<T> List { get; }
|
||||
public int Count => List.Count;
|
||||
public long LongCount
|
||||
{
|
||||
get => List.LongCount;
|
||||
set => List.LongCount = value;
|
||||
}
|
||||
|
||||
public bool IsReadOnly => List.IsReadOnly;
|
||||
|
||||
public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; }
|
||||
public T this[long index] { get => List[index]; set => List[index] = value; }
|
||||
|
||||
public RecyclableStack(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
List = new(blockSize);
|
||||
}
|
||||
|
||||
public RecyclableStack(IEnumerable<T> list, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
List = new(list, blockSize);
|
||||
}
|
||||
|
||||
public RecyclableStack(RecyclableList<T> list, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
_blockSize = blockSize;
|
||||
List = new(list, blockSize);
|
||||
}
|
||||
|
||||
public void Push(T item)
|
||||
{
|
||||
List.Add(item);
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
var toRemove = List[LongCount - 1];
|
||||
LongCount--;
|
||||
if (List.Capacity - LongCount == _blockSize)
|
||||
{
|
||||
List.RemoveBlock(List.BlockCount - 1);
|
||||
}
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
|
||||
public int IndexOf(T item) => List.IndexOf(item);
|
||||
public long LongIndexOf(T item) => List.LongIndexOf(item);
|
||||
public void Insert(int index, T item) => List.Insert(index, item);
|
||||
public void RemoveAt(int index) => List.RemoveAt(index);
|
||||
public void Add(T item) => List.Add(item);
|
||||
public void Clear() => List.Clear();
|
||||
public bool Contains(T item) => List.Contains(item);
|
||||
public void CopyTo(T[] array, int arrayIndex) => List.CopyTo(array, arrayIndex);
|
||||
public bool Remove(T item) => List.Remove(item);
|
||||
public IEnumerator<T> GetEnumerator() => List.Reverse().GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
Clear();
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
{
|
||||
public class SystemRandomNumberGenerator : IRandomNumberGenerator
|
||||
{
|
||||
private static readonly Random _random = new();
|
||||
private readonly Random _random = new();
|
||||
|
||||
public int NextInt32(int startIndex, int endIndex) => _random.Next(startIndex, endIndex);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Recyclable.CollectionsTests
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = list.Count.Should().Be(itemsCount, "we added so many items");
|
||||
_ = list.LongCount.Should().Be(itemsCount, "we added so many items");
|
||||
for (var index = 0; index < itemsCount; index++)
|
||||
{
|
||||
var actual = list[index];
|
||||
@@ -76,7 +76,6 @@ namespace Recyclable.CollectionsTests
|
||||
|
||||
// Validate
|
||||
_ = list.LongCount.Should().Be(_totalObjectCount);
|
||||
_ = list.Count.Should().Be(_totalObjectCount);
|
||||
_ = list.Should().Equal(expectedList);
|
||||
_ = expectedList.Should().AllSatisfy(expected => list.Contains(expected));
|
||||
|
||||
@@ -85,7 +84,6 @@ namespace Recyclable.CollectionsTests
|
||||
|
||||
// Validate
|
||||
_ = list.LongCount.Should().Be(0);
|
||||
_ = list.Count.Should().Be(0);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -95,7 +93,7 @@ namespace Recyclable.CollectionsTests
|
||||
using var list = NewRecyclableList;
|
||||
|
||||
// Act
|
||||
var actual = new int[list.Count];
|
||||
var actual = new int[list.LongCount];
|
||||
list.CopyTo(actual, 0);
|
||||
_ = actual.Should().BeEquivalentTo(_testData);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
[TestClass]
|
||||
public class RecyclableQueueTests
|
||||
{
|
||||
private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" };
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotBeSortedWhenCreated()
|
||||
{
|
||||
// Act
|
||||
using var list = new RecyclableQueue<string>(_testData, 2);
|
||||
|
||||
// Validate
|
||||
_ = list.Should().HaveCount(_testData.Length)
|
||||
.And.ContainInConsecutiveOrder(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var list = new RecyclableQueue<string>(2);
|
||||
|
||||
// Validate
|
||||
_ = list.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotBeSortedWhenInitialized()
|
||||
{
|
||||
// Act
|
||||
using var list = new RecyclableQueue<string>(2)
|
||||
{
|
||||
_testData[0],
|
||||
_testData[1],
|
||||
_testData[2],
|
||||
_testData[3],
|
||||
_testData[4]
|
||||
};
|
||||
|
||||
// Validate
|
||||
_ = list.Should().NotBeEmpty()
|
||||
.And.ContainInConsecutiveOrder(_testData)
|
||||
.And.BeEquivalentTo(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeEmptyAfterClear()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
|
||||
// Act
|
||||
list.Clear();
|
||||
|
||||
// Validate
|
||||
_ = list.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void DequeueShouldReturnItemsInTheSameOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>();
|
||||
|
||||
// Act
|
||||
for (var itemIdx = 0; itemIdx < _testData.Length; itemIdx++)
|
||||
{
|
||||
list.Enqueue(_testData[itemIdx]);
|
||||
}
|
||||
|
||||
var dequeuedItems = new RecyclableList<string>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
dequeuedItems.Add(list.Dequeue());
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void DequeueShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
|
||||
// Act
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
_ = list.Dequeue();
|
||||
}
|
||||
|
||||
_ = list.Dequeue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void GetEnumeratorShouldYieldAllItemsInTheSameOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
|
||||
// Act
|
||||
var actual = new RecyclableList<string>();
|
||||
var enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = actual.Should().ContainInConsecutiveOrder(_testData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ namespace Recyclable.CollectionsTests
|
||||
public void ShouldBeSortedWhenCreated()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableSortedList<int, string>(_testData);
|
||||
using var sortedList = new RecyclableSortedList<int, string>(_testData, 2);
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().HaveCount(_testData.Length)
|
||||
@@ -24,17 +24,17 @@ namespace Recyclable.CollectionsTests
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableSortedList<int, string>();
|
||||
using var sortedList = new RecyclableSortedList<int, string>(2);
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeSortedWhenInitialized()
|
||||
public void ShouldNotBeSortedWhenInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableSortedList<int, string>()
|
||||
using var sortedList = new RecyclableSortedList<int, string>(2)
|
||||
{
|
||||
_testData[0],
|
||||
_testData[1],
|
||||
@@ -45,15 +45,14 @@ namespace Recyclable.CollectionsTests
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().NotBeEmpty()
|
||||
.And.BeInAscendingOrder(x => x.Key)
|
||||
.And.BeEquivalentTo(_testData);
|
||||
.And.ContainInConsecutiveOrder(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeSortedWhenUpdateEnds()
|
||||
{
|
||||
// Prepare
|
||||
using var sortedList = new RecyclableSortedList<int, string>();
|
||||
using var sortedList = new RecyclableSortedList<int, string>(2);
|
||||
|
||||
// Act
|
||||
sortedList.BeginUpdate();
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
[TestClass]
|
||||
public class RecyclableStackTests
|
||||
{
|
||||
private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" };
|
||||
|
||||
[TestMethod]
|
||||
public void GetEnumeratorShouldYieldAllItemsInReversedOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableStack<string>(_testData);
|
||||
|
||||
// Act
|
||||
var actual = new RecyclableList<string>();
|
||||
var enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = actual.Should().ContainInConsecutiveOrder(_testData.Reverse());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotBeSortedWhenCreated()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableStack<string>(_testData, 2);
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().HaveCount(_testData.Length)
|
||||
.And.ContainInConsecutiveOrder(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableStack<string>(2);
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldNotBeSortedWhenInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableStack<string>(2)
|
||||
{
|
||||
_testData[0],
|
||||
_testData[1],
|
||||
_testData[2],
|
||||
_testData[3],
|
||||
_testData[4]
|
||||
};
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().NotBeEmpty()
|
||||
.And.ContainInConsecutiveOrder(_testData)
|
||||
.And.BeEquivalentTo(_testData);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void ShouldBeEmptyAfterClear()
|
||||
{
|
||||
// Prepare
|
||||
using var sortedList = new RecyclableStack<string>(_testData);
|
||||
|
||||
// Act
|
||||
sortedList.Clear();
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void PopShouldReturnItemsInReversedOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableStack<string>();
|
||||
|
||||
// Act
|
||||
for (var itemIdx = 0; itemIdx < _testData.Length; itemIdx++)
|
||||
{
|
||||
list.Push(_testData[itemIdx]);
|
||||
}
|
||||
|
||||
var dequeuedItems = new RecyclableList<string>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
dequeuedItems.Add(list.Pop());
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData.Reverse());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void PopShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
|
||||
// Act
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
_ = list.Dequeue();
|
||||
}
|
||||
|
||||
_ = list.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user