Merge pull request #12 from mlemanczyk/codex/implement-recyclablesortedsett

Implement RecyclableSortedSet
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-07-14 11:43:26 +02:00
commit 80b4b0e890
4 files changed
+417 -10

No files matched your search

@@ -0,0 +1,39 @@
using System.Runtime.CompilerServices;
using System.Collections.Generic;
using Recyclable.Collections.Linq;
namespace Recyclable.Collections
{
[Obsolete("It'll be moved to Recyclable.Collections.Compatibility.List NuGet package in the next release.")]
public static class zRecyclableLongListCompatibilityListSorted
{
public static void Sort<T>(this RecyclableLongList<T> list)
{
QuickSortExtensions<T>.QuickSort(list);
list._version++;
}
public static void Sort<T>(this RecyclableLongList<T> list, Comparison<T> comparison)
{
QuickSortExtensions<T>.QuickSort(list, new ComparisonToComparerAdapter<T>(comparison), new SystemRandomNumberGenerator());
list._version++;
}
public static void Sort<T>(this RecyclableLongList<T> list, IComparer<T>? comparer)
{
QuickSortExtensions<T>.QuickSort(list, comparer ?? Comparer<T>.Default, new SystemRandomNumberGenerator());
list._version++;
}
public static void Sort<T>(this RecyclableLongList<T> list, long index, long count, IComparer<T>? comparer)
{
if (count == 0)
{
return;
}
QuickSortExtensions<T>.QuickSort(list, (int)index, (int)(index + count - 1), comparer ?? Comparer<T>.Default, new SystemRandomNumberGenerator());
list._version++;
}
}
}
@@ -0,0 +1,215 @@
using System.Collections;
using System.Numerics;
using System.Runtime.CompilerServices;
using Recyclable.Collections.Pools;
namespace Recyclable.Collections
{
internal sealed class RecyclableSortedSet<T> : IEnumerable<T>, IDisposable where T : notnull
{
private static readonly bool _needsClearing = !typeof(T).IsValueType;
private readonly IComparer<T> _comparer;
private T[] _items;
private int _count;
private bool _disposed;
public RecyclableSortedSet(int initialCapacity = RecyclableDefaults.InitialCapacity, IComparer<T>? comparer = null)
{
if (initialCapacity < 1)
{
initialCapacity = 1;
}
if (!BitOperations.IsPow2((uint)initialCapacity))
{
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
}
_items = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
? RecyclableArrayPool<T>.RentShared(initialCapacity)
: new T[initialCapacity];
_count = 0;
_comparer = comparer ?? Comparer<T>.Default;
}
public int Count => _count;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Add(T item)
{
int index = BinarySearch(item);
if (index < _count && _comparer.Compare(_items[index], item) == 0)
{
return false;
}
if (_count == _items.Length)
{
Grow();
}
if (index < _count)
{
Array.Copy(_items, index, _items, index + 1, _count - index);
}
_items[index] = item;
_count++;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(T item)
{
int index = BinarySearch(item);
return index < _count && _comparer.Compare(_items[index], item) == 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Remove(T item)
{
int index = BinarySearch(item);
if (index >= _count || _comparer.Compare(_items[index], item) != 0)
{
return false;
}
if (index < _count - 1)
{
Array.Copy(_items, index + 1, _items, index, _count - index - 1);
}
_count--;
if (_needsClearing)
{
_items[_count] = default!;
}
return true;
}
public void Clear()
{
if (_needsClearing)
{
Array.Clear(_items, 0, _count);
}
_count = 0;
}
public Enumerator GetEnumerator() => new(this);
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
private void Grow()
{
int newSize;
if (_items.Length >= RecyclableDefaults.MaxPooledBlockSize)
{
newSize = RecyclableDefaults.MaxPooledBlockSize;
}
else
{
var doubled = _items.Length << 1;
newSize = (int)Math.Min(doubled, RecyclableDefaults.MaxPooledBlockSize);
}
var newItems = newSize >= RecyclableDefaults.MinPooledArrayLength
? RecyclableArrayPool<T>.RentShared(newSize)
: new T[newSize];
Array.Copy(_items, newItems, _count);
if (_items.Length >= RecyclableDefaults.MinPooledArrayLength)
{
RecyclableArrayPool<T>.ReturnShared(_items, _needsClearing);
}
_items = newItems;
}
private int BinarySearch(T item)
{
int low = 0;
int high = _count - 1;
while (low <= high)
{
int mid = (low + high) >> 1;
int cmp = _comparer.Compare(_items[mid], item);
if (cmp == 0)
{
return mid;
}
if (cmp < 0)
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
return low;
}
public void Dispose()
{
if (_disposed)
{
return;
}
Clear();
if (_items.Length >= RecyclableDefaults.MinPooledArrayLength)
{
RecyclableArrayPool<T>.ReturnShared(_items, _needsClearing);
}
_items = Array.Empty<T>();
_disposed = true;
GC.SuppressFinalize(this);
}
public struct Enumerator : IEnumerator<T>
{
private readonly RecyclableSortedSet<T> _set;
private int _index;
private T? _current;
internal Enumerator(RecyclableSortedSet<T> set)
{
_set = set;
_index = 0;
_current = default;
}
public T Current => _current!;
object IEnumerator.Current => _current!;
public bool MoveNext()
{
if (_index >= _set._count)
{
return false;
}
_current = _set._items[_index++];
return true;
}
public void Reset()
{
_index = 0;
_current = default;
}
public void Dispose()
{
}
}
}
}
@@ -928,11 +928,11 @@ namespace Recyclable.CollectionsTests
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void SetByIndexShouldReturnCorrectItemWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
var expectedData = testData.ToList();
public void SetByIndexShouldReturnCorrectItemWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{
// Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
var expectedData = testData.ToList();
// Act & Validate
foreach (long item in testData)
@@ -941,11 +941,96 @@ namespace Recyclable.CollectionsTests
list[item - 1] = item + 1;
// Validate
expectedData[(int)item - 1] = item + 1;
list[item - 1].Should().Be(item + 1);
list.Should().Equal(expectedData);
}
}
expectedData[(int)item - 1] = item + 1;
list[item - 1].Should().Be(item + 1);
list.Should().Equal(expectedData);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void SortShouldSortItemsInCorrectOrder(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
using var list = new RecyclableLongList<long>(testData.Reverse(), initialCapacity: itemsCount);
var expectedData = testData.Reverse().ToList();
expectedData.Sort();
// Act
list.Sort();
// Validate
_ = list.Should().Equal(expectedData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void SortShouldSortItemsInCorrectOrderWhenWithComparer(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
using var list = new RecyclableLongList<long>(testData.Reverse(), initialCapacity: itemsCount);
IComparer<long> comparer = Comparer<long>.Default;
var expectedData = testData.Reverse().ToList();
expectedData.Sort(comparer);
// Act
list.Sort(comparer);
// Validate
_ = list.Should().Equal(expectedData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
public void SortShouldSortItemsInCorrectOrderWhenWithComparison(string testCase, IEnumerable<long> testData, long itemsCount)
{
// Prepare
using var list = new RecyclableLongList<long>(testData.Reverse(), initialCapacity: itemsCount);
static int comparison(long x, long y)
{
return (x - y) switch
{
> 0 => 1,
< 0 => -1,
_ => 0,
};
}
var expectedData = testData.Reverse().ToList();
expectedData.Sort(comparison);
// Act
list.Sort(comparison);
// Validate
_ = list.Should().Equal(expectedData);
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithItemIndexVariants), MemberType = typeof(RecyclableLongListTestData))]
public void SortShouldSortItemsInCorrectOrderWhenConstrained(string testCase, IEnumerable<long> testData, long itemsCount, long[] itemIndexes)
{
// Prepare
IComparer<long> comparer = Comparer<long>.Default;
var expectedData = testData.Reverse().ToArray();
foreach (var itemIndex in itemIndexes)
{
// Prepare
using var list = new RecyclableLongList<long>(testData.Reverse(), initialCapacity: itemsCount);
var startingIndex = (int)Math.Min(1, itemIndex);
var expectedSortedData = expectedData.ToList();
expectedSortedData.Sort(startingIndex, (int)itemIndex, comparer);
// Act
list.Sort(startingIndex, (int)itemIndex, comparer);
// Validate
_ = list.Should().Equal(expectedSortedData);
}
}
[Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
@@ -0,0 +1,68 @@
using FluentAssertions;
using Recyclable.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Recyclable.CollectionsTests
{
public class RecyclableSortedSetTests
{
[Fact]
public void AddShouldStoreUniqueItemsInOrder()
{
using var set = new RecyclableSortedSet<int>();
var values = new[] { 4, 1, 3, 2, 5 };
foreach (var v in values)
{
set.Add(v).Should().BeTrue();
}
set.Add(3).Should().BeFalse();
_ = set.Should().Equal(values.OrderBy(x => x));
}
[Fact]
public void RemoveShouldDeleteItem()
{
using var set = new RecyclableSortedSet<int>();
var values = new[] { 4, 1, 3, 2, 5 };
foreach (var v in values)
{
set.Add(v);
}
set.Remove(3).Should().BeTrue();
_ = set.Contains(3).Should().BeFalse();
_ = set.Should().Equal(values.Where(x => x != 3).OrderBy(x => x));
}
[Fact]
public void EnumeratorShouldYieldItemsInOrder()
{
using var set = new RecyclableSortedSet<int>();
foreach (var v in new[] { 3, 1, 4, 2 })
{
set.Add(v);
}
var result = new List<int>();
foreach (var item in set)
{
result.Add(item);
}
_ = result.Should().Equal(new[] { 1, 2, 3, 4 });
}
[Fact]
public void ShouldBeEmptyAfterClear()
{
using var set = new RecyclableSortedSet<int>();
set.Add(1);
set.Add(2);
set.Clear();
_ = set.Should().BeEmpty();
}
}
}