Align stack enumerator with .NET Stack

This commit is contained in:
Marek Lemańczyk committed 2025-07-12 02:46:17 +02:00
1 parent 210f0df68c
commit 1b4b9f534b
2 files changed
+212 -81

No files matched your search

+207 -76
View File
@@ -1,94 +1,225 @@
using System.Collections; using System.Collections;
using System.Numerics;
using Recyclable.Collections.Pools;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
internal class RecyclableStack<T> : IList<T>, IDisposable internal sealed class RecyclableStack<T> : IEnumerable<T>, IDisposable
{ {
private static readonly bool _needsClearing = !typeof(T).IsValueType; private static readonly bool _needsClearing = !typeof(T).IsValueType;
private bool _disposedValue;
protected RecyclableLongList<T> List { get; } private sealed class Chunk
public int Count => checked((int)List._longCount); {
public long LongCount internal T[] Buffer;
{ internal int Index;
get => List._longCount; internal Chunk? Previous;
set => List._longCount = value; internal Chunk? Next;
}
public bool IsReadOnly => false;
public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; } internal Chunk(int size, Chunk? previous)
public T this[long index] { get => List[index]; set => List[index] = value; } {
Buffer = size >= RecyclableDefaults.MinPooledArrayLength
? RecyclableArrayPool<T>.RentShared(size)
: new T[size];
Previous = previous;
Next = null;
Index = 0;
}
}
public RecyclableStack(int blockSize = RecyclableDefaults.BlockSize) private Chunk _current;
{ private Chunk _bottom;
List = new(blockSize); private bool _disposed;
} private long _capacity;
private long _count;
public RecyclableStack(IEnumerable<T> list, int blockSize = RecyclableDefaults.BlockSize) public RecyclableStack(int initialCapacity = RecyclableDefaults.InitialCapacity)
{ {
List = new(list, blockSize); if (initialCapacity < 1)
} {
initialCapacity = 1;
}
public RecyclableStack(RecyclableLongList<T> list, int blockSize = RecyclableDefaults.BlockSize) if (!BitOperations.IsPow2((uint)initialCapacity))
{ {
List = new(list, blockSize); initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
} }
public void Push(T item) _current = new Chunk(initialCapacity, null);
{ _bottom = _current;
List.Add(item); _capacity = initialCapacity;
} _count = 0;
}
public T Pop() public RecyclableStack(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.InitialCapacity) : this(initialCapacity)
{ {
RecyclableLongList<T> list = List; foreach (var item in source)
var itemIndex = (int)(list.LongCount & list._blockSizeMinus1) - 1; {
var blockIndex = (int)(LongCount >> list._blockSizePow2BitShift); Push(item);
var toRemove = list._memoryBlocks[blockIndex][itemIndex]; }
}
LongCount--; public int Count => checked((int)_count);
public long LongCount => _count;
if (_needsClearing) public void Push(T item)
{ {
#nullable disable if (_current.Index == _current.Buffer.Length)
new Span<T>(list._memoryBlocks[blockIndex])[itemIndex] = default; {
#nullable restore Grow();
} }
if (itemIndex == 0) _current.Buffer[_current.Index++] = item;
{ _count++;
list.RemoveBlock(blockIndex); }
}
return toRemove; public void Add(T item) => Push(item);
}
public int IndexOf(T item) => List.IndexOf(item); public T Pop()
public long LongIndexOf(T item) => List.LongIndexOf(item); {
public void Insert(int index, T item) => List.Insert(index, item); if (_count == 0)
public void RemoveAt(int index) => List.RemoveAt(index); {
public void Add(T item) => List.Add(item); ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_count), "Stack is empty");
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) _count--;
{ _current.Index--;
if (!_disposedValue) var value = _current.Buffer[_current.Index];
{ if (_needsClearing)
Clear(); {
_disposedValue = true; _current.Buffer[_current.Index] = default!;
} }
}
public void Dispose() if (_current.Index == 0 && _current.Previous != null)
{ {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method ReleaseCurrentChunk();
Dispose(disposing: true); }
GC.SuppressFinalize(this);
} return value;
} }
public void Clear()
{
while (_current.Previous != null)
{
ReleaseCurrentChunk();
}
if (_needsClearing && _current.Index > 0)
{
Array.Clear(_current.Buffer, 0, _current.Index);
}
_capacity = _current.Buffer.Length;
_current.Index = 0;
_count = 0;
_bottom = _current;
}
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 (_capacity >= RecyclableDefaults.MaxPooledBlockSize)
{
newSize = RecyclableDefaults.MaxPooledBlockSize;
}
else
{
var doubled = _capacity * 2;
newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize);
}
var newChunk = new Chunk(newSize, _current);
_current.Next = newChunk;
_current = newChunk;
_capacity += newSize;
}
private void ReleaseCurrentChunk()
{
var toReturn = _current.Buffer;
_current = _current.Previous!;
_current.Next = null;
_capacity -= toReturn.Length;
if (_current.Previous == null)
{
_bottom = _current;
}
if (toReturn.Length >= RecyclableDefaults.MinPooledArrayLength)
{
RecyclableArrayPool<T>.ReturnShared(toReturn, _needsClearing);
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
Clear();
if (_current.Buffer.Length >= RecyclableDefaults.MinPooledArrayLength)
{
RecyclableArrayPool<T>.ReturnShared(_current.Buffer, _needsClearing);
}
_disposed = true;
GC.SuppressFinalize(this);
}
public struct Enumerator : IEnumerator<T>
{
private readonly Chunk? _start;
private Chunk? _chunk;
private int _index;
private T? _current;
internal Enumerator(RecyclableStack<T> stack)
{
_start = stack._current;
_chunk = stack._current;
_index = _chunk != null ? _chunk.Index - 1 : -1;
_current = default;
}
public T Current => _current!;
object IEnumerator.Current => _current!;
public bool MoveNext()
{
if (_chunk == null)
{
return false;
}
if (_index < 0)
{
_chunk = _chunk.Previous;
if (_chunk == null)
{
return false;
}
_index = _chunk.Index - 1;
}
_current = _chunk.Buffer[_index--];
return true;
}
public void Reset()
{
_chunk = _start;
_index = _chunk != null ? _chunk.Index - 1 : -1;
}
public void Dispose()
{
_chunk = null;
_current = default;
}
}
}
} }
@@ -32,8 +32,8 @@ namespace Recyclable.CollectionsTests
using var sortedList = new RecyclableStack<string>(_testData, 2); using var sortedList = new RecyclableStack<string>(_testData, 2);
// Validate // Validate
_ = sortedList.Should().HaveCount(_testData.Length) _ = sortedList.Should().HaveCount(_testData.Length)
.And.ContainInConsecutiveOrder(_testData); .And.ContainInConsecutiveOrder(_testData.Reverse());
} }
[Fact] [Fact]
@@ -60,9 +60,9 @@ namespace Recyclable.CollectionsTests
}; };
// Validate // Validate
_ = sortedList.Should().NotBeEmpty() _ = sortedList.Should().NotBeEmpty()
.And.ContainInConsecutiveOrder(_testData) .And.ContainInConsecutiveOrder(_testData.Reverse())
.And.BeEquivalentTo(_testData); .And.BeEquivalentTo(_testData);
} }
[Fact] [Fact]