Compare commits
87
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5a574355e | ||
|
|
4b17a4c827 | ||
|
|
37f147a262 | ||
|
|
b0e7f96496 | ||
|
|
f6640deaf1 | ||
|
|
050d7fd386 | ||
|
|
33187c1053 | ||
|
|
ff38aa302a | ||
|
|
6d80b36d70 | ||
|
|
d8440fe8c9 | ||
|
|
67630e3797 | ||
|
|
8cc2e49ae4 | ||
|
|
4ea3df4cca | ||
|
|
9d1afe3e32 | ||
|
|
1ee464d6b8 | ||
|
|
26f2920f33 | ||
|
|
aa48407bba | ||
|
|
3dea509958 | ||
|
|
1dc6c479ae | ||
|
|
12b01c32ba | ||
|
|
ea5c4eee28 | ||
|
|
c24d8d54ea | ||
|
|
a178956761 | ||
|
|
78e9373491 | ||
|
|
56a5c3375a | ||
|
|
aacfed4e7e | ||
|
|
414e482aaf | ||
|
|
2546d6bf3f | ||
|
|
2de3736f94 | ||
|
|
136c6408f3 | ||
|
|
1aedfb2cbc | ||
|
|
4274f4dcbc | ||
|
|
7e71abdcf2 | ||
|
|
ffcbfb0493 | ||
|
|
037271acc8 | ||
|
|
25bf466f77 | ||
|
|
c2affa4ad7 | ||
|
|
a18caf42d6 | ||
|
|
1e64c71af3 | ||
|
|
ed23fd7f69 | ||
|
|
29a5db476e | ||
|
|
1abc07e9a2 | ||
|
|
dc0b3b92e5 | ||
|
|
b571a2df59 | ||
|
|
1d4d5e9217 | ||
|
|
976c5eb78b | ||
|
|
40074ce368 | ||
|
|
f4e2285734 | ||
|
|
35f1124d6f | ||
|
|
801ab88c01 | ||
|
|
e1298c817d | ||
|
|
66443480b7 | ||
|
|
941cf4f3fd | ||
|
|
0d28f6515d | ||
|
|
0496b63e5a | ||
|
|
9aa96ccb19 | ||
|
|
53c34d0fa7 | ||
|
|
80b4b0e890 | ||
|
|
9133867d39 | ||
|
|
80499d0936 | ||
|
|
06987cfb0f | ||
|
|
8ff09f12a5 | ||
|
|
7128a73e2e | ||
|
|
a97a81b50c | ||
|
|
f203f19312 | ||
|
|
f9af1086e5 | ||
|
|
0f2d05f60d | ||
|
|
98c24430ba | ||
|
|
6934dbd02e | ||
|
|
6f9ffd7916 | ||
|
|
713a8ffdba | ||
|
|
3a712dbe57 | ||
|
|
93261808aa | ||
|
|
770ae2d2f6 | ||
|
|
e9887957f9 | ||
|
|
fc0afdcca8 | ||
|
|
aa52bddd66 | ||
|
|
ac163014d8 | ||
|
|
1b4b9f534b | ||
|
|
210f0df68c | ||
|
|
64482ca13c | ||
|
|
21634eca68 | ||
|
|
a9ee409a18 | ||
|
|
6944fe25b3 | ||
|
|
29b809cead | ||
|
|
877b363381 | ||
|
|
c3205debed |
No files matched your search
@@ -0,0 +1,28 @@
|
||||
# This workflow will build a .NET project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net
|
||||
|
||||
name: .NET
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 8.0.x
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore
|
||||
- name: Build
|
||||
run: dotnet build --no-restore
|
||||
- name: Test
|
||||
run: dotnet test --framework net8.0 --verbosity normal
|
||||
Vendored
+1
-1
@@ -10,7 +10,7 @@
|
||||
"request": "launch",
|
||||
"preLaunchTask": "build",
|
||||
// If you have changed target frameworks, make sure to update the program path.
|
||||
"program": "${workspaceFolder}/Recyclable.Collections.Benchmarks/bin/Debug/net6.0/Recyclable.Collections.Benchmarks.dll",
|
||||
"program": "${workspaceFolder}/Recyclable.Collections.Benchmarks/bin/Debug/net8.0/Recyclable.Collections.Benchmarks.dll",
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}/Recyclable.Collections.Benchmarks",
|
||||
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,29 @@
|
||||
# AGENTS
|
||||
|
||||
This repository hosts various libraries distributed as NuGet packages. Keep these links up to date so contributors and tools can easily locate them.
|
||||
|
||||
Only the .NET SDK **8.0** is available in the execution environment. Use this version when building or running tests.
|
||||
|
||||
## Implementation Guidelines
|
||||
1. All `*.csproj` files should enable nullable reference types with `<Nullable>enable</Nullable>`.
|
||||
1. Implementations should be optimized for maximum performance even if that introduces some code duplication.
|
||||
1. Consider applying attributes such as `MethodImplOptions.AggressiveInlining` or faster alternatives to common operations when appropriate. Another would be modulo "%" or division "/" operations, which could be often replaced with faster alternatives, possibly under specific conditions.
|
||||
|
||||
## Running Tests
|
||||
1. When running `dotnet test`, do **not** pass `--no-build` because that leads to missing assemblies, test failures without logs and other issues.
|
||||
1. When running `dotnet test` specify the `--framework` argument matching the SDK available in the environment (currently `net8.0`). If you omit this parameter, the tests will fail running with no logs etc.
|
||||
1. The tests run for a very long time, because there are 61_000+ unit tests for all the collections. Set timeout when running them to 15 mis.
|
||||
1. All tests must use **xUnit** together with **FluentAssertions**.
|
||||
|
||||
## Source Code Formatting Guidelines
|
||||
1. Indent code using four spaces or a single tab per level.
|
||||
1. Place one blank line after each closing curly brace of a code block, except when several closing braces appear sequentially.
|
||||
1. Always use braces for `if` and `else` bodies.
|
||||
|
||||
## NuGet packages
|
||||
- [Recyclable.Collections](https://github.com/mlemanczyk/Recyclable.Collections)
|
||||
- [Recyclable.Collections.Compatibility.List](https://github.com/mlemanczyk/Recyclable.Collections.Compatibility.List)
|
||||
- [Recyclable.Collections.Concurrent](https://github.com/mlemanczyk/Recyclable.Collections.Concurrent)
|
||||
- [Recyclable.Collections.Linq](https://github.com/mlemanczyk/Recyclable.Collections.Linq)
|
||||
- [Recyclable.Collections.Searching](https://github.com/mlemanczyk/Recyclable.Collections.Searching)
|
||||
- [Recyclable.Collections.TestData](https://github.com/mlemanczyk/Recyclable.Collections.TestData)
|
||||
@@ -5,6 +5,14 @@
|
||||
* `RecyclableList<T>`
|
||||
* `RecyclableLongList<T>`
|
||||
|
||||
## NuGet packages
|
||||
- [Recyclable.Collections](https://github.com/mlemanczyk/Recyclable.Collections)
|
||||
- [Recyclable.Collections.Compatibility.List](https://github.com/mlemanczyk/Recyclable.Collections.Compatibility.List)
|
||||
- [Recyclable.Collections.Concurrent](https://github.com/mlemanczyk/Recyclable.Collections.Concurrent)
|
||||
- [Recyclable.Collections.Linq](https://github.com/mlemanczyk/Recyclable.Collections.Linq)
|
||||
- [Recyclable.Collections.Searching](https://github.com/mlemanczyk/Recyclable.Collections.Searching)
|
||||
- [Recyclable.Collections.TestData](https://github.com/mlemanczyk/Recyclable.Collections.TestData)
|
||||
|
||||
# Milestones
|
||||
1. ✅ Create basic classes
|
||||
1. ✅ `RecyclableList<T>`
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Recyclable.Collections.Pools
|
||||
{
|
||||
public sealed class BiDirectionalRecyclableArrayPoolChunk<T>
|
||||
{
|
||||
private static readonly T[] _empty = Array.Empty<T>();
|
||||
|
||||
internal static T[] Empty => _empty;
|
||||
|
||||
internal T[] Value;
|
||||
internal int Top;
|
||||
internal int Bottom;
|
||||
internal BiDirectionalRecyclableArrayPoolChunk<T>? Previous;
|
||||
internal BiDirectionalRecyclableArrayPoolChunk<T>? Next;
|
||||
|
||||
public BiDirectionalRecyclableArrayPoolChunk()
|
||||
{
|
||||
Value = _empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Recyclable.Collections.Pools
|
||||
{
|
||||
public static class BiDirectionalRecyclableArrayPoolChunkPool<T>
|
||||
{
|
||||
private static readonly ObjectPool<BiDirectionalRecyclableArrayPoolChunk<T>> _pool =
|
||||
new DefaultObjectPool<BiDirectionalRecyclableArrayPoolChunk<T>>(new DefaultPooledObjectPolicy<BiDirectionalRecyclableArrayPoolChunk<T>>());
|
||||
|
||||
public static BiDirectionalRecyclableArrayPoolChunk<T> Rent() => _pool.Get();
|
||||
|
||||
public static void Return(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
chunk.Top = 0;
|
||||
chunk.Bottom = 0;
|
||||
chunk.Previous = null;
|
||||
chunk.Next = null;
|
||||
_pool.Return(chunk);
|
||||
}
|
||||
|
||||
public static void Dispose(BiDirectionalRecyclableArrayPoolChunk<T> chunk, bool needsClearing)
|
||||
{
|
||||
var buffer = chunk.Value;
|
||||
if (buffer.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(buffer, needsClearing);
|
||||
}
|
||||
|
||||
chunk.Value = BiDirectionalRecyclableArrayPoolChunk<T>.Empty;
|
||||
chunk.Top = 0;
|
||||
chunk.Bottom = 0;
|
||||
chunk.Previous = null;
|
||||
chunk.Next = null;
|
||||
_pool.Return(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Recyclable.Collections.Pools
|
||||
{
|
||||
public sealed class RecyclableArrayPoolChunk<T>
|
||||
{
|
||||
private static readonly T[] _empty = Array.Empty<T>();
|
||||
|
||||
internal static T[] Empty => _empty;
|
||||
|
||||
internal T[] Value;
|
||||
internal int Index;
|
||||
internal RecyclableArrayPoolChunk<T>? Previous;
|
||||
internal RecyclableArrayPoolChunk<T>? Next;
|
||||
|
||||
public RecyclableArrayPoolChunk()
|
||||
{
|
||||
Value = _empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
|
||||
namespace Recyclable.Collections.Pools
|
||||
{
|
||||
public static class RecyclableArrayPoolChunkPool<T>
|
||||
{
|
||||
private static readonly ObjectPool<RecyclableArrayPoolChunk<T>> _pool =
|
||||
new DefaultObjectPool<RecyclableArrayPoolChunk<T>>(new DefaultPooledObjectPolicy<RecyclableArrayPoolChunk<T>>());
|
||||
|
||||
public static RecyclableArrayPoolChunk<T> Rent() => _pool.Get();
|
||||
|
||||
public static void Return(RecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
chunk.Index = 0;
|
||||
chunk.Previous = null;
|
||||
chunk.Next = null;
|
||||
_pool.Return(chunk);
|
||||
}
|
||||
|
||||
public static void Dispose(RecyclableArrayPoolChunk<T> chunk, bool needsClearing)
|
||||
{
|
||||
var buffer = chunk.Value;
|
||||
if (buffer.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(buffer, needsClearing);
|
||||
}
|
||||
chunk.Value = RecyclableArrayPoolChunk<T>.Empty;
|
||||
chunk.Index = 0;
|
||||
chunk.Previous = null;
|
||||
chunk.Next = null;
|
||||
_pool.Return(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<Nullable>enable</Nullable>
|
||||
<DefineConstants>$(DefineConstants);WITH_VERSIONING</DefineConstants>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<PackageId>Recyclable.Collections</PackageId>
|
||||
<PackageVersion>0.0.6</PackageVersion>
|
||||
@@ -30,8 +31,12 @@
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.5" />
|
||||
<PackageReference Include="morelinq" Version="3.4.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.5" />
|
||||
<PackageReference Include="morelinq" Version="3.4.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\VersionedCollectionsGenerator\VersionedCollectionsGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,406 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal sealed class RecyclableDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IDisposable
|
||||
where TKey : notnull
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(TKey).IsValueType || !typeof(TValue).IsValueType;
|
||||
|
||||
internal readonly int _blockSize;
|
||||
internal readonly int _blockSizeMinus1;
|
||||
internal readonly byte _blockShift;
|
||||
|
||||
#nullable disable
|
||||
internal Entry[][] _entries;
|
||||
#nullable restore
|
||||
internal int[] _buckets;
|
||||
internal int _count;
|
||||
internal bool _disposed;
|
||||
|
||||
internal struct Entry
|
||||
{
|
||||
public int HashCode;
|
||||
public int Next;
|
||||
public TKey Key;
|
||||
public TValue Value;
|
||||
}
|
||||
|
||||
public RecyclableDictionary(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
if (!BitOperations.IsPow2((uint)blockSize))
|
||||
{
|
||||
blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)blockSize);
|
||||
}
|
||||
|
||||
_blockSize = blockSize;
|
||||
_blockSizeMinus1 = blockSize - 1;
|
||||
_blockShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize));
|
||||
|
||||
_entries = 4 >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry[]>.RentShared(4)
|
||||
: new Entry[4][];
|
||||
_entries[0] = blockSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry>.RentShared(blockSize)
|
||||
: new Entry[blockSize];
|
||||
_buckets = new int[4];
|
||||
Array.Fill(_buckets, -1);
|
||||
}
|
||||
|
||||
public int Count => _count;
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
var index = FindIndex(key, out _);
|
||||
if (index >= 0)
|
||||
{
|
||||
return GetEntry(index).Value;
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(key), "Key not found");
|
||||
return default!;
|
||||
}
|
||||
set
|
||||
{
|
||||
var index = FindIndex(key, out _);
|
||||
if (index >= 0)
|
||||
{
|
||||
ref var entry = ref GetEntry(index);
|
||||
entry.Value = value;
|
||||
return;
|
||||
}
|
||||
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
var hash = key.GetHashCode() & int.MaxValue;
|
||||
if (_count >= (_buckets.Length * 3) / 4)
|
||||
{
|
||||
ResizeBuckets(_buckets.Length * 2);
|
||||
}
|
||||
|
||||
EnsureCapacity(_count + 1);
|
||||
|
||||
int bucket = hash & (_buckets.Length - 1);
|
||||
int index = _buckets[bucket];
|
||||
while (index >= 0)
|
||||
{
|
||||
ref var check = ref GetEntry(index);
|
||||
if (check.HashCode == hash && EqualityComparer<TKey>.Default.Equals(check.Key, key))
|
||||
{
|
||||
throw new ArgumentException("An element with the same key already exists.", nameof(key));
|
||||
}
|
||||
|
||||
index = check.Next;
|
||||
}
|
||||
|
||||
int newIndex = _count++;
|
||||
ref var entry = ref GetEntry(newIndex);
|
||||
entry.HashCode = hash;
|
||||
entry.Key = key;
|
||||
entry.Value = value;
|
||||
entry.Next = _buckets[bucket];
|
||||
_buckets[bucket] = newIndex;
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ContainsKey(TKey key) => FindIndex(key, out _) >= 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
var index = FindIndex(key, out _);
|
||||
if (index >= 0)
|
||||
{
|
||||
value = GetEntry(index).Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hash = key.GetHashCode() & int.MaxValue;
|
||||
int bucket = hash & (_buckets.Length - 1);
|
||||
int prev = -1;
|
||||
int index = _buckets[bucket];
|
||||
while (index >= 0)
|
||||
{
|
||||
ref var entry = ref GetEntry(index);
|
||||
if (entry.HashCode == hash && EqualityComparer<TKey>.Default.Equals(entry.Key, key))
|
||||
{
|
||||
if (prev < 0)
|
||||
{
|
||||
_buckets[bucket] = entry.Next;
|
||||
}
|
||||
else
|
||||
{
|
||||
GetEntry(prev).Next = entry.Next;
|
||||
}
|
||||
|
||||
int lastIndex = _count - 1;
|
||||
if (index != lastIndex)
|
||||
{
|
||||
MoveEntry(lastIndex, index);
|
||||
}
|
||||
|
||||
ClearEntry(lastIndex);
|
||||
_count--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
prev = index;
|
||||
index = entry.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
Array.Fill(_buckets, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
int blockIndex = 0;
|
||||
if (_blockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
while (blockIndex < _entries.Length && _entries[blockIndex] != null)
|
||||
{
|
||||
RecyclableArrayPool<Entry>.ReturnShared(_entries[blockIndex]!, _needsClearing);
|
||||
_entries[blockIndex++] = null!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_needsClearing)
|
||||
{
|
||||
while (blockIndex < _entries.Length && _entries[blockIndex] != null)
|
||||
{
|
||||
Array.Clear(_entries[blockIndex]!, 0, _entries[blockIndex]!.Length);
|
||||
_entries[blockIndex++] = null!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (blockIndex < _entries.Length && _entries[blockIndex] != null)
|
||||
{
|
||||
_entries[blockIndex++] = null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
Array.Fill(_buckets, -1);
|
||||
}
|
||||
|
||||
public TKey GetKey(int index) => GetEntry(index).Key;
|
||||
|
||||
public TValue GetValue(int index) => GetEntry(index).Value;
|
||||
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Clear();
|
||||
if (_entries.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<Entry[]>.ReturnShared(_entries, false);
|
||||
}
|
||||
|
||||
if (_buckets.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<int>.ReturnShared(_buckets, false);
|
||||
}
|
||||
|
||||
_entries = Array.Empty<Entry[]>();
|
||||
_buckets = Array.Empty<int>();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private ref Entry GetEntry(int index)
|
||||
{
|
||||
return ref _entries[index >> _blockShift][index & _blockSizeMinus1];
|
||||
}
|
||||
|
||||
private void ClearEntry(int index)
|
||||
{
|
||||
ref var entry = ref GetEntry(index);
|
||||
entry.HashCode = 0;
|
||||
entry.Next = -1;
|
||||
if (_needsClearing)
|
||||
{
|
||||
entry.Key = default!;
|
||||
entry.Value = default!;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveEntry(int sourceIndex, int targetIndex)
|
||||
{
|
||||
ref var source = ref GetEntry(sourceIndex);
|
||||
ref var target = ref GetEntry(targetIndex);
|
||||
target = source;
|
||||
|
||||
int bucket = source.HashCode & (_buckets.Length - 1);
|
||||
int cur = _buckets[bucket];
|
||||
int prev = -1;
|
||||
while (cur != sourceIndex)
|
||||
{
|
||||
prev = cur;
|
||||
cur = GetEntry(cur).Next;
|
||||
}
|
||||
|
||||
if (prev < 0)
|
||||
{
|
||||
_buckets[bucket] = targetIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
GetEntry(prev).Next = targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int min)
|
||||
{
|
||||
int requiredBlocks = (min + _blockSizeMinus1) >> _blockShift;
|
||||
if (_entries.Length < requiredBlocks)
|
||||
{
|
||||
var newBlocks = requiredBlocks >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry[]>.RentShared(requiredBlocks)
|
||||
: new Entry[requiredBlocks][];
|
||||
|
||||
Array.Copy(_entries, newBlocks, _entries.Length);
|
||||
|
||||
if (_entries.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<Entry[]>.ReturnShared(_entries, false);
|
||||
}
|
||||
|
||||
_entries = newBlocks;
|
||||
}
|
||||
|
||||
for (int i = 0; i < requiredBlocks; i++)
|
||||
{
|
||||
if (_entries[i] == null)
|
||||
{
|
||||
_entries[i] = _blockSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry>.RentShared(_blockSize)
|
||||
: new Entry[_blockSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeBuckets(int newSize)
|
||||
{
|
||||
var newBuckets = new int[newSize];
|
||||
Array.Fill(newBuckets, -1);
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
ref var entry = ref GetEntry(i);
|
||||
int bucket = entry.HashCode & (newSize - 1);
|
||||
entry.Next = newBuckets[bucket];
|
||||
newBuckets[bucket] = i;
|
||||
}
|
||||
|
||||
_buckets = newBuckets;
|
||||
}
|
||||
|
||||
private int FindIndex(TKey key, out int hash)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
hash = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
hash = key.GetHashCode() & int.MaxValue;
|
||||
int bucket = hash & (_buckets.Length - 1);
|
||||
int index = _buckets[bucket];
|
||||
while (index >= 0)
|
||||
{
|
||||
ref var entry = ref GetEntry(index);
|
||||
if (entry.HashCode == hash && EqualityComparer<TKey>.Default.Equals(entry.Key, key))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index = entry.Next;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
|
||||
{
|
||||
private readonly RecyclableDictionary<TKey, TValue> _dictionary;
|
||||
private int _index;
|
||||
private KeyValuePair<TKey, TValue> _current;
|
||||
|
||||
internal Enumerator(RecyclableDictionary<TKey, TValue> dictionary)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public KeyValuePair<TKey, TValue> Current => _current;
|
||||
|
||||
object IEnumerator.Current => _current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index >= _dictionary._count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var entry = _dictionary.GetEntry(_index++);
|
||||
_current = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,362 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal sealed class RecyclableHashSet<T> : IEnumerable<T>, IDisposable
|
||||
where T : notnull
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
|
||||
internal readonly int _blockSize;
|
||||
internal readonly int _blockSizeMinus1;
|
||||
internal readonly byte _blockShift;
|
||||
|
||||
#nullable disable
|
||||
internal Entry[][] _entries;
|
||||
#nullable restore
|
||||
internal int[] _buckets;
|
||||
internal int _count;
|
||||
internal bool _disposed;
|
||||
|
||||
internal struct Entry
|
||||
{
|
||||
public int HashCode;
|
||||
public int Next;
|
||||
public T Value;
|
||||
}
|
||||
|
||||
public RecyclableHashSet(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
if (!BitOperations.IsPow2((uint)blockSize))
|
||||
{
|
||||
blockSize = (int)BitOperations.RoundUpToPowerOf2((uint)blockSize);
|
||||
}
|
||||
|
||||
_blockSize = blockSize;
|
||||
_blockSizeMinus1 = blockSize - 1;
|
||||
_blockShift = (byte)(31 - BitOperations.LeadingZeroCount((uint)blockSize));
|
||||
|
||||
_entries = 4 >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry[]>.RentShared(4)
|
||||
: new Entry[4][];
|
||||
_entries[0] = blockSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry>.RentShared(blockSize)
|
||||
: new Entry[blockSize];
|
||||
_buckets = new int[4];
|
||||
Array.Fill(_buckets, -1);
|
||||
}
|
||||
|
||||
public int Count => _count;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Add(T item)
|
||||
{
|
||||
var hash = item.GetHashCode() & int.MaxValue;
|
||||
if (_count >= (_buckets.Length * 3) / 4)
|
||||
{
|
||||
ResizeBuckets(_buckets.Length * 2);
|
||||
}
|
||||
|
||||
EnsureCapacity(_count + 1);
|
||||
|
||||
int bucket = hash & (_buckets.Length - 1);
|
||||
int index = _buckets[bucket];
|
||||
while (index >= 0)
|
||||
{
|
||||
ref var check = ref GetEntry(index);
|
||||
if (check.HashCode == hash && EqualityComparer<T>.Default.Equals(check.Value, item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
index = check.Next;
|
||||
}
|
||||
|
||||
int newIndex = _count++;
|
||||
ref var entry = ref GetEntry(newIndex);
|
||||
entry.HashCode = hash;
|
||||
entry.Value = item;
|
||||
entry.Next = _buckets[bucket];
|
||||
_buckets[bucket] = newIndex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Contains(T item) => FindIndex(item, out _) >= 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hash = item.GetHashCode() & int.MaxValue;
|
||||
int bucket = hash & (_buckets.Length - 1);
|
||||
int prev = -1;
|
||||
int index = _buckets[bucket];
|
||||
while (index >= 0)
|
||||
{
|
||||
ref var entry = ref GetEntry(index);
|
||||
if (entry.HashCode == hash && EqualityComparer<T>.Default.Equals(entry.Value, item))
|
||||
{
|
||||
if (prev < 0)
|
||||
{
|
||||
_buckets[bucket] = entry.Next;
|
||||
}
|
||||
else
|
||||
{
|
||||
GetEntry(prev).Next = entry.Next;
|
||||
}
|
||||
|
||||
int lastIndex = _count - 1;
|
||||
if (index != lastIndex)
|
||||
{
|
||||
MoveEntry(lastIndex, index);
|
||||
}
|
||||
|
||||
ClearEntry(lastIndex);
|
||||
_count--;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
prev = index;
|
||||
index = entry.Next;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
Array.Fill(_buckets, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
int blockIndex = 0;
|
||||
if (_blockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
while (blockIndex < _entries.Length && _entries[blockIndex] != null)
|
||||
{
|
||||
RecyclableArrayPool<Entry>.ReturnShared(_entries[blockIndex]!, _needsClearing);
|
||||
_entries[blockIndex++] = null!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_needsClearing)
|
||||
{
|
||||
while (blockIndex < _entries.Length && _entries[blockIndex] != null)
|
||||
{
|
||||
Array.Clear(_entries[blockIndex]!, 0, _entries[blockIndex]!.Length);
|
||||
_entries[blockIndex++] = null!;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (blockIndex < _entries.Length && _entries[blockIndex] != null)
|
||||
{
|
||||
_entries[blockIndex++] = null!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
Array.Fill(_buckets, -1);
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Clear();
|
||||
if (_entries.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<Entry[]>.ReturnShared(_entries, false);
|
||||
}
|
||||
|
||||
if (_buckets.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<int>.ReturnShared(_buckets, false);
|
||||
}
|
||||
|
||||
_entries = Array.Empty<Entry[]>();
|
||||
_buckets = Array.Empty<int>();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private ref Entry GetEntry(int index)
|
||||
{
|
||||
return ref _entries[index >> _blockShift][index & _blockSizeMinus1];
|
||||
}
|
||||
|
||||
private void ClearEntry(int index)
|
||||
{
|
||||
ref var entry = ref GetEntry(index);
|
||||
entry.HashCode = 0;
|
||||
entry.Next = -1;
|
||||
if (_needsClearing)
|
||||
{
|
||||
entry.Value = default!;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveEntry(int sourceIndex, int targetIndex)
|
||||
{
|
||||
ref var source = ref GetEntry(sourceIndex);
|
||||
ref var target = ref GetEntry(targetIndex);
|
||||
target = source;
|
||||
|
||||
int bucket = source.HashCode & (_buckets.Length - 1);
|
||||
int cur = _buckets[bucket];
|
||||
int prev = -1;
|
||||
while (cur != sourceIndex)
|
||||
{
|
||||
prev = cur;
|
||||
cur = GetEntry(cur).Next;
|
||||
}
|
||||
|
||||
if (prev < 0)
|
||||
{
|
||||
_buckets[bucket] = targetIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
GetEntry(prev).Next = targetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int min)
|
||||
{
|
||||
int requiredBlocks = (min + _blockSizeMinus1) >> _blockShift;
|
||||
if (_entries.Length < requiredBlocks)
|
||||
{
|
||||
var newBlocks = requiredBlocks >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry[]>.RentShared(requiredBlocks)
|
||||
: new Entry[requiredBlocks][];
|
||||
|
||||
Array.Copy(_entries, newBlocks, _entries.Length);
|
||||
|
||||
if (_entries.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<Entry[]>.ReturnShared(_entries, false);
|
||||
}
|
||||
|
||||
_entries = newBlocks;
|
||||
}
|
||||
|
||||
for (int i = 0; i < requiredBlocks; i++)
|
||||
{
|
||||
if (_entries[i] == null)
|
||||
{
|
||||
_entries[i] = _blockSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<Entry>.RentShared(_blockSize)
|
||||
: new Entry[_blockSize];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ResizeBuckets(int newSize)
|
||||
{
|
||||
int[] newBuckets = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<int>.RentShared(newSize)
|
||||
: new int[newSize];
|
||||
Array.Fill(newBuckets, -1);
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
ref var entry = ref GetEntry(i);
|
||||
int bucket = entry.HashCode & (newSize - 1);
|
||||
entry.Next = newBuckets[bucket];
|
||||
newBuckets[bucket] = i;
|
||||
}
|
||||
|
||||
if (_buckets.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<int>.ReturnShared(_buckets, false);
|
||||
}
|
||||
|
||||
_buckets = newBuckets;
|
||||
}
|
||||
|
||||
private int FindIndex(T item, out int hash)
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
hash = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
hash = item.GetHashCode() & int.MaxValue;
|
||||
int bucket = hash & (_buckets.Length - 1);
|
||||
int index = _buckets[bucket];
|
||||
while (index >= 0)
|
||||
{
|
||||
ref var entry = ref GetEntry(index);
|
||||
if (entry.HashCode == hash && EqualityComparer<T>.Default.Equals(entry.Value, item))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
index = entry.Next;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly RecyclableHashSet<T> _set;
|
||||
private int _index;
|
||||
private T? _current;
|
||||
|
||||
internal Enumerator(RecyclableHashSet<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.GetEntry(_index++).Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,323 @@
|
||||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal sealed class RecyclableLinkedList<T> : IEnumerable<T>, IDisposable
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static BiDirectionalRecyclableArrayPoolChunk<T> RentChunk(int size, BiDirectionalRecyclableArrayPoolChunk<T>? previous, bool forHead)
|
||||
{
|
||||
var chunk = BiDirectionalRecyclableArrayPoolChunkPool<T>.Rent();
|
||||
if (chunk.Value.Length < size)
|
||||
{
|
||||
if (chunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(chunk.Value, _needsClearing);
|
||||
}
|
||||
|
||||
chunk.Value = size >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(size)
|
||||
: new T[size];
|
||||
}
|
||||
|
||||
if (forHead)
|
||||
{
|
||||
chunk.Top = size;
|
||||
chunk.Bottom = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
chunk.Top = 0;
|
||||
chunk.Bottom = 0;
|
||||
}
|
||||
|
||||
chunk.Previous = previous;
|
||||
chunk.Next = null;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReturnChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
BiDirectionalRecyclableArrayPoolChunkPool<T>.Return(chunk);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void DisposeChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
BiDirectionalRecyclableArrayPoolChunkPool<T>.Dispose(chunk, _needsClearing);
|
||||
}
|
||||
|
||||
internal BiDirectionalRecyclableArrayPoolChunk<T> _head;
|
||||
internal BiDirectionalRecyclableArrayPoolChunk<T> _tail;
|
||||
internal bool _disposed;
|
||||
internal long _capacity;
|
||||
internal long _count;
|
||||
|
||||
public RecyclableLinkedList(int initialCapacity = RecyclableDefaults.InitialCapacity)
|
||||
{
|
||||
if (initialCapacity < 1)
|
||||
{
|
||||
initialCapacity = 1;
|
||||
}
|
||||
|
||||
if (!BitOperations.IsPow2((uint)initialCapacity))
|
||||
{
|
||||
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
|
||||
}
|
||||
|
||||
_head = RentChunk(initialCapacity, null, false);
|
||||
_tail = _head;
|
||||
_capacity = initialCapacity;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public RecyclableLinkedList(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.InitialCapacity) : this(initialCapacity)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
AddLast(item);
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => checked((int)_count);
|
||||
public long LongCount => _count;
|
||||
|
||||
public void AddLast(T item)
|
||||
{
|
||||
if (_tail.Top == _tail.Value.Length)
|
||||
{
|
||||
GrowTail();
|
||||
}
|
||||
|
||||
_tail.Value[_tail.Top++] = item;
|
||||
_count++;
|
||||
}
|
||||
|
||||
public void AddFirst(T item)
|
||||
{
|
||||
if (_head.Bottom == 0)
|
||||
{
|
||||
GrowHead();
|
||||
}
|
||||
|
||||
_head.Value[--_head.Bottom] = item;
|
||||
_count++;
|
||||
}
|
||||
|
||||
public T RemoveFirst()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_count), "List is empty");
|
||||
}
|
||||
|
||||
_count--;
|
||||
var value = _head.Value[_head.Bottom];
|
||||
if (_needsClearing)
|
||||
{
|
||||
_head.Value[_head.Bottom] = default!;
|
||||
}
|
||||
|
||||
_head.Bottom++;
|
||||
if (_head.Bottom == _head.Top && _head.Next != null)
|
||||
{
|
||||
ReleaseHeadChunk();
|
||||
}
|
||||
else if (_head.Bottom == _head.Top)
|
||||
{
|
||||
_head.Bottom = _head.Top;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public T RemoveLast()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_count), "List is empty");
|
||||
}
|
||||
|
||||
_count--;
|
||||
_tail.Top--;
|
||||
var value = _tail.Value[_tail.Top];
|
||||
if (_needsClearing)
|
||||
{
|
||||
_tail.Value[_tail.Top] = default!;
|
||||
}
|
||||
|
||||
if (_tail.Top == _tail.Bottom && _tail.Previous != null)
|
||||
{
|
||||
ReleaseTailChunk();
|
||||
}
|
||||
else if (_tail.Top == _tail.Bottom)
|
||||
{
|
||||
_tail.Top = _tail.Bottom;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
while (_head.Next != null)
|
||||
{
|
||||
ReleaseHeadChunk();
|
||||
}
|
||||
|
||||
while (_tail.Previous != null)
|
||||
{
|
||||
ReleaseTailChunk();
|
||||
}
|
||||
|
||||
if (_needsClearing && _head.Top > _head.Bottom)
|
||||
{
|
||||
Array.Clear(_head.Value, _head.Bottom, _head.Top - _head.Bottom);
|
||||
}
|
||||
|
||||
_capacity = _head.Value.Length;
|
||||
_head.Bottom = _head.Top;
|
||||
_tail = _head;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
private void GrowTail()
|
||||
{
|
||||
int newSize;
|
||||
if (_capacity >= RecyclableDefaults.MaxPooledBlockSize)
|
||||
{
|
||||
newSize = RecyclableDefaults.MaxPooledBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var doubled = _capacity << 1;
|
||||
newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize);
|
||||
}
|
||||
|
||||
var newChunk = RentChunk(newSize, _tail, false);
|
||||
_tail.Next = newChunk;
|
||||
_tail = newChunk;
|
||||
_capacity += newSize;
|
||||
}
|
||||
|
||||
private void GrowHead()
|
||||
{
|
||||
int newSize;
|
||||
if (_capacity >= RecyclableDefaults.MaxPooledBlockSize)
|
||||
{
|
||||
newSize = RecyclableDefaults.MaxPooledBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var doubled = _capacity << 1;
|
||||
newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize);
|
||||
}
|
||||
|
||||
var newChunk = RentChunk(newSize, null, true);
|
||||
newChunk.Next = _head;
|
||||
_head.Previous = newChunk;
|
||||
_head = newChunk;
|
||||
_capacity += newSize;
|
||||
}
|
||||
|
||||
private void ReleaseHeadChunk()
|
||||
{
|
||||
var toReturn = _head;
|
||||
_head = toReturn.Next!;
|
||||
_head.Previous = null;
|
||||
_capacity -= toReturn.Value.Length;
|
||||
ReturnChunk(toReturn);
|
||||
}
|
||||
|
||||
private void ReleaseTailChunk()
|
||||
{
|
||||
var toReturn = _tail;
|
||||
_tail = toReturn.Previous!;
|
||||
_tail.Next = null;
|
||||
_capacity -= toReturn.Value.Length;
|
||||
ReturnChunk(toReturn);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var chunk = _head;
|
||||
while (chunk != null)
|
||||
{
|
||||
var next = chunk.Next;
|
||||
DisposeChunk(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly BiDirectionalRecyclableArrayPoolChunk<T>? _start;
|
||||
private BiDirectionalRecyclableArrayPoolChunk<T>? _chunk;
|
||||
private int _index;
|
||||
private T? _current;
|
||||
|
||||
internal Enumerator(RecyclableLinkedList<T> list)
|
||||
{
|
||||
_start = list._head;
|
||||
_chunk = list._head;
|
||||
_index = _chunk != null ? _chunk.Bottom : 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public T Current => _current!;
|
||||
object IEnumerator.Current => _current!;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_chunk == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (_index >= _chunk.Top)
|
||||
{
|
||||
_chunk = _chunk.Next;
|
||||
if (_chunk == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_index = _chunk.Bottom;
|
||||
}
|
||||
|
||||
_current = _chunk.Value[_index++];
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_chunk = _start;
|
||||
_index = _chunk != null ? _chunk.Bottom : 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_chunk = null;
|
||||
_current = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,82 @@
|
||||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public static class zRecyclableListAddRange
|
||||
{
|
||||
private static void AddRangeEnumerated<T>(this RecyclableList<T> list, IEnumerable<T> items, int growByCount)
|
||||
{
|
||||
public static class zRecyclableListAddRange
|
||||
{
|
||||
|
||||
private static class AddRangeHelper<T>
|
||||
{
|
||||
private static readonly Action<RecyclableList<T>, object>? _dictionaryAdder;
|
||||
private static readonly Action<RecyclableList<T>, object>? _sortedListAdder;
|
||||
private static readonly Type? _dictionaryType;
|
||||
private static readonly Type? _sortedListType;
|
||||
|
||||
static AddRangeHelper()
|
||||
{
|
||||
Type elementType = typeof(T);
|
||||
if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
|
||||
{
|
||||
Type[] args = elementType.GetGenericArguments();
|
||||
_dictionaryType = typeof(RecyclableDictionary<,>).MakeGenericType(args);
|
||||
MethodInfo? method = typeof(zRecyclableListAddRange).GetMethod(
|
||||
nameof(AddRange),
|
||||
BindingFlags.NonPublic | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: new[] { typeof(RecyclableList<>).MakeGenericType(elementType), _dictionaryType },
|
||||
modifiers: null);
|
||||
if (method != null)
|
||||
{
|
||||
var listParam = Expression.Parameter(typeof(RecyclableList<T>));
|
||||
var objParam = Expression.Parameter(typeof(object));
|
||||
var call = Expression.Call(method, listParam, Expression.Convert(objParam, _dictionaryType));
|
||||
_dictionaryAdder = Expression.Lambda<Action<RecyclableList<T>, object>>(call, listParam, objParam).Compile();
|
||||
}
|
||||
}
|
||||
else if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(ValueTuple<,>))
|
||||
{
|
||||
Type[] args = elementType.GetGenericArguments();
|
||||
_sortedListType = typeof(RecyclableSortedList<,>).MakeGenericType(args);
|
||||
MethodInfo? method = typeof(zRecyclableListAddRange).GetMethod(
|
||||
nameof(AddRange),
|
||||
BindingFlags.NonPublic | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: new[] { typeof(RecyclableList<>).MakeGenericType(elementType), _sortedListType },
|
||||
modifiers: null);
|
||||
if (method != null)
|
||||
{
|
||||
var listParam = Expression.Parameter(typeof(RecyclableList<T>));
|
||||
var objParam = Expression.Parameter(typeof(object));
|
||||
var call = Expression.Call(method, listParam, Expression.Convert(objParam, _sortedListType));
|
||||
_sortedListAdder = Expression.Lambda<Action<RecyclableList<T>, object>>(call, listParam, objParam).Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryAddRange(RecyclableList<T> list, IEnumerable<T> items)
|
||||
{
|
||||
if (_dictionaryAdder != null && _dictionaryType!.IsInstanceOfType(items))
|
||||
{
|
||||
_dictionaryAdder(list, items);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items))
|
||||
{
|
||||
_sortedListAdder(list, items);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddRangeEnumerated<T>(this RecyclableList<T> list, IEnumerable<T> items, int growByCount)
|
||||
{
|
||||
var enumerator = items.GetEnumerator();
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
@@ -57,8 +126,9 @@ namespace Recyclable.Collections
|
||||
list._count = currentItemsCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static void AddRange<T>(this RecyclableList<T> list, in Array items)
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
[VersionedCollectionsGenerator.RewriteForVersioned]
|
||||
public static void AddRange<T>(this RecyclableList<T> list, in Array items)
|
||||
{
|
||||
if (list._capacity < list._count + items.Length)
|
||||
{
|
||||
@@ -67,7 +137,9 @@ namespace Recyclable.Collections
|
||||
|
||||
Array.Copy(items, items.GetLowerBound(0), list._memoryBlock, list._count, items.Length);
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -80,7 +152,9 @@ namespace Recyclable.Collections
|
||||
|
||||
new ReadOnlySpan<T>(items).CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length));
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -93,7 +167,9 @@ namespace Recyclable.Collections
|
||||
|
||||
items.CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length));
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -106,7 +182,9 @@ namespace Recyclable.Collections
|
||||
|
||||
items.CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length));
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -119,7 +197,9 @@ namespace Recyclable.Collections
|
||||
|
||||
items.CopyTo(list._memoryBlock, list._count);
|
||||
list._count += items.Count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -132,7 +212,9 @@ namespace Recyclable.Collections
|
||||
|
||||
items.CopyTo(list._memoryBlock, list._count);
|
||||
list._count += items.Count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -145,7 +227,9 @@ namespace Recyclable.Collections
|
||||
|
||||
items.CopyTo(list._memoryBlock, list._count);
|
||||
list._count += items.Count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -158,16 +242,18 @@ namespace Recyclable.Collections
|
||||
|
||||
new ReadOnlySpan<T>(items._memoryBlock, 0, items._count).CopyTo(new Span<T>(list._memoryBlock, list._count, items._count));
|
||||
list._count += items._count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static void AddRange<T>(this RecyclableList<T> list, RecyclableLongList<T> items)
|
||||
{
|
||||
if (items._longCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public static void AddRange<T>(this RecyclableList<T> list, RecyclableLongList<T> items)
|
||||
{
|
||||
if (items._longCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int oldCount = list._count;
|
||||
long sourceItemsCount = items._longCount;
|
||||
@@ -208,9 +294,281 @@ namespace Recyclable.Collections
|
||||
itemsSpan.CopyTo(targetSpan);
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
list._version++;
|
||||
}
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableList<T> list, RecyclableHashSet<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = items._count,
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<T>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
Span<T> targetSpan = new(list._memoryBlock, oldCount, sourceCount);
|
||||
var entries = items._entries;
|
||||
int shift = items._blockShift,
|
||||
mask = items._blockSizeMinus1;
|
||||
|
||||
for (int i = 0; i < sourceCount; i++)
|
||||
{
|
||||
targetSpan[i] = entries[i >> shift][i & mask].Value;
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableList<T> list, RecyclableStack<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (items._count > int.MaxValue - list._count)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), "The total number of items exceeds the capacity of RecyclableList.");
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = checked((int)items._count),
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<T>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
Span<T> targetSpan = new(list._memoryBlock, oldCount, sourceCount);
|
||||
int index = 0;
|
||||
var chunk = items._current;
|
||||
while (chunk != null)
|
||||
{
|
||||
var array = chunk.Value;
|
||||
for (int i = chunk.Index - 1; i >= 0; i--)
|
||||
{
|
||||
targetSpan[index++] = array[i];
|
||||
}
|
||||
|
||||
chunk = chunk.Previous;
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableList<T> list, RecyclableSortedSet<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = items._count,
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<T>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
new ReadOnlySpan<T>(items._items, 0, sourceCount).CopyTo(new Span<T>(list._memoryBlock, oldCount, sourceCount));
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableList<T> list, RecyclableLinkedList<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (items._count > int.MaxValue - list._count)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), "The total number of items exceeds the capacity of RecyclableList.");
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = checked((int)items._count),
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<T>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
Span<T> targetSpan = new(list._memoryBlock, oldCount, sourceCount);
|
||||
int index = 0;
|
||||
var chunk = items._head;
|
||||
while (chunk != null)
|
||||
{
|
||||
for (int i = chunk.Bottom; i < chunk.Top; i++)
|
||||
{
|
||||
targetSpan[index++] = chunk.Value[i];
|
||||
}
|
||||
|
||||
chunk = chunk.Next;
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableList<T> list, RecyclablePriorityQueue<T> items)
|
||||
{
|
||||
if (items._size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = items._size,
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<T>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
new ReadOnlySpan<T>(items._heap, 0, sourceCount).CopyTo(new Span<T>(list._memoryBlock, oldCount, sourceCount));
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableList<T> list, RecyclableQueue<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (items._count > int.MaxValue - list._count)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(items), "The total number of items exceeds the capacity of RecyclableList.");
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = checked((int)items._count),
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<T>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
Span<T> targetSpan = new(list._memoryBlock, oldCount, sourceCount);
|
||||
int index = 0;
|
||||
var chunk = items._head;
|
||||
while (chunk != null)
|
||||
{
|
||||
for (int i = chunk.Bottom; i < chunk.Top; i++)
|
||||
{
|
||||
targetSpan[index++] = chunk.Value[i];
|
||||
}
|
||||
|
||||
chunk = chunk.Next;
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<TKey, TValue>(this RecyclableList<KeyValuePair<TKey, TValue>> list, RecyclableDictionary<TKey, TValue> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = items._count,
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<KeyValuePair<TKey, TValue>>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
Span<KeyValuePair<TKey, TValue>> targetSpan = new(list._memoryBlock, oldCount, sourceCount);
|
||||
var entries = items._entries;
|
||||
int shift = items._blockShift,
|
||||
mask = items._blockSizeMinus1;
|
||||
|
||||
for (int i = 0; i < sourceCount; i++)
|
||||
{
|
||||
ref var entry = ref entries[i >> shift][i & mask];
|
||||
targetSpan[i] = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<TKey, TValue>(this RecyclableList<(TKey Key, TValue Value)> list, RecyclableSortedList<TKey, TValue> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int oldCount = list._count,
|
||||
sourceCount = items._count,
|
||||
targetCapacity = oldCount + sourceCount;
|
||||
|
||||
if (list._capacity < targetCapacity)
|
||||
{
|
||||
list._capacity = RecyclableListHelpers<(TKey Key, TValue Value)>.EnsureCapacity(list, oldCount, checked((int)BitOperations.RoundUpToPowerOf2((uint)targetCapacity)));
|
||||
}
|
||||
|
||||
Span<(TKey Key, TValue Value)> targetSpan = new(list._memoryBlock, oldCount, sourceCount);
|
||||
for (int i = 0; i < sourceCount; i++)
|
||||
{
|
||||
targetSpan[i] = (items._keys[i], items._values[i]);
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static IEnumerator AddRange<T>(this RecyclableList<T> list, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
@@ -291,27 +649,58 @@ namespace Recyclable.Collections
|
||||
{
|
||||
AddRange(list, sourceList);
|
||||
}
|
||||
else if (items is ICollection<T> sourceICollection)
|
||||
{
|
||||
AddRange(list, sourceICollection);
|
||||
}
|
||||
else if (items is ICollection sourceICollectionWithObjects)
|
||||
{
|
||||
AddRange(list, sourceICollectionWithObjects);
|
||||
}
|
||||
else if (items is IReadOnlyList<T> sourceIReadOnlyList)
|
||||
{
|
||||
AddRange(list, sourceIReadOnlyList);
|
||||
}
|
||||
else if (items is ICollection<T> sourceICollection)
|
||||
{
|
||||
AddRange(list, sourceICollection);
|
||||
}
|
||||
else if (items is ICollection sourceICollectionWithObjects)
|
||||
{
|
||||
AddRange(list, sourceICollectionWithObjects);
|
||||
}
|
||||
else if (items is RecyclableHashSet<T> sourceHashSet)
|
||||
{
|
||||
AddRange(list, sourceHashSet);
|
||||
}
|
||||
else if (items is RecyclableStack<T> sourceStack)
|
||||
{
|
||||
AddRange(list, sourceStack);
|
||||
}
|
||||
else if (items is RecyclableSortedSet<T> sourceSortedSet)
|
||||
{
|
||||
AddRange(list, sourceSortedSet);
|
||||
}
|
||||
else if (items is RecyclableLinkedList<T> sourceLinkedList)
|
||||
{
|
||||
AddRange(list, sourceLinkedList);
|
||||
}
|
||||
else if (items is RecyclablePriorityQueue<T> sourcePriorityQueue)
|
||||
{
|
||||
AddRange(list, sourcePriorityQueue);
|
||||
}
|
||||
else if (items is RecyclableQueue<T> sourceQueue)
|
||||
{
|
||||
AddRange(list, sourceQueue);
|
||||
}
|
||||
else if (AddRangeHelper<T>.TryAddRange(list, items))
|
||||
{
|
||||
}
|
||||
else if (items is IReadOnlyList<T> sourceIReadOnlyList)
|
||||
{
|
||||
AddRange(list, sourceIReadOnlyList);
|
||||
}
|
||||
else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity) && requiredAdditionalCapacity != 0)
|
||||
{
|
||||
AddRangeWithKnownCount(list, items, list._count, requiredAdditionalCapacity);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
AddRangeEnumerated(list, items, growByCount);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
list._count += requiredAdditionalCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -94,7 +96,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
|
||||
Array.Copy(items, items.GetLowerBound(0), memoryBlock, index, items.Length);
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -109,7 +113,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
|
||||
new ReadOnlySpan<T>(items).CopyTo(new Span<T>(memoryBlock, index, items.Length));
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -124,7 +130,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
|
||||
items.CopyTo(new Span<T>(memoryBlock, index, items.Length));
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -139,7 +147,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
|
||||
items.CopyTo(new Span<T>(memoryBlock, index, items.Length));
|
||||
list._count += items.Length;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -154,7 +164,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index);
|
||||
items.CopyTo(memoryBlock, index);
|
||||
list._count += items.Count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -169,7 +181,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index);
|
||||
items.CopyTo(list._memoryBlock, index);
|
||||
list._count += items.Count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -184,7 +198,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index);
|
||||
items.CopyTo(memoryBlock, index);
|
||||
list._count += items.Count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -199,7 +215,9 @@ namespace Recyclable.Collections
|
||||
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index);
|
||||
new ReadOnlySpan<T>(items._memoryBlock, 0, items._count).CopyTo(new(list._memoryBlock, index, items._count));
|
||||
list._count += items._count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -251,7 +269,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
list._count = targetCapacity;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static IEnumerator InsertRange<T>(this RecyclableList<T> list, int index, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength)
|
||||
|
||||
@@ -101,7 +101,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
list._count -= foundItemsCount;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
return foundItemsCount;
|
||||
}
|
||||
|
||||
@@ -125,43 +127,57 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
list._count -= count;
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Reverse<T>(this RecyclableList<T> list)
|
||||
{
|
||||
Array.Reverse(list._memoryBlock, 0, list._count);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Reverse<T>(this RecyclableList<T> list, int index, int count)
|
||||
{
|
||||
Array.Reverse(list._memoryBlock, index, count);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Sort<T>(this RecyclableList<T> list)
|
||||
{
|
||||
Array.Sort(list._memoryBlock, 0, list._count);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Sort<T>(this RecyclableList<T> list, Comparison<T> comparison)
|
||||
{
|
||||
Array.Sort(list._memoryBlock, 0, list._count, new ComparisonToComparerAdapter<T>(comparison));
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Sort<T>(this RecyclableList<T> list, IComparer<T>? comparer)
|
||||
{
|
||||
Array.Sort(list._memoryBlock, 0, list._count, comparer);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Sort<T>(this RecyclableList<T> list, int index, int count, IComparer<T>? comparer)
|
||||
{
|
||||
Array.Sort(list._memoryBlock, index, count, comparer);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static T[] ToArray<T>(this RecyclableList<T> list)
|
||||
|
||||
@@ -6,9 +6,10 @@ namespace Recyclable.Collections
|
||||
{
|
||||
public partial class RecyclableList<T>
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private static readonly bool _isReferenceType = !typeof(T).IsValueType;
|
||||
|
||||
private int _currentItemIndex;
|
||||
|
||||
@@ -3,8 +3,9 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public static class RecyclableListExtensions
|
||||
{
|
||||
[VersionedCollectionsGenerator.GenerateVersioned]
|
||||
public static class RecyclableListExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RecyclableList<T> ToRecyclableList<T>(this Array values) => new(values);
|
||||
|
||||
|
||||
@@ -6,11 +6,13 @@ namespace Recyclable.Collections
|
||||
{
|
||||
public partial class RecyclableList<T> : IRecyclableVersionedList<T>
|
||||
{
|
||||
VersionedEnumerator IRecyclableVersionedList<T>.GetEnumerator() => new(this);
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
VersionedEnumerator IRecyclableVersionedList<T>.GetEnumerator() => new(this);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct VersionedEnumerator : IEnumerator<T>
|
||||
{
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
public struct VersionedEnumerator : IEnumerator<T>
|
||||
{
|
||||
private int _currentItemIndex;
|
||||
private readonly ulong _enumeratorVersion;
|
||||
private readonly RecyclableList<T> _list;
|
||||
|
||||
@@ -5,8 +5,9 @@ using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
[Serializable]
|
||||
public sealed partial class RecyclableList<T> : IList<T>, IReadOnlyList<T>, IDisposable
|
||||
[Serializable]
|
||||
[VersionedCollectionsGenerator.GenerateVersioned]
|
||||
public sealed partial class RecyclableList<T> : IList<T>, IReadOnlyList<T>, IDisposable
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
|
||||
@@ -17,7 +18,8 @@ namespace Recyclable.Collections
|
||||
#nullable restore
|
||||
|
||||
internal ulong _version;
|
||||
public ulong Version => _version;
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
public ulong Version => _version;
|
||||
|
||||
public RecyclableList()
|
||||
{
|
||||
@@ -172,7 +174,9 @@ namespace Recyclable.Collections
|
||||
set
|
||||
{
|
||||
_memoryBlock[index] = value;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,17 +189,19 @@ namespace Recyclable.Collections
|
||||
if (_capacity != value)
|
||||
{
|
||||
_capacity = RecyclableListHelpers<T>.EnsureCapacity(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)value)));
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal int _count;
|
||||
public int Count
|
||||
{
|
||||
get => _count;
|
||||
set
|
||||
{
|
||||
internal int _count;
|
||||
public int Count
|
||||
{
|
||||
get => _count;
|
||||
set
|
||||
{
|
||||
if (_count != value)
|
||||
{
|
||||
if (value > _capacity)
|
||||
@@ -204,12 +210,30 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
_count = value;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
/// <summary>
|
||||
/// Ensures that the capacity of this list is at least the specified value.
|
||||
/// </summary>
|
||||
/// <param name="capacity">The minimum capacity to ensure.</param>
|
||||
/// <returns>The new capacity of the list.</returns>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int EnsureCapacity(int capacity)
|
||||
{
|
||||
if (capacity > _capacity)
|
||||
{
|
||||
_capacity = RecyclableListHelpers<T>.EnsureCapacity(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)capacity)));
|
||||
}
|
||||
|
||||
return _capacity;
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(T item)
|
||||
@@ -227,7 +251,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
_memoryBlock[_count++] = item;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public T[] AsArray => _memoryBlock;
|
||||
@@ -246,7 +272,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -279,7 +307,9 @@ namespace Recyclable.Collections
|
||||
|
||||
_memoryBlock[index] = item;
|
||||
_count++;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -299,7 +329,9 @@ namespace Recyclable.Collections
|
||||
_memoryBlock[_count] = default;
|
||||
}
|
||||
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -326,21 +358,40 @@ namespace Recyclable.Collections
|
||||
_memoryBlock[_count] = default;
|
||||
}
|
||||
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
#if WITH_VERSIONING
|
||||
public VersionedEnumerator GetEnumerator() => new(this);
|
||||
#else
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
#if WITH_VERSIONING
|
||||
IEnumerator IEnumerable.GetEnumerator() => new VersionedEnumerator(this);
|
||||
#else
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
#if WITH_VERSIONING
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new VersionedEnumerator(this);
|
||||
#else
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
#endif
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
if (_count != 0)
|
||||
{
|
||||
if (_needsClearing)
|
||||
|
||||
@@ -1,13 +1,83 @@
|
||||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public static class zRecyclableLongListAddRange
|
||||
{
|
||||
private static void AddRangeEnumerated<T>(this RecyclableLongList<T> targetList, IEnumerable<T> items)
|
||||
{
|
||||
public static class zRecyclableLongListAddRange
|
||||
{
|
||||
|
||||
private static class AddRangeHelper<T>
|
||||
{
|
||||
private static readonly Action<RecyclableLongList<T>, object>? _dictionaryAdder;
|
||||
private static readonly Action<RecyclableLongList<T>, object>? _sortedListAdder;
|
||||
private static readonly Type? _dictionaryType;
|
||||
private static readonly Type? _sortedListType;
|
||||
|
||||
static AddRangeHelper()
|
||||
{
|
||||
Type elementType = typeof(T);
|
||||
if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
|
||||
{
|
||||
Type[] args = elementType.GetGenericArguments();
|
||||
_dictionaryType = typeof(RecyclableDictionary<,>).MakeGenericType(args);
|
||||
MethodInfo? method = typeof(zRecyclableLongListAddRange).GetMethod(
|
||||
nameof(AddRange),
|
||||
BindingFlags.NonPublic | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: new[] { typeof(RecyclableLongList<>).MakeGenericType(elementType), _dictionaryType },
|
||||
modifiers: null);
|
||||
if (method != null)
|
||||
{
|
||||
var listParam = Expression.Parameter(typeof(RecyclableLongList<T>));
|
||||
var objParam = Expression.Parameter(typeof(object));
|
||||
var call = Expression.Call(method, listParam, Expression.Convert(objParam, _dictionaryType));
|
||||
_dictionaryAdder = Expression.Lambda<Action<RecyclableLongList<T>, object>>(call, listParam, objParam).Compile();
|
||||
}
|
||||
}
|
||||
else if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(ValueTuple<,>))
|
||||
{
|
||||
Type[] args = elementType.GetGenericArguments();
|
||||
_sortedListType = typeof(RecyclableSortedList<,>).MakeGenericType(args);
|
||||
MethodInfo? method = typeof(zRecyclableLongListAddRange).GetMethod(
|
||||
nameof(AddRange),
|
||||
BindingFlags.NonPublic | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: new[] { typeof(RecyclableLongList<>).MakeGenericType(elementType), _sortedListType },
|
||||
modifiers: null);
|
||||
if (method != null)
|
||||
{
|
||||
var listParam = Expression.Parameter(typeof(RecyclableLongList<T>));
|
||||
var objParam = Expression.Parameter(typeof(object));
|
||||
var call = Expression.Call(method, listParam, Expression.Convert(objParam, _sortedListType));
|
||||
_sortedListAdder = Expression.Lambda<Action<RecyclableLongList<T>, object>>(call, listParam, objParam).Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryAddRange(RecyclableLongList<T> list, IEnumerable<T> items)
|
||||
{
|
||||
if (_dictionaryAdder != null && _dictionaryType!.IsInstanceOfType(items))
|
||||
{
|
||||
_dictionaryAdder(list, items);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items))
|
||||
{
|
||||
_sortedListAdder(list, items);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddRangeEnumerated<T>(this RecyclableLongList<T> targetList, IEnumerable<T> items)
|
||||
{
|
||||
using IEnumerator<T> enumerator = items.GetEnumerator();
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
@@ -56,7 +126,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
targetList._capacity = capacity;
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -116,10 +188,13 @@ namespace Recyclable.Collections
|
||||
targetList._nextItemBlockIndex = targetBlockIdx;
|
||||
targetList._nextItemIndex = targetItemIdx;
|
||||
targetList._lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1);
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, in Array items)
|
||||
[VersionedCollectionsGenerator.RewriteForVersioned]
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, in Array items)
|
||||
{
|
||||
if (items.LongLength == 0)
|
||||
{
|
||||
@@ -170,7 +245,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, ICollection items)
|
||||
@@ -235,7 +312,9 @@ namespace Recyclable.Collections
|
||||
RecyclableArrayPool<T>.ReturnShared(itemsBuffer, RecyclableLongList<T>._needsClearing);
|
||||
}
|
||||
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, ICollection<T> items)
|
||||
@@ -303,7 +382,9 @@ namespace Recyclable.Collections
|
||||
RecyclableArrayPool<T>.ReturnShared(itemsBuffer, RecyclableLongList<T>._needsClearing);
|
||||
}
|
||||
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, IEnumerable source)
|
||||
@@ -405,27 +486,56 @@ namespace Recyclable.Collections
|
||||
targetList.AddRange(sourceICollection);
|
||||
return;
|
||||
|
||||
case ICollection sourceICollectionWithObjects:
|
||||
targetList.AddRange(sourceICollectionWithObjects);
|
||||
return;
|
||||
case ICollection sourceICollectionWithObjects:
|
||||
targetList.AddRange(sourceICollectionWithObjects);
|
||||
return;
|
||||
|
||||
case IReadOnlyList<T> sourceIReadOnlyList:
|
||||
targetList.AddRange(sourceIReadOnlyList);
|
||||
return;
|
||||
case IReadOnlyList<T> sourceIReadOnlyList:
|
||||
targetList.AddRange(sourceIReadOnlyList);
|
||||
return;
|
||||
|
||||
default:
|
||||
if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
|
||||
{
|
||||
targetList.AddRangeWithKnownCount(items, requiredAdditionalCapacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetList.AddRangeEnumerated(items);
|
||||
}
|
||||
case RecyclableHashSet<T> sourceHashSet:
|
||||
targetList.AddRange(sourceHashSet);
|
||||
return;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
case RecyclableStack<T> sourceStack:
|
||||
targetList.AddRange(sourceStack);
|
||||
return;
|
||||
|
||||
case RecyclableSortedSet<T> sourceSortedSet:
|
||||
targetList.AddRange(sourceSortedSet);
|
||||
return;
|
||||
|
||||
case RecyclableLinkedList<T> sourceLinkedList:
|
||||
targetList.AddRange(sourceLinkedList);
|
||||
return;
|
||||
|
||||
case RecyclablePriorityQueue<T> sourcePriorityQueue:
|
||||
targetList.AddRange(sourcePriorityQueue);
|
||||
return;
|
||||
|
||||
case RecyclableQueue<T> sourceQueue:
|
||||
targetList.AddRange(sourceQueue);
|
||||
return;
|
||||
|
||||
default:
|
||||
if (AddRangeHelper<T>.TryAddRange(targetList, items))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity))
|
||||
{
|
||||
targetList.AddRangeWithKnownCount(items, requiredAdditionalCapacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
targetList.AddRangeEnumerated(items);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, IReadOnlyList<T> items)
|
||||
{
|
||||
@@ -517,7 +627,9 @@ namespace Recyclable.Collections
|
||||
targetList._nextItemBlockIndex = targetBlockIndex;
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._lastBlockWithData = targetBlockIndex - (targetList._nextItemIndex > 0 ? 0 : 1);
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, ReadOnlySpan<T> items)
|
||||
@@ -572,7 +684,9 @@ namespace Recyclable.Collections
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._lastBlockWithData = targetBlockIndex;
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableList<T> items)
|
||||
@@ -629,7 +743,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableLongList<T> items)
|
||||
@@ -702,7 +818,9 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, Span<T> items)
|
||||
@@ -757,15 +875,17 @@ namespace Recyclable.Collections
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._lastBlockWithData = targetBlockIndex;
|
||||
targetList._version++;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, in T[] items)
|
||||
{
|
||||
if (items.LongLength == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
public static void AddRange<T>(this RecyclableLongList<T> targetList, in T[] items)
|
||||
{
|
||||
if (items.LongLength == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int targetBlockIndex = targetList._nextItemBlockIndex;
|
||||
|
||||
@@ -812,8 +932,362 @@ namespace Recyclable.Collections
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._lastBlockWithData = targetBlockIndex;
|
||||
targetList._version++;
|
||||
}
|
||||
targetList._lastBlockWithData = targetBlockIndex;
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableHashSet<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._count,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<T>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
var entries = items._entries;
|
||||
int shift = items._blockShift,
|
||||
mask = items._blockSizeMinus1;
|
||||
|
||||
for (long i = 0; i < sourceCount; i++)
|
||||
{
|
||||
blocks[blockIndex][itemIndex++] = entries[i >> shift][(int)(i & mask)].Value;
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableStack<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._count,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<T>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
var chunk = items._current;
|
||||
while (chunk != null)
|
||||
{
|
||||
var array = chunk.Value;
|
||||
for (int i = chunk.Index - 1; i >= 0; i--)
|
||||
{
|
||||
blocks[blockIndex][itemIndex++] = array[i];
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
chunk = chunk.Previous;
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableSortedSet<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._count,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<T>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
int idx = 0;
|
||||
while (idx < items._count)
|
||||
{
|
||||
blocks[blockIndex][itemIndex++] = items._items[idx++];
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableLinkedList<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._count,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<T>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
var chunk = items._head;
|
||||
while (chunk != null)
|
||||
{
|
||||
for (int i = chunk.Bottom; i < chunk.Top; i++)
|
||||
{
|
||||
blocks[blockIndex][itemIndex++] = chunk.Value[i];
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
chunk = chunk.Next;
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclablePriorityQueue<T> items)
|
||||
{
|
||||
if (items._size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._size,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<T>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
int idx = 0;
|
||||
while (idx < items._size)
|
||||
{
|
||||
blocks[blockIndex][itemIndex++] = items._heap[idx++];
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableQueue<T> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._count,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<T>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
var chunkQ = items._head;
|
||||
while (chunkQ != null)
|
||||
{
|
||||
for (int i = chunkQ.Bottom; i < chunkQ.Top; i++)
|
||||
{
|
||||
blocks[blockIndex][itemIndex++] = chunkQ.Value[i];
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
chunkQ = chunkQ.Next;
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<TKey, TValue>(this RecyclableLongList<KeyValuePair<TKey, TValue>> targetList, RecyclableDictionary<TKey, TValue> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._count,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<KeyValuePair<TKey, TValue>>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
var entries = items._entries;
|
||||
int shift = items._blockShift,
|
||||
mask = items._blockSizeMinus1;
|
||||
|
||||
for (long i = 0; i < sourceCount; i++)
|
||||
{
|
||||
ref var entry = ref entries[i >> shift][(int)(i & mask)];
|
||||
blocks[blockIndex][itemIndex++] = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<TKey, TValue>(this RecyclableLongList<(TKey Key, TValue Value)> targetList, RecyclableSortedList<TKey, TValue> items)
|
||||
{
|
||||
if (items._count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
long sourceCount = items._count,
|
||||
targetCapacity = targetList._longCount + sourceCount;
|
||||
|
||||
if (targetList._capacity < targetCapacity)
|
||||
{
|
||||
targetList._capacity = RecyclableLongList<(TKey Key, TValue Value)>.Helpers.Resize(targetList, targetList._blockSize, targetList._blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)targetCapacity)));
|
||||
}
|
||||
|
||||
var blocks = targetList._memoryBlocks;
|
||||
int blockIndex = targetList._nextItemBlockIndex,
|
||||
itemIndex = targetList._nextItemIndex,
|
||||
blockSize = targetList._blockSize;
|
||||
|
||||
int idx2 = 0;
|
||||
while (idx2 < items._count)
|
||||
{
|
||||
blocks[blockIndex][itemIndex++] = (items._keys[idx2], items._values[idx2]);
|
||||
idx2++;
|
||||
if (itemIndex == blockSize)
|
||||
{
|
||||
itemIndex = 0;
|
||||
blockIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
targetList._longCount = targetCapacity;
|
||||
targetList._nextItemBlockIndex = blockIndex;
|
||||
targetList._nextItemIndex = itemIndex;
|
||||
targetList._lastBlockWithData = blockIndex - (itemIndex > 0 ? 0 : 1);
|
||||
#if WITH_VERSIONING
|
||||
targetList._version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
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);
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Sort<T>(this RecyclableLongList<T> list, Comparison<T> comparison)
|
||||
{
|
||||
QuickSortExtensions<T>.QuickSort(list, new ComparisonToComparerAdapter<T>(comparison), new SystemRandomNumberGenerator());
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void Sort<T>(this RecyclableLongList<T> list, IComparer<T>? comparer)
|
||||
{
|
||||
QuickSortExtensions<T>.QuickSort(list, comparer ?? Comparer<T>.Default, new SystemRandomNumberGenerator());
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
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());
|
||||
#if WITH_VERSIONING
|
||||
list._version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,8 +5,9 @@ namespace Recyclable.Collections
|
||||
{
|
||||
public partial class RecyclableLongList<T>
|
||||
{
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
#nullable disable
|
||||
private static readonly T _default = default;
|
||||
#nullable restore
|
||||
|
||||
@@ -3,8 +3,9 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public static class RecyclableLongListExtensions
|
||||
{
|
||||
[VersionedCollectionsGenerator.GenerateVersioned]
|
||||
public static class RecyclableLongListExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static RecyclableLongList<T> ToRecyclableLongList<T>(this Array values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Recyclable.Collections.Pools;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
@@ -76,11 +77,11 @@ namespace Recyclable.Collections
|
||||
return;
|
||||
}
|
||||
|
||||
ReadOnlySpan<T> sourceBlockMemory;
|
||||
Span<T> destinationArrayMemory;
|
||||
// TODO: Replace with bit-shifting
|
||||
int startingBlockIndex = (int)(startingIndex / blockSize);
|
||||
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize != 0 ? 1 : 0)) - 1;
|
||||
ReadOnlySpan<T> sourceBlockMemory;
|
||||
Span<T> destinationArrayMemory;
|
||||
int blockSizePow2Shift = BitOperations.TrailingZeroCount((uint)blockSize);
|
||||
int startingBlockIndex = (int)(startingIndex >> blockSizePow2Shift);
|
||||
int lastBlockIndex = (int)((itemsCount >> blockSizePow2Shift) + ((itemsCount & (blockSize - 1)) != 0 ? 1 : 0)) - 1;
|
||||
ReadOnlySpan<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1);
|
||||
destinationArrayMemory = new Span<T>(destinationArray, destinationArrayIndex, (int)Math.Min(destinationArray.Length - destinationArrayIndex, itemsCount));
|
||||
int memoryBlockIndex;
|
||||
@@ -105,9 +106,9 @@ namespace Recyclable.Collections
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Replace with bit-shifting
|
||||
int startingBlockIndex = (int)(startingIndex / blockSize);
|
||||
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize != 0 ? 1 : 0)) - 1;
|
||||
int blockSizePow2Shift = BitOperations.TrailingZeroCount((uint)blockSize);
|
||||
int startingBlockIndex = (int)(startingIndex >> blockSizePow2Shift);
|
||||
int lastBlockIndex = (int)((itemsCount >> blockSizePow2Shift) + ((itemsCount & (blockSize - 1)) != 0 ? 1 : 0)) - 1;
|
||||
|
||||
var destinationIndex = destinationArrayIndex;
|
||||
ReadOnlySpan<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1);
|
||||
|
||||
@@ -5,11 +5,14 @@ namespace Recyclable.Collections
|
||||
{
|
||||
public partial class RecyclableLongList<T> : IRecyclableVersionedLongList<T>
|
||||
{
|
||||
public VersionedEnumerator GetVersionedEnumerator() => new(this);
|
||||
VersionedEnumerator IRecyclableVersionedLongList<T>.GetEnumerator() => new(this);
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
public VersionedEnumerator GetVersionedEnumerator() => new(this);
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
VersionedEnumerator IRecyclableVersionedLongList<T>.GetEnumerator() => new(this);
|
||||
|
||||
public struct VersionedEnumerator : IEnumerator<T>
|
||||
{
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
public struct VersionedEnumerator : IEnumerator<T>
|
||||
{
|
||||
#nullable disable
|
||||
private static readonly T _default = default;
|
||||
#nullable restore
|
||||
|
||||
@@ -9,7 +9,8 @@ using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
public partial class RecyclableLongList<T> : IList<T>, IReadOnlyList<T>, IDisposable
|
||||
[VersionedCollectionsGenerator.GenerateVersioned]
|
||||
public partial class RecyclableLongList<T> : IList<T>, IReadOnlyList<T>, IDisposable
|
||||
{
|
||||
private static readonly bool _defaultIsNull = default(T) == null;
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
@@ -25,7 +26,7 @@ namespace Recyclable.Collections
|
||||
#nullable restore
|
||||
internal int _nextItemBlockIndex;
|
||||
internal int _nextItemIndex;
|
||||
internal ulong _version;
|
||||
internal ulong _version;
|
||||
|
||||
public int BlockSize => _blockSize;
|
||||
public int BlockSizeMinus1 => _blockSizeMinus1;
|
||||
@@ -35,15 +36,19 @@ namespace Recyclable.Collections
|
||||
get => _capacity;
|
||||
private set
|
||||
{
|
||||
_capacity = value;
|
||||
_version++;
|
||||
if (_capacity != value)
|
||||
{
|
||||
_capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)value)));
|
||||
_version++;
|
||||
}
|
||||
}
|
||||
}
|
||||
_capacity = value;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
if (_capacity != value)
|
||||
{
|
||||
_capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)value)));
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => checked((int)_longCount);
|
||||
public bool IsReadOnly => false;
|
||||
@@ -51,7 +56,8 @@ namespace Recyclable.Collections
|
||||
public long LongCount => _longCount;
|
||||
public int NextItemBlockIndex => _nextItemBlockIndex;
|
||||
public int NextItemIndex => _nextItemIndex;
|
||||
public ulong Version => _version;
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
public ulong Version => _version;
|
||||
|
||||
public RecyclableLongList(int minBlockSize = RecyclableDefaults.BlockSize, long initialCapacity = RecyclableDefaults.InitialCapacity)
|
||||
{
|
||||
@@ -251,20 +257,24 @@ namespace Recyclable.Collections
|
||||
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1];
|
||||
set
|
||||
{
|
||||
new Span<T>(_memoryBlocks[(int)(index >> _blockSizePow2BitShift)])[(int)(index & _blockSizeMinus1)] = value;
|
||||
_version++;
|
||||
}
|
||||
}
|
||||
new Span<T>(_memoryBlocks[(int)(index >> _blockSizePow2BitShift)])[(int)(index & _blockSizeMinus1)] = value;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1];
|
||||
set
|
||||
{
|
||||
new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value;
|
||||
_version++;
|
||||
}
|
||||
}
|
||||
new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public T[][] AsArray => _memoryBlocks;
|
||||
|
||||
@@ -292,9 +302,11 @@ namespace Recyclable.Collections
|
||||
_lastBlockWithData++;
|
||||
}
|
||||
|
||||
_longCount++;
|
||||
_version++;
|
||||
}
|
||||
_longCount++;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
@@ -326,9 +338,11 @@ namespace Recyclable.Collections
|
||||
_nextItemBlockIndex = 0;
|
||||
_nextItemIndex = 0;
|
||||
_lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex;
|
||||
_longCount = 0;
|
||||
_version++;
|
||||
}
|
||||
_longCount = 0;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Contains(T item)
|
||||
@@ -358,9 +372,24 @@ namespace Recyclable.Collections
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) => Helpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex);
|
||||
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
#if WITH_VERSIONING
|
||||
public VersionedEnumerator GetEnumerator() => new(this);
|
||||
#else
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
#endif
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
#if WITH_VERSIONING
|
||||
IEnumerator IEnumerable.GetEnumerator() => new VersionedEnumerator(this);
|
||||
#else
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
#endif
|
||||
[VersionedCollectionsGenerator.CloneForVersioned]
|
||||
#if WITH_VERSIONING
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new VersionedEnumerator(this);
|
||||
#else
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
#endif
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public int IndexOf(T item)
|
||||
@@ -433,8 +462,10 @@ namespace Recyclable.Collections
|
||||
_lastBlockWithData++;
|
||||
}
|
||||
|
||||
_version++;
|
||||
}
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Insert(long index, T item)
|
||||
{
|
||||
@@ -472,8 +503,10 @@ namespace Recyclable.Collections
|
||||
_lastBlockWithData++;
|
||||
}
|
||||
|
||||
_version++;
|
||||
}
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public long LongIndexOf(T item)
|
||||
@@ -543,9 +576,11 @@ namespace Recyclable.Collections
|
||||
#nullable restore
|
||||
}
|
||||
|
||||
_version++;
|
||||
return true;
|
||||
}
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -585,8 +620,10 @@ namespace Recyclable.Collections
|
||||
#nullable restore
|
||||
}
|
||||
|
||||
_version++;
|
||||
}
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public void RemoveAt(long index)
|
||||
@@ -623,8 +660,10 @@ namespace Recyclable.Collections
|
||||
#nullable restore
|
||||
}
|
||||
|
||||
_version++;
|
||||
}
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void RemoveBlock(int index)
|
||||
{
|
||||
@@ -644,17 +683,21 @@ namespace Recyclable.Collections
|
||||
_nextItemBlockIndex--;
|
||||
}
|
||||
|
||||
_lastBlockWithData--;
|
||||
_version++;
|
||||
}
|
||||
_lastBlockWithData--;
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_version++;
|
||||
if (_capacity > 0)
|
||||
{
|
||||
Clear();
|
||||
if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
public void Dispose()
|
||||
{
|
||||
#if WITH_VERSIONING
|
||||
_version++;
|
||||
#endif
|
||||
if (_capacity > 0)
|
||||
{
|
||||
Clear();
|
||||
if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
// If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed.
|
||||
RecyclableArrayPool<T[]>.ReturnShared(_memoryBlocks, false);
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,244 @@
|
||||
using System.Collections;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Numerics;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal sealed class RecyclablePriorityQueue<T> : IEnumerable<T>, IDisposable
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
|
||||
internal readonly IComparer<T> _comparer;
|
||||
|
||||
#nullable disable
|
||||
internal T[] _heap;
|
||||
#nullable restore
|
||||
internal int _size;
|
||||
internal bool _disposed;
|
||||
|
||||
public RecyclablePriorityQueue(int initialCapacity = RecyclableDefaults.InitialCapacity, IComparer<T>? comparer = null)
|
||||
{
|
||||
if (initialCapacity < 1)
|
||||
{
|
||||
initialCapacity = 1;
|
||||
}
|
||||
|
||||
if (!BitOperations.IsPow2((uint)initialCapacity))
|
||||
{
|
||||
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
|
||||
}
|
||||
|
||||
_heap = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(initialCapacity)
|
||||
: new T[initialCapacity];
|
||||
_size = 0;
|
||||
_comparer = comparer ?? Comparer<T>.Default;
|
||||
}
|
||||
|
||||
public int Count => _size;
|
||||
public long LongCount => _size;
|
||||
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (_size == _heap.Length)
|
||||
{
|
||||
Grow();
|
||||
}
|
||||
|
||||
int index = _size++;
|
||||
_heap[index] = item;
|
||||
MoveUp(index);
|
||||
}
|
||||
|
||||
public T Dequeue()
|
||||
{
|
||||
if (_size == 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_size), "Queue is empty");
|
||||
}
|
||||
|
||||
var root = _heap[0];
|
||||
if (_needsClearing)
|
||||
{
|
||||
_heap[0] = default!;
|
||||
}
|
||||
|
||||
_size--;
|
||||
if (_size > 0)
|
||||
{
|
||||
_heap[0] = _heap[_size];
|
||||
if (_needsClearing)
|
||||
{
|
||||
_heap[_size] = default!;
|
||||
}
|
||||
|
||||
MoveDown(0);
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
public bool TryDequeue(out T? item)
|
||||
{
|
||||
if (_size > 0)
|
||||
{
|
||||
item = Dequeue();
|
||||
return true;
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
if (_size == 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_size), "Queue is empty");
|
||||
}
|
||||
|
||||
return _heap[0];
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_needsClearing)
|
||||
{
|
||||
Array.Clear(_heap, 0, _size);
|
||||
}
|
||||
|
||||
_size = 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 (_heap.Length >= RecyclableDefaults.MaxPooledBlockSize)
|
||||
{
|
||||
newSize = RecyclableDefaults.MaxPooledBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var doubled = _heap.Length << 1;
|
||||
newSize = (int)Math.Min(doubled, RecyclableDefaults.MaxPooledBlockSize);
|
||||
}
|
||||
|
||||
var newHeap = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(newSize)
|
||||
: new T[newSize];
|
||||
Array.Copy(_heap, newHeap, _size);
|
||||
|
||||
if (_heap.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(_heap, _needsClearing);
|
||||
}
|
||||
|
||||
_heap = newHeap;
|
||||
}
|
||||
|
||||
private void MoveUp(int index)
|
||||
{
|
||||
var item = _heap[index];
|
||||
while (index > 0)
|
||||
{
|
||||
int parent = (index - 1) >> 1;
|
||||
if (_comparer.Compare(item, _heap[parent]) >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_heap[index] = _heap[parent];
|
||||
index = parent;
|
||||
}
|
||||
|
||||
_heap[index] = item;
|
||||
}
|
||||
|
||||
private void MoveDown(int index)
|
||||
{
|
||||
var item = _heap[index];
|
||||
int half = _size >> 1;
|
||||
while (index < half)
|
||||
{
|
||||
int child = (index << 1) + 1;
|
||||
int right = child + 1;
|
||||
if (right < _size && _comparer.Compare(_heap[right], _heap[child]) < 0)
|
||||
{
|
||||
child = right;
|
||||
}
|
||||
|
||||
if (_comparer.Compare(_heap[child], item) >= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
_heap[index] = _heap[child];
|
||||
index = child;
|
||||
}
|
||||
|
||||
_heap[index] = item;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_heap.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(_heap, _needsClearing);
|
||||
}
|
||||
|
||||
_heap = Array.Empty<T>();
|
||||
_size = 0;
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly RecyclablePriorityQueue<T> _queue;
|
||||
private int _index;
|
||||
private T? _current;
|
||||
|
||||
internal Enumerator(RecyclablePriorityQueue<T> queue)
|
||||
{
|
||||
_queue = queue;
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public T Current => _current!;
|
||||
object IEnumerator.Current => _current!;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index >= _queue._size)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_current = _queue._heap[_index++];
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_current = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,928 @@
|
||||
using System.Collections;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Numerics;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal static class zRecyclableQueueAddRange
|
||||
{
|
||||
private static class AddRangeHelper<T>
|
||||
{
|
||||
private static readonly Action<RecyclableQueue<T>, object>? _dictionaryAdder;
|
||||
private static readonly Action<RecyclableQueue<T>, object>? _sortedListAdder;
|
||||
private static readonly Type? _dictionaryType;
|
||||
private static readonly Type? _sortedListType;
|
||||
|
||||
static AddRangeHelper()
|
||||
{
|
||||
Type elementType = typeof(T);
|
||||
if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(KeyValuePair<,>))
|
||||
{
|
||||
Type[] args = elementType.GetGenericArguments();
|
||||
_dictionaryType = typeof(RecyclableDictionary<,>).MakeGenericType(args);
|
||||
MethodInfo? method = typeof(zRecyclableQueueAddRange).GetMethod(
|
||||
nameof(AddRange),
|
||||
BindingFlags.NonPublic | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: new[] { typeof(RecyclableQueue<>).MakeGenericType(elementType), _dictionaryType },
|
||||
modifiers: null);
|
||||
if (method != null)
|
||||
{
|
||||
var queueParam = Expression.Parameter(typeof(RecyclableQueue<T>));
|
||||
var objParam = Expression.Parameter(typeof(object));
|
||||
var call = Expression.Call(method, queueParam, Expression.Convert(objParam, _dictionaryType));
|
||||
_dictionaryAdder = Expression.Lambda<Action<RecyclableQueue<T>, object>>(call, queueParam, objParam).Compile();
|
||||
}
|
||||
}
|
||||
else if (elementType.IsGenericType && elementType.GetGenericTypeDefinition() == typeof(ValueTuple<,>))
|
||||
{
|
||||
Type[] args = elementType.GetGenericArguments();
|
||||
_sortedListType = typeof(RecyclableSortedList<,>).MakeGenericType(args);
|
||||
MethodInfo? method = typeof(zRecyclableQueueAddRange).GetMethod(
|
||||
nameof(AddRange),
|
||||
BindingFlags.NonPublic | BindingFlags.Static,
|
||||
binder: null,
|
||||
types: new[] { typeof(RecyclableQueue<>).MakeGenericType(elementType), _sortedListType },
|
||||
modifiers: null);
|
||||
if (method != null)
|
||||
{
|
||||
var queueParam = Expression.Parameter(typeof(RecyclableQueue<T>));
|
||||
var objParam = Expression.Parameter(typeof(object));
|
||||
var call = Expression.Call(method, queueParam, Expression.Convert(objParam, _sortedListType));
|
||||
_sortedListAdder = Expression.Lambda<Action<RecyclableQueue<T>, object>>(call, queueParam, objParam).Compile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool TryAddRange(RecyclableQueue<T> queue, IEnumerable<T> items)
|
||||
{
|
||||
if (_dictionaryAdder != null && _dictionaryType!.IsInstanceOfType(items))
|
||||
{
|
||||
_dictionaryAdder(queue, items);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_sortedListAdder != null && _sortedListType!.IsInstanceOfType(items))
|
||||
{
|
||||
_sortedListAdder(queue, items);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, ReadOnlySpan<T> items)
|
||||
{
|
||||
int count = items.Length;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
items.Slice(copied, toCopy).CopyTo(new Span<T>(chunk.Value, index, toCopy));
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, Span<T> items) => AddRange(queue, (ReadOnlySpan<T>)items);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, T[] items) => AddRange(queue, (ReadOnlySpan<T>)items);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, in Array items)
|
||||
{
|
||||
int count = items.Length;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
int lower = items.GetLowerBound(0);
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
Array.Copy(items, lower + copied, chunk.Value, index, toCopy);
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, List<T> items) => AddRange(queue, CollectionsMarshal.AsSpan(items));
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, ICollection items)
|
||||
{
|
||||
int count = items.Count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
T[] buffer = count >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)count)))
|
||||
: new T[count];
|
||||
|
||||
items.CopyTo(buffer, 0);
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
Array.Copy(buffer, copied, chunk.Value, index, toCopy);
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
|
||||
if (count >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(buffer, RecyclableQueue<T>._needsClearing);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, ICollection<T> items)
|
||||
{
|
||||
int count = items.Count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
T[] buffer = count >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(checked((int)BitOperations.RoundUpToPowerOf2((uint)count)))
|
||||
: new T[count];
|
||||
|
||||
items.CopyTo(buffer, 0);
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
Array.Copy(buffer, copied, chunk.Value, index, toCopy);
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
|
||||
if (count >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(buffer, RecyclableQueue<T>._needsClearing);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclableList<T> items)
|
||||
{
|
||||
int count = items._count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
T[] source = items._memoryBlock;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
Array.Copy(source, copied, chunk.Value, index, toCopy);
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Grow<T>(RecyclableQueue<T> queue)
|
||||
{
|
||||
int newSize;
|
||||
if (queue._capacity >= RecyclableDefaults.MaxPooledBlockSize)
|
||||
{
|
||||
newSize = RecyclableDefaults.MaxPooledBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
long doubled = queue._capacity << 1;
|
||||
newSize = (int)Math.Min(doubled - queue._capacity, RecyclableDefaults.MaxPooledBlockSize);
|
||||
}
|
||||
|
||||
var newChunk = BiDirectionalRecyclableArrayPoolChunkPool<T>.Rent();
|
||||
if (newChunk.Value.Length < newSize)
|
||||
{
|
||||
if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(newChunk.Value, RecyclableQueue<T>._needsClearing);
|
||||
}
|
||||
|
||||
newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(newSize)
|
||||
: new T[newSize];
|
||||
}
|
||||
|
||||
newChunk.Top = 0;
|
||||
newChunk.Bottom = 0;
|
||||
newChunk.Previous = queue._tail;
|
||||
newChunk.Next = null;
|
||||
queue._tail.Next = newChunk;
|
||||
queue._tail = newChunk;
|
||||
queue._capacity += newSize;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Grow<T>(RecyclableQueue<T> queue, long additionalCapacity)
|
||||
{
|
||||
int newSize = additionalCapacity >= RecyclableDefaults.MaxPooledBlockSize
|
||||
? RecyclableDefaults.MaxPooledBlockSize
|
||||
: (int)Math.Min(BitOperations.RoundUpToPowerOf2((uint)additionalCapacity), RecyclableDefaults.MaxPooledBlockSize);
|
||||
|
||||
var newChunk = BiDirectionalRecyclableArrayPoolChunkPool<T>.Rent();
|
||||
if (newChunk.Value.Length < newSize)
|
||||
{
|
||||
if (newChunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(newChunk.Value, RecyclableQueue<T>._needsClearing);
|
||||
}
|
||||
|
||||
newChunk.Value = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(newSize)
|
||||
: new T[newSize];
|
||||
}
|
||||
|
||||
newChunk.Top = 0;
|
||||
newChunk.Bottom = 0;
|
||||
newChunk.Previous = queue._tail;
|
||||
newChunk.Next = null;
|
||||
queue._tail.Next = newChunk;
|
||||
queue._tail = newChunk;
|
||||
queue._capacity += newSize;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void EnsureCapacity<T>(RecyclableQueue<T> queue, long requiredCapacity)
|
||||
{
|
||||
while (queue._capacity < requiredCapacity)
|
||||
{
|
||||
Grow(queue, requiredCapacity - queue._capacity);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclableLongList<T> items)
|
||||
{
|
||||
long longCount = items._longCount;
|
||||
if (longCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int blockSize = items._blockSize;
|
||||
int fullBlocks = (int)(longCount >> items._blockSizePow2BitShift);
|
||||
int lastBlockLength = (int)(longCount & items._blockSizeMinus1);
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
|
||||
for (int blockIndex = 0; blockIndex < fullBlocks; blockIndex++)
|
||||
{
|
||||
T[] block = items._memoryBlocks[blockIndex];
|
||||
int sourceIndex = 0;
|
||||
while (sourceIndex < blockSize)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, blockSize - sourceIndex);
|
||||
Array.Copy(block, sourceIndex, chunk.Value, index, toCopy);
|
||||
sourceIndex += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastBlockLength > 0)
|
||||
{
|
||||
T[] block = items._memoryBlocks[fullBlocks];
|
||||
int sourceIndex = 0;
|
||||
while (sourceIndex < lastBlockLength)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, lastBlockLength - sourceIndex);
|
||||
Array.Copy(block, sourceIndex, chunk.Value, index, toCopy);
|
||||
sourceIndex += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += longCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclableHashSet<T> items)
|
||||
{
|
||||
int count = items._count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var entries = items._entries;
|
||||
int shift = items._blockShift,
|
||||
mask = items._blockSizeMinus1;
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
var destination = chunk.Value;
|
||||
for (int i = 0; i < toCopy; i++)
|
||||
{
|
||||
destination[index + i] = entries[(copied + i) >> shift][(copied + i) & mask].Value;
|
||||
}
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclableStack<T> items)
|
||||
{
|
||||
long longCount = items._count;
|
||||
if (longCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> destChunk = queue._tail;
|
||||
int destIndex = destChunk.Top;
|
||||
int destLength = destChunk.Value.Length;
|
||||
|
||||
RecyclableArrayPoolChunk<T> sourceChunk = items._current;
|
||||
while (sourceChunk.Previous != null)
|
||||
{
|
||||
sourceChunk = sourceChunk.Previous;
|
||||
}
|
||||
|
||||
while (sourceChunk != null)
|
||||
{
|
||||
T[] source = sourceChunk.Value;
|
||||
int sourceCount = sourceChunk.Index;
|
||||
int sourceIndex = 0;
|
||||
|
||||
while (sourceIndex < sourceCount)
|
||||
{
|
||||
if (destIndex == destLength)
|
||||
{
|
||||
destChunk.Top = destIndex;
|
||||
Grow(queue);
|
||||
destChunk = queue._tail;
|
||||
destIndex = destChunk.Top;
|
||||
destLength = destChunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(destLength - destIndex, sourceCount - sourceIndex);
|
||||
Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy);
|
||||
sourceIndex += toCopy;
|
||||
destIndex += toCopy;
|
||||
}
|
||||
|
||||
sourceChunk = sourceChunk.Next!;
|
||||
}
|
||||
|
||||
destChunk.Top = destIndex;
|
||||
queue._count += longCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclableSortedSet<T> items)
|
||||
{
|
||||
int count = items._count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
T[] source = items._items;
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
Array.Copy(source, copied, chunk.Value, index, toCopy);
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclableLinkedList<T> items)
|
||||
{
|
||||
long longCount = items._count;
|
||||
if (longCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> destChunk = queue._tail;
|
||||
int destIndex = destChunk.Top;
|
||||
int destLength = destChunk.Value.Length;
|
||||
|
||||
var sourceChunk = items._head;
|
||||
while (sourceChunk != null)
|
||||
{
|
||||
int sourceIndex = sourceChunk.Bottom;
|
||||
int sourceTop = sourceChunk.Top;
|
||||
T[] source = sourceChunk.Value;
|
||||
|
||||
while (sourceIndex < sourceTop)
|
||||
{
|
||||
if (destIndex == destLength)
|
||||
{
|
||||
destChunk.Top = destIndex;
|
||||
Grow(queue);
|
||||
destChunk = queue._tail;
|
||||
destIndex = destChunk.Top;
|
||||
destLength = destChunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(destLength - destIndex, sourceTop - sourceIndex);
|
||||
Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy);
|
||||
sourceIndex += toCopy;
|
||||
destIndex += toCopy;
|
||||
}
|
||||
|
||||
sourceChunk = sourceChunk.Next;
|
||||
}
|
||||
|
||||
destChunk.Top = destIndex;
|
||||
queue._count += longCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclablePriorityQueue<T> items)
|
||||
{
|
||||
int count = items._size;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
T[] source = items._heap;
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
Array.Copy(source, copied, chunk.Value, index, toCopy);
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, RecyclableQueue<T> items)
|
||||
{
|
||||
long longCount = items._count;
|
||||
if (longCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> destChunk = queue._tail;
|
||||
int destIndex = destChunk.Top;
|
||||
int destLength = destChunk.Value.Length;
|
||||
|
||||
EnsureCapacity(queue, queue._count + longCount);
|
||||
|
||||
var sourceChunk = items._head;
|
||||
while (sourceChunk != null)
|
||||
{
|
||||
int sourceIndex = sourceChunk.Bottom;
|
||||
int sourceTop = sourceChunk.Top;
|
||||
T[] source = sourceChunk.Value;
|
||||
|
||||
while (sourceIndex < sourceTop)
|
||||
{
|
||||
if (destIndex == destLength)
|
||||
{
|
||||
destChunk.Top = destIndex;
|
||||
destChunk = destChunk.Next!;
|
||||
destIndex = destChunk.Top;
|
||||
destLength = destChunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(destLength - destIndex, sourceTop - sourceIndex);
|
||||
Array.Copy(source, sourceIndex, destChunk.Value, destIndex, toCopy);
|
||||
sourceIndex += toCopy;
|
||||
destIndex += toCopy;
|
||||
}
|
||||
|
||||
sourceChunk = sourceChunk.Next;
|
||||
}
|
||||
|
||||
destChunk.Top = destIndex;
|
||||
queue._count += longCount;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<TKey, TValue>(this RecyclableQueue<KeyValuePair<TKey, TValue>> queue, RecyclableDictionary<TKey, TValue> items)
|
||||
where TKey : notnull
|
||||
{
|
||||
int count = items._count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
var entries = items._entries;
|
||||
int shift = items._blockShift,
|
||||
mask = items._blockSizeMinus1;
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<KeyValuePair<TKey, TValue>> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
var destination = chunk.Value;
|
||||
int local = copied;
|
||||
for (int i = 0; i < toCopy; i++)
|
||||
{
|
||||
ref var entry = ref entries[(local + i) >> shift][(local + i) & mask];
|
||||
destination[index + i] = new KeyValuePair<TKey, TValue>(entry.Key, entry.Value);
|
||||
}
|
||||
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<TKey, TValue>(this RecyclableQueue<(TKey Key, TValue Value)> queue, RecyclableSortedList<TKey, TValue> items)
|
||||
where TKey : notnull
|
||||
{
|
||||
int count = items._count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
TKey[] keys = items._keys;
|
||||
TValue[] values = items._values;
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<(TKey Key, TValue Value)> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
int copied = 0;
|
||||
|
||||
while (copied < count)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
int toCopy = Math.Min(chunkLength - index, count - copied);
|
||||
var destination = chunk.Value;
|
||||
for (int i = 0; i < toCopy; i++)
|
||||
{
|
||||
destination[index + i] = (keys[copied + i], values[copied + i]);
|
||||
}
|
||||
|
||||
copied += toCopy;
|
||||
index += toCopy;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, IReadOnlyList<T> items)
|
||||
{
|
||||
int count = items.Count;
|
||||
if (count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureCapacity(queue, queue._count + count);
|
||||
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
chunk.Value[index++] = items[i];
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
queue._count += count;
|
||||
}
|
||||
|
||||
internal static IEnumerator AddRange<T>(this RecyclableQueue<T> queue, IEnumerable source)
|
||||
{
|
||||
IEnumerator enumerator = source.GetEnumerator();
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
chunk.Value[index++] = (T)enumerator.Current!;
|
||||
queue._count++;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
return enumerator;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
internal static void AddRange<T>(this RecyclableQueue<T> queue, IEnumerable<T> items)
|
||||
{
|
||||
if (items is RecyclableList<T> recyclableList)
|
||||
{
|
||||
AddRange(queue, recyclableList);
|
||||
}
|
||||
else if (items is RecyclableLongList<T> recyclableLongList)
|
||||
{
|
||||
AddRange(queue, recyclableLongList);
|
||||
}
|
||||
else if (items is T[] array)
|
||||
{
|
||||
AddRange(queue, array);
|
||||
}
|
||||
else if (items is List<T> list)
|
||||
{
|
||||
AddRange(queue, list);
|
||||
}
|
||||
else if (items is ICollection<T> iCollection)
|
||||
{
|
||||
AddRange(queue, iCollection);
|
||||
}
|
||||
else if (items is ICollection collection)
|
||||
{
|
||||
AddRange(queue, collection);
|
||||
}
|
||||
else if (items is RecyclableHashSet<T> hashSet)
|
||||
{
|
||||
AddRange(queue, hashSet);
|
||||
}
|
||||
else if (items is RecyclableStack<T> stack)
|
||||
{
|
||||
AddRange(queue, stack);
|
||||
}
|
||||
else if (items is RecyclableSortedSet<T> sortedSet)
|
||||
{
|
||||
AddRange(queue, sortedSet);
|
||||
}
|
||||
else if (items is RecyclableLinkedList<T> linkedList)
|
||||
{
|
||||
AddRange(queue, linkedList);
|
||||
}
|
||||
else if (items is RecyclablePriorityQueue<T> priorityQueue)
|
||||
{
|
||||
AddRange(queue, priorityQueue);
|
||||
}
|
||||
else if (items is RecyclableQueue<T> queueSource)
|
||||
{
|
||||
AddRange(queue, queueSource);
|
||||
}
|
||||
else if (AddRangeHelper<T>.TryAddRange(queue, items))
|
||||
{
|
||||
}
|
||||
else if (items is IReadOnlyList<T> readOnlyList)
|
||||
{
|
||||
AddRange(queue, readOnlyList);
|
||||
}
|
||||
else
|
||||
{
|
||||
using IEnumerator<T> enumerator = items.GetEnumerator();
|
||||
BiDirectionalRecyclableArrayPoolChunk<T> chunk = queue._tail;
|
||||
int index = chunk.Top;
|
||||
int chunkLength = chunk.Value.Length;
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
if (index == chunkLength)
|
||||
{
|
||||
chunk.Top = index;
|
||||
Grow(queue);
|
||||
chunk = queue._tail;
|
||||
index = chunk.Top;
|
||||
chunkLength = chunk.Value.Length;
|
||||
}
|
||||
|
||||
chunk.Value[index++] = enumerator.Current;
|
||||
queue._count++;
|
||||
}
|
||||
|
||||
chunk.Top = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,211 +1,257 @@
|
||||
using System.Buffers;
|
||||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable
|
||||
{
|
||||
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create();
|
||||
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
|
||||
internal sealed class RecyclableQueue<T> : IEnumerable<T>, IDisposable
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
|
||||
private bool _disposedValue;
|
||||
private RecyclableList<T[]> Memory { get; }
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static BiDirectionalRecyclableArrayPoolChunk<T> RentChunk(int size, BiDirectionalRecyclableArrayPoolChunk<T>? previous)
|
||||
{
|
||||
var chunk = BiDirectionalRecyclableArrayPoolChunkPool<T>.Rent();
|
||||
if (chunk.Value.Length < size)
|
||||
{
|
||||
if (chunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(chunk.Value, _needsClearing);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static T Dequeue(RecyclableQueue<T> queue)
|
||||
{
|
||||
if (queue.LongCount <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(queue), "There are no items in the queue");
|
||||
}
|
||||
chunk.Value = size >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(size)
|
||||
: new T[size];
|
||||
}
|
||||
|
||||
var toRemove = queue[0];
|
||||
queue.RemovedCount++;
|
||||
queue.LongCount--;
|
||||
if (queue.Capacity - queue.LongCount == queue.BlockSize)
|
||||
{
|
||||
queue.RemoveBlock(0);
|
||||
queue.RemovedCount -= queue.BlockSize;
|
||||
}
|
||||
chunk.Top = 0;
|
||||
chunk.Bottom = 0;
|
||||
chunk.Previous = previous;
|
||||
chunk.Next = null;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReturnChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
BiDirectionalRecyclableArrayPoolChunkPool<T>.Return(chunk);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool TryDequeue(RecyclableQueue<T> queue, out T? item)
|
||||
{
|
||||
if (queue.LongCount > 0)
|
||||
{
|
||||
item = queue.Dequeue();
|
||||
return true;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void DisposeChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
BiDirectionalRecyclableArrayPoolChunkPool<T>.Dispose(chunk, _needsClearing);
|
||||
}
|
||||
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
internal BiDirectionalRecyclableArrayPoolChunk<T> _head;
|
||||
internal BiDirectionalRecyclableArrayPoolChunk<T> _tail;
|
||||
internal bool _disposed;
|
||||
internal long _capacity;
|
||||
internal long _count;
|
||||
|
||||
protected static readonly bool NeedsClearing = !typeof(T).IsValueType;
|
||||
public RecyclableQueue(int initialCapacity = RecyclableDefaults.InitialCapacity)
|
||||
{
|
||||
if (initialCapacity < 1)
|
||||
{
|
||||
initialCapacity = 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected static long GetAbsoluteIndex(long index, long removedCount) => index + removedCount;
|
||||
if (!BitOperations.IsPow2((uint)initialCapacity))
|
||||
{
|
||||
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
protected long GetRelativeIndex(long index) => index - RemovedCount;
|
||||
_head = RentChunk(initialCapacity, null);
|
||||
_tail = _head;
|
||||
_capacity = initialCapacity;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
protected long RemovedCount { get; set; }
|
||||
public RecyclableQueue(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.InitialCapacity) : this(initialCapacity)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
Enqueue(item);
|
||||
}
|
||||
}
|
||||
|
||||
public RecyclableQueue(IEnumerable<T> source, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
BlockSize = blockSize;
|
||||
Memory = new(1);
|
||||
foreach (var item in source)
|
||||
{
|
||||
Enqueue(item);
|
||||
}
|
||||
}
|
||||
public int Count => checked((int)_count);
|
||||
public long LongCount => _count;
|
||||
|
||||
public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
BlockSize = blockSize;
|
||||
Memory = new(1);
|
||||
}
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (_tail.Top == _tail.Value.Length)
|
||||
{
|
||||
Grow();
|
||||
}
|
||||
|
||||
public int BlockSize { get; protected set; }
|
||||
public long Capacity { get; set; }
|
||||
_tail.Value[_tail.Top++] = item;
|
||||
_count++;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => checked((int)LongCount);
|
||||
set => LongCount = value;
|
||||
}
|
||||
public void Add(T item) => Enqueue(item);
|
||||
|
||||
public long LongCount { get; set; }
|
||||
public bool IsReadOnly { get; }
|
||||
public T Dequeue()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_count), "Queue is empty");
|
||||
}
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get => GetItem(index);
|
||||
set => SetItem(index, value);
|
||||
}
|
||||
_count--;
|
||||
var value = _head.Value[_head.Bottom];
|
||||
if (_needsClearing)
|
||||
{
|
||||
_head.Value[_head.Bottom] = default!;
|
||||
}
|
||||
|
||||
public T this[long index]
|
||||
{
|
||||
get => GetItem(index);
|
||||
set => SetItem(index, value);
|
||||
}
|
||||
_head.Bottom++;
|
||||
if (_head.Bottom == _head.Top && _head.Next != null)
|
||||
{
|
||||
ReleaseHeadChunk();
|
||||
}
|
||||
else if (_head.Bottom == _head.Top)
|
||||
{
|
||||
_head.Bottom = 0;
|
||||
_head.Top = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SetItem(long index, T value)
|
||||
{
|
||||
long absoluteIndex = GetAbsoluteIndex(index, RemovedCount);
|
||||
int arrayIndex = RecyclableQueueHelpers<T>.ToArrayIndex(absoluteIndex, BlockSize);
|
||||
long itemIndex = RecyclableQueueHelpers<T>.ToItemIndex(absoluteIndex, BlockSize);
|
||||
Memory[arrayIndex][itemIndex] = value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private T GetItem(long index)
|
||||
{
|
||||
long absoluteIndex = GetAbsoluteIndex(index, RemovedCount);
|
||||
int arrayIndex = RecyclableQueueHelpers<T>.ToArrayIndex(absoluteIndex, BlockSize);
|
||||
long itemIndex = RecyclableQueueHelpers<T>.ToItemIndex(absoluteIndex, BlockSize);
|
||||
return Memory[arrayIndex][itemIndex];
|
||||
}
|
||||
public bool TryDequeue(out T? item)
|
||||
{
|
||||
if (_count > 0)
|
||||
{
|
||||
item = Dequeue();
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (LongCount == Capacity)
|
||||
{
|
||||
AddBlock();
|
||||
}
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
SetItem(LongCount, item);
|
||||
LongCount++;
|
||||
}
|
||||
public void Clear()
|
||||
{
|
||||
while (_head.Next != null)
|
||||
{
|
||||
ReleaseHeadChunk();
|
||||
}
|
||||
|
||||
public void AddBlock()
|
||||
{
|
||||
T[] newArray = (BlockSize >= RecyclableDefaults.MinPooledArrayLength)
|
||||
? _arrayPool.Rent(BlockSize)
|
||||
: new T[BlockSize];
|
||||
if (_needsClearing && _head.Top > _head.Bottom)
|
||||
{
|
||||
Array.Clear(_head.Value, _head.Bottom, _head.Top - _head.Bottom);
|
||||
}
|
||||
|
||||
Memory.Add(newArray);
|
||||
Capacity += BlockSize;
|
||||
}
|
||||
_capacity = _head.Value.Length;
|
||||
_head.Top = 0;
|
||||
_head.Bottom = 0;
|
||||
_tail = _head;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public T Dequeue() => Dequeue(this);
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
public void Add(T item) => Enqueue(item);
|
||||
private void Grow()
|
||||
{
|
||||
int newSize;
|
||||
if (_capacity >= RecyclableDefaults.MaxPooledBlockSize)
|
||||
{
|
||||
newSize = RecyclableDefaults.MaxPooledBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var doubled = _capacity << 1;
|
||||
newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Remove in reversed order for performance savings
|
||||
while (Memory._count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
RemoveBlock(Memory._count - 1);
|
||||
}
|
||||
#pragma warning disable RCS1075 // We want to try returning as many arrays, as possible, before the list is cleared.
|
||||
catch (Exception)
|
||||
#pragma warning restore RCS1075
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LongCount = 0;
|
||||
RemovedCount = 0;
|
||||
Memory.Clear();
|
||||
}
|
||||
}
|
||||
var newChunk = RentChunk(newSize, _tail);
|
||||
_tail.Next = newChunk;
|
||||
_tail = newChunk;
|
||||
_capacity += newSize;
|
||||
}
|
||||
|
||||
#pragma warning disable CS0618
|
||||
public bool Contains(T item) => RecyclableQueueHelpers<T>.Contains(Memory, item);
|
||||
public void CopyTo(T[] array, int arrayIndex) => RecyclableQueueHelpers<T>.CopyTo(Memory, RemovedCount, BlockSize, checked((int)(LongCount % BlockSize)), array, arrayIndex);
|
||||
public IEnumerable<T> Enumerate() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount);
|
||||
public IEnumerator<T> GetEnumerator() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount).GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount).GetEnumerator();
|
||||
public int IndexOf(T item) => checked((int)GetRelativeIndex(RecyclableQueueHelpers<T>.LongIndexOf(Memory, BlockSize, item, _equalityComparer)));
|
||||
public long LongIndexOf(T item) => GetRelativeIndex(RecyclableQueueHelpers<T>.LongIndexOf(Memory, BlockSize, item, _equalityComparer));
|
||||
#pragma warning restore CS0618
|
||||
private void ReleaseHeadChunk()
|
||||
{
|
||||
var toReturn = _head;
|
||||
_head = toReturn.Next!;
|
||||
_head.Previous = null;
|
||||
_capacity -= toReturn.Value.Length;
|
||||
ReturnChunk(toReturn);
|
||||
}
|
||||
|
||||
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);
|
||||
public bool Remove(T item) => TryDequeue(out var _);
|
||||
public void RemoveBlock(int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Memory[index].LongLength is > RecyclableDefaults.MinPooledArrayLength and <= int.MaxValue)
|
||||
{
|
||||
_arrayPool.Return(Memory[index], NeedsClearing);
|
||||
}
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Memory.RemoveAt(index);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Capacity -= BlockSize;
|
||||
//LongCount -= BlockSize;
|
||||
}
|
||||
}
|
||||
var chunk = _head;
|
||||
while (chunk != null)
|
||||
{
|
||||
var next = chunk.Next;
|
||||
DisposeChunk(chunk);
|
||||
chunk = next;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
Clear();
|
||||
_disposedValue = true;
|
||||
}
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly BiDirectionalRecyclableArrayPoolChunk<T>? _start;
|
||||
private BiDirectionalRecyclableArrayPoolChunk<T>? _chunk;
|
||||
private int _index;
|
||||
private T? _current;
|
||||
|
||||
internal Enumerator(RecyclableQueue<T> queue)
|
||||
{
|
||||
_start = queue._head;
|
||||
_chunk = queue._head;
|
||||
_index = _chunk != null ? _chunk.Bottom : 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public T Current => _current!;
|
||||
object IEnumerator.Current => _current!;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_chunk == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_index >= _chunk.Top)
|
||||
{
|
||||
_chunk = _chunk.Next;
|
||||
if (_chunk == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_index = _chunk.Bottom;
|
||||
}
|
||||
|
||||
_current = _chunk.Value[_index++];
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_chunk = _start;
|
||||
_index = _chunk != null ? _chunk.Bottom : 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_chunk = null;
|
||||
_current = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal sealed class RecyclableSortedDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>, IDisposable
|
||||
where TKey : notnull
|
||||
{
|
||||
private static readonly bool _needsClearing = !typeof(TKey).IsValueType || !typeof(TValue).IsValueType;
|
||||
|
||||
private readonly IComparer<TKey> _comparer;
|
||||
private TKey[] _keys;
|
||||
private TValue[] _values;
|
||||
private int _count;
|
||||
private bool _disposed;
|
||||
|
||||
public RecyclableSortedDictionary(int initialCapacity = RecyclableDefaults.InitialCapacity, IComparer<TKey>? comparer = null)
|
||||
{
|
||||
if (initialCapacity < 1)
|
||||
{
|
||||
initialCapacity = 1;
|
||||
}
|
||||
|
||||
if (!BitOperations.IsPow2((uint)initialCapacity))
|
||||
{
|
||||
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
|
||||
}
|
||||
|
||||
_keys = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TKey>.RentShared(initialCapacity)
|
||||
: new TKey[initialCapacity];
|
||||
_values = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TValue>.RentShared(initialCapacity)
|
||||
: new TValue[initialCapacity];
|
||||
_count = 0;
|
||||
_comparer = comparer ?? Comparer<TKey>.Default;
|
||||
}
|
||||
|
||||
public int Count => _count;
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index < _count && _comparer.Compare(_keys[index], key) == 0)
|
||||
{
|
||||
return _values[index];
|
||||
}
|
||||
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(key), "Key not found");
|
||||
return default!;
|
||||
}
|
||||
set
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index < _count && _comparer.Compare(_keys[index], key) == 0)
|
||||
{
|
||||
_values[index] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
InsertAt(index, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index < _count && _comparer.Compare(_keys[index], key) == 0)
|
||||
{
|
||||
throw new ArgumentException("An element with the same key already exists.", nameof(key));
|
||||
}
|
||||
|
||||
InsertAt(index, key, value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
return index < _count && _comparer.Compare(_keys[index], key) == 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index >= _count || _comparer.Compare(_keys[index], key) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index < _count - 1)
|
||||
{
|
||||
Array.Copy(_keys, index + 1, _keys, index, _count - index - 1);
|
||||
Array.Copy(_values, index + 1, _values, index, _count - index - 1);
|
||||
}
|
||||
|
||||
_count--;
|
||||
if (_needsClearing)
|
||||
{
|
||||
_keys[_count] = default!;
|
||||
_values[_count] = default!;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index < _count && _comparer.Compare(_keys[index], key) == 0)
|
||||
{
|
||||
value = _values[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_needsClearing)
|
||||
{
|
||||
Array.Clear(_keys, 0, _count);
|
||||
Array.Clear(_values, 0, _count);
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public TKey GetKey(int index) => _keys[index];
|
||||
public TValue GetValue(int index) => _values[index];
|
||||
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
private void InsertAt(int index, TKey key, TValue value)
|
||||
{
|
||||
if (_count == _keys.Length)
|
||||
{
|
||||
Grow();
|
||||
}
|
||||
|
||||
if (index < _count)
|
||||
{
|
||||
Array.Copy(_keys, index, _keys, index + 1, _count - index);
|
||||
Array.Copy(_values, index, _values, index + 1, _count - index);
|
||||
}
|
||||
|
||||
_keys[index] = key;
|
||||
_values[index] = value;
|
||||
_count++;
|
||||
}
|
||||
|
||||
private void Grow()
|
||||
{
|
||||
int newSize;
|
||||
if (_keys.Length >= RecyclableDefaults.MaxPooledBlockSize)
|
||||
{
|
||||
newSize = RecyclableDefaults.MaxPooledBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
int doubled = _keys.Length << 1;
|
||||
newSize = (int)Math.Min(doubled, RecyclableDefaults.MaxPooledBlockSize);
|
||||
}
|
||||
|
||||
var newKeys = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TKey>.RentShared(newSize)
|
||||
: new TKey[newSize];
|
||||
var newValues = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TValue>.RentShared(newSize)
|
||||
: new TValue[newSize];
|
||||
Array.Copy(_keys, newKeys, _count);
|
||||
Array.Copy(_values, newValues, _count);
|
||||
|
||||
if (_keys.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TKey>.ReturnShared(_keys, _needsClearing);
|
||||
}
|
||||
if (_values.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TValue>.ReturnShared(_values, _needsClearing);
|
||||
}
|
||||
|
||||
_keys = newKeys;
|
||||
_values = newValues;
|
||||
}
|
||||
|
||||
private int BinarySearch(TKey key)
|
||||
{
|
||||
int low = 0;
|
||||
int high = _count - 1;
|
||||
while (low <= high)
|
||||
{
|
||||
int mid = (low + high) >> 1;
|
||||
int cmp = _comparer.Compare(_keys[mid], key);
|
||||
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 (_keys.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TKey>.ReturnShared(_keys, _needsClearing);
|
||||
}
|
||||
if (_values.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TValue>.ReturnShared(_values, _needsClearing);
|
||||
}
|
||||
|
||||
_keys = Array.Empty<TKey>();
|
||||
_values = Array.Empty<TValue>();
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<KeyValuePair<TKey, TValue>>
|
||||
{
|
||||
private readonly RecyclableSortedDictionary<TKey, TValue> _dictionary;
|
||||
private int _index;
|
||||
private KeyValuePair<TKey, TValue> _current;
|
||||
|
||||
internal Enumerator(RecyclableSortedDictionary<TKey, TValue> dictionary)
|
||||
{
|
||||
_dictionary = dictionary;
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public KeyValuePair<TKey, TValue> Current => _current;
|
||||
object IEnumerator.Current => _current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index >= _dictionary._count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_current = new KeyValuePair<TKey, TValue>(_dictionary._keys[_index], _dictionary._values[_index]);
|
||||
_index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
_current = default;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +1,281 @@
|
||||
using Recyclable.Collections.Linq;
|
||||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal class RecyclableSortedList<TKey, TValue> : RecyclableLongList<(TKey Key, TValue Value)>
|
||||
where TKey : notnull
|
||||
{
|
||||
private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer<TKey, TValue>();
|
||||
private uint _isUpdating;
|
||||
private readonly SystemRandomNumberGenerator _randomNumberGenerator = new();
|
||||
internal sealed class RecyclableSortedList<TKey, TValue> : IEnumerable<(TKey Key, TValue Value)>, IDisposable
|
||||
where TKey : notnull
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(TKey).IsValueType || !typeof(TValue).IsValueType;
|
||||
|
||||
protected void ListUpdated()
|
||||
{
|
||||
QuickSort();
|
||||
}
|
||||
internal TKey[] _keys;
|
||||
internal TValue[] _values;
|
||||
internal int _count;
|
||||
internal readonly IComparer<TKey> _comparer;
|
||||
internal bool _disposed;
|
||||
|
||||
public RecyclableSortedList() : base()
|
||||
{
|
||||
}
|
||||
public RecyclableSortedList(int initialCapacity = RecyclableDefaults.InitialCapacity, IComparer<TKey>? comparer = null)
|
||||
{
|
||||
if (initialCapacity < 1)
|
||||
{
|
||||
initialCapacity = 1;
|
||||
}
|
||||
|
||||
public RecyclableSortedList(Dictionary<TKey, TValue> source, int blockSize = RecyclableDefaults.BlockSize)
|
||||
: this(source.Select(x => (x.Key, x.Value)), blockSize)
|
||||
{
|
||||
}
|
||||
if (!BitOperations.IsPow2((uint)initialCapacity))
|
||||
{
|
||||
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
|
||||
}
|
||||
|
||||
public RecyclableSortedList(IEnumerable<(TKey Key, TValue Value)> source, int blockSize = RecyclableDefaults.BlockSize)
|
||||
: base(source, blockSize)
|
||||
{
|
||||
QuickSort();
|
||||
}
|
||||
_keys = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TKey>.RentShared(initialCapacity)
|
||||
: new TKey[initialCapacity];
|
||||
_values = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TValue>.RentShared(initialCapacity)
|
||||
: new TValue[initialCapacity];
|
||||
_count = 0;
|
||||
_comparer = comparer ?? Comparer<TKey>.Default;
|
||||
}
|
||||
|
||||
public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize)
|
||||
: base(blockSize)
|
||||
{
|
||||
}
|
||||
public int Count => _count;
|
||||
|
||||
public bool IsUpdating => _isUpdating > 0;
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
return _values[index];
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value) => Add((key, value));
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(key), "Key not found");
|
||||
return default!;
|
||||
}
|
||||
set
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
_values[index] = value;
|
||||
return;
|
||||
}
|
||||
|
||||
public void BeginUpdate() => _isUpdating++;
|
||||
Insert(~index, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public new void Clear()
|
||||
{
|
||||
BeginUpdate();
|
||||
try
|
||||
{
|
||||
base.Clear();
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndUpdate();
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(key), "An element with the same key already exists.");
|
||||
}
|
||||
|
||||
public void EndUpdate(bool raiseListUpdated = true)
|
||||
{
|
||||
if (_isUpdating > 0)
|
||||
{
|
||||
_isUpdating--;
|
||||
if (raiseListUpdated && _isUpdating == 0)
|
||||
{
|
||||
ListUpdated();
|
||||
}
|
||||
}
|
||||
}
|
||||
Insert(~index, key, value);
|
||||
}
|
||||
|
||||
public void QuickSort()
|
||||
{
|
||||
BeginUpdate();
|
||||
try
|
||||
{
|
||||
QuickSortExtensions<(TKey, TValue)>.QuickSort(this, _comparer, _randomNumberGenerator);
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndUpdate(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool ContainsKey(TKey key) => BinarySearch(key) >= 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index >= 0)
|
||||
{
|
||||
value = _values[index];
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
int index = BinarySearch(key);
|
||||
if (index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index < _count - 1)
|
||||
{
|
||||
Array.Copy(_keys, index + 1, _keys, index, _count - index - 1);
|
||||
Array.Copy(_values, index + 1, _values, index, _count - index - 1);
|
||||
}
|
||||
|
||||
_count--;
|
||||
if (_needsClearing)
|
||||
{
|
||||
_keys[_count] = default!;
|
||||
_values[_count] = default!;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
if (_needsClearing && _count > 0)
|
||||
{
|
||||
Array.Clear(_keys, 0, _count);
|
||||
Array.Clear(_values, 0, _count);
|
||||
}
|
||||
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
public TKey GetKey(int index) => _keys[index];
|
||||
public TValue GetValue(int index) => _values[index];
|
||||
|
||||
public Enumerator GetEnumerator() => new(this);
|
||||
IEnumerator<(TKey Key, TValue Value)> IEnumerable<(TKey Key, TValue Value)>.GetEnumerator() => new Enumerator(this);
|
||||
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_keys.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TKey>.ReturnShared(_keys, _needsClearing);
|
||||
}
|
||||
|
||||
if (_values.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TValue>.ReturnShared(_values, _needsClearing);
|
||||
}
|
||||
|
||||
_keys = Array.Empty<TKey>();
|
||||
_values = Array.Empty<TValue>();
|
||||
_count = 0;
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Insert(int index, TKey key, TValue value)
|
||||
{
|
||||
if (_count == _keys.Length)
|
||||
{
|
||||
Grow(_count + 1);
|
||||
}
|
||||
|
||||
if (index < _count)
|
||||
{
|
||||
Array.Copy(_keys, index, _keys, index + 1, _count - index);
|
||||
Array.Copy(_values, index, _values, index + 1, _count - index);
|
||||
}
|
||||
|
||||
_keys[index] = key;
|
||||
_values[index] = value;
|
||||
_count++;
|
||||
}
|
||||
|
||||
private void Grow(int min)
|
||||
{
|
||||
int newSize = _keys.Length << 1;
|
||||
if (newSize < min)
|
||||
{
|
||||
newSize = min;
|
||||
}
|
||||
|
||||
var newKeys = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TKey>.RentShared(newSize)
|
||||
: new TKey[newSize];
|
||||
var newValues = newSize >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<TValue>.RentShared(newSize)
|
||||
: new TValue[newSize];
|
||||
|
||||
Array.Copy(_keys, newKeys, _count);
|
||||
Array.Copy(_values, newValues, _count);
|
||||
|
||||
if (_keys.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TKey>.ReturnShared(_keys, _needsClearing);
|
||||
}
|
||||
|
||||
if (_values.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<TValue>.ReturnShared(_values, _needsClearing);
|
||||
}
|
||||
|
||||
_keys = newKeys;
|
||||
_values = newValues;
|
||||
}
|
||||
|
||||
private int BinarySearch(TKey key)
|
||||
{
|
||||
int low = 0;
|
||||
int high = _count - 1;
|
||||
while (low <= high)
|
||||
{
|
||||
int mid = (low + high) >> 1;
|
||||
int cmp = _comparer.Compare(_keys[mid], key);
|
||||
if (cmp == 0)
|
||||
{
|
||||
return mid;
|
||||
}
|
||||
|
||||
if (cmp < 0)
|
||||
{
|
||||
low = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
high = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ~low;
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<(TKey Key, TValue Value)>
|
||||
{
|
||||
private readonly RecyclableSortedList<TKey, TValue> _list;
|
||||
private int _index;
|
||||
private (TKey, TValue) _current;
|
||||
|
||||
internal Enumerator(RecyclableSortedList<TKey, TValue> list)
|
||||
{
|
||||
_list = list;
|
||||
_index = 0;
|
||||
_current = default!;
|
||||
}
|
||||
|
||||
public (TKey Key, TValue Value) Current => _current;
|
||||
object IEnumerator.Current => _current;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_index >= _list._count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_current = (_list._keys[_index], _list._values[_index]);
|
||||
_index++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_index = 0;
|
||||
_current = default!;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
|
||||
internal readonly IComparer<T> _comparer;
|
||||
|
||||
internal T[] _items;
|
||||
internal int _count;
|
||||
internal 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()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,94 +1,238 @@
|
||||
using System.Collections;
|
||||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Recyclable.Collections.Pools;
|
||||
|
||||
namespace Recyclable.Collections
|
||||
{
|
||||
internal class RecyclableStack<T> : IList<T>, IDisposable
|
||||
{
|
||||
private static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
private bool _disposedValue;
|
||||
internal sealed class RecyclableStack<T> : IEnumerable<T>, IDisposable
|
||||
{
|
||||
internal static readonly bool _needsClearing = !typeof(T).IsValueType;
|
||||
|
||||
protected RecyclableLongList<T> List { get; }
|
||||
public int Count => checked((int)List._longCount);
|
||||
public long LongCount
|
||||
{
|
||||
get => List._longCount;
|
||||
set => List._longCount = value;
|
||||
}
|
||||
public bool IsReadOnly => false;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static RecyclableArrayPoolChunk<T> RentChunk(int size, RecyclableArrayPoolChunk<T>? previous)
|
||||
{
|
||||
var chunk = RecyclableArrayPoolChunkPool<T>.Rent();
|
||||
if (chunk.Value.Length < size)
|
||||
{
|
||||
if (chunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength)
|
||||
{
|
||||
RecyclableArrayPool<T>.ReturnShared(chunk.Value, _needsClearing);
|
||||
}
|
||||
|
||||
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; }
|
||||
chunk.Value = size >= RecyclableDefaults.MinPooledArrayLength
|
||||
? RecyclableArrayPool<T>.RentShared(size)
|
||||
: new T[size];
|
||||
}
|
||||
chunk.Index = 0;
|
||||
chunk.Previous = previous;
|
||||
chunk.Next = null;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
public RecyclableStack(int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
List = new(blockSize);
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ReturnChunk(RecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
RecyclableArrayPoolChunkPool<T>.Return(chunk);
|
||||
}
|
||||
|
||||
public RecyclableStack(IEnumerable<T> list, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
List = new(list, blockSize);
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void DisposeChunk(RecyclableArrayPoolChunk<T> chunk)
|
||||
{
|
||||
RecyclableArrayPoolChunkPool<T>.Dispose(chunk, _needsClearing);
|
||||
}
|
||||
|
||||
public RecyclableStack(RecyclableLongList<T> list, int blockSize = RecyclableDefaults.BlockSize)
|
||||
{
|
||||
List = new(list, blockSize);
|
||||
}
|
||||
internal RecyclableArrayPoolChunk<T> _current;
|
||||
internal bool _disposed;
|
||||
internal long _capacity;
|
||||
internal long _count;
|
||||
|
||||
public void Push(T item)
|
||||
{
|
||||
List.Add(item);
|
||||
}
|
||||
public RecyclableStack(int initialCapacity = RecyclableDefaults.InitialCapacity)
|
||||
{
|
||||
if (initialCapacity < 1)
|
||||
{
|
||||
initialCapacity = 1;
|
||||
}
|
||||
|
||||
public T Pop()
|
||||
{
|
||||
RecyclableLongList<T> list = List;
|
||||
var itemIndex = (int)(list.LongCount & list._blockSizeMinus1) - 1;
|
||||
var blockIndex = (int)(LongCount >> list._blockSizePow2BitShift);
|
||||
var toRemove = list._memoryBlocks[blockIndex][itemIndex];
|
||||
if (!BitOperations.IsPow2((uint)initialCapacity))
|
||||
{
|
||||
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
|
||||
}
|
||||
|
||||
LongCount--;
|
||||
_current = RentChunk(initialCapacity, null);
|
||||
_capacity = initialCapacity;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
if (_needsClearing)
|
||||
{
|
||||
#nullable disable
|
||||
new Span<T>(list._memoryBlocks[blockIndex])[itemIndex] = default;
|
||||
#nullable restore
|
||||
}
|
||||
public RecyclableStack(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.InitialCapacity) : this(initialCapacity)
|
||||
{
|
||||
foreach (var item in source)
|
||||
{
|
||||
Push(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (itemIndex == 0)
|
||||
{
|
||||
list.RemoveBlock(blockIndex);
|
||||
}
|
||||
public int Count => checked((int)_count);
|
||||
public long LongCount => _count;
|
||||
|
||||
return toRemove;
|
||||
}
|
||||
public void Push(T item)
|
||||
{
|
||||
if (_current.Index == _current.Value.Length)
|
||||
{
|
||||
Grow();
|
||||
}
|
||||
|
||||
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();
|
||||
_current.Value[_current.Index++] = item;
|
||||
_count++;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
Clear();
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
public void Add(T item) => Push(item);
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
public T Pop()
|
||||
{
|
||||
if (_count == 0)
|
||||
{
|
||||
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_count), "Stack is empty");
|
||||
}
|
||||
|
||||
_count--;
|
||||
_current.Index--;
|
||||
var value = _current.Value[_current.Index];
|
||||
if (_needsClearing)
|
||||
{
|
||||
_current.Value[_current.Index] = default!;
|
||||
}
|
||||
|
||||
if (_current.Index == 0 && _current.Previous != null)
|
||||
{
|
||||
ReleaseCurrentChunk();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
while (_current.Previous != null)
|
||||
{
|
||||
ReleaseCurrentChunk();
|
||||
}
|
||||
|
||||
if (_needsClearing && _current.Index > 0)
|
||||
{
|
||||
Array.Clear(_current.Value, 0, _current.Index);
|
||||
}
|
||||
|
||||
_capacity = _current.Value.Length;
|
||||
_current.Index = 0;
|
||||
_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 (_capacity >= RecyclableDefaults.MaxPooledBlockSize)
|
||||
{
|
||||
newSize = RecyclableDefaults.MaxPooledBlockSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
var doubled = _capacity << 1;
|
||||
newSize = (int)Math.Min(doubled - _capacity, RecyclableDefaults.MaxPooledBlockSize);
|
||||
}
|
||||
|
||||
var newChunk = RentChunk(newSize, _current);
|
||||
_current.Next = newChunk;
|
||||
_current = newChunk;
|
||||
_capacity += newSize;
|
||||
}
|
||||
|
||||
private void ReleaseCurrentChunk()
|
||||
{
|
||||
var toReturn = _current;
|
||||
_current = toReturn.Previous!;
|
||||
_current.Next = null;
|
||||
_capacity -= toReturn.Value.Length;
|
||||
ReturnChunk(toReturn);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var chunk = _current;
|
||||
while (chunk != null)
|
||||
{
|
||||
var previous = chunk.Previous;
|
||||
DisposeChunk(chunk);
|
||||
chunk = previous;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private readonly RecyclableArrayPoolChunk<T>? _start;
|
||||
private RecyclableArrayPoolChunk<T>? _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()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (_chunk == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_index < 0)
|
||||
{
|
||||
_chunk = _chunk.Previous;
|
||||
if (_chunk == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_index = _chunk.Index - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
_current = _chunk.Value[_index--];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_chunk = _start;
|
||||
_index = _chunk != null ? _chunk.Index - 1 : -1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_chunk = null;
|
||||
_current = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
|
||||
@@ -0,0 +1,323 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
public class RecyclableDictionaryTests
|
||||
{
|
||||
private static readonly (int, string)[] _testData = new[] { (1, "a"), (2, "b"), (3, "c"), (4, "d"), (5, "e") };
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddShouldStoreItems(int itemsCount)
|
||||
{
|
||||
using var dict = new RecyclableDictionary<long, long>();
|
||||
foreach (var key in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
dict.Add(key, -key);
|
||||
}
|
||||
|
||||
_ = dict.Should().HaveCount(itemsCount);
|
||||
for (var i = 0; i < itemsCount; i++)
|
||||
{
|
||||
long expectedKey = i + 1;
|
||||
dict.GetKey(i).Should().Be(expectedKey);
|
||||
dict.GetValue(i).Should().Be(-expectedKey);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexerShouldUpdateValue()
|
||||
{
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
dict.Add(1, "a");
|
||||
|
||||
dict[1] = "b";
|
||||
|
||||
_ = dict[1].Should().Be("b");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void RemoveShouldSwapWithLast(int itemsCount)
|
||||
{
|
||||
using var dict = new RecyclableDictionary<long, long>();
|
||||
foreach (var key in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
dict.Add(key, key);
|
||||
}
|
||||
|
||||
if (itemsCount > 0)
|
||||
{
|
||||
long keyToRemove = (itemsCount + 1) / 2;
|
||||
dict.Remove(keyToRemove).Should().BeTrue();
|
||||
_ = dict.ContainsKey(keyToRemove).Should().BeFalse();
|
||||
_ = dict.Should().HaveCount(itemsCount - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
dict.Remove(0).Should().BeFalse();
|
||||
_ = dict.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValueShouldReturnValue()
|
||||
{
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
dict.Add(1, "a");
|
||||
|
||||
var found = dict.TryGetValue(1, out var value);
|
||||
|
||||
_ = found.Should().BeTrue();
|
||||
_ = value.Should().Be("a");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void EnumeratorShouldYieldItems(int itemsCount)
|
||||
{
|
||||
using var dict = new RecyclableDictionary<long, long>();
|
||||
foreach (var key in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
dict.Add(key, key);
|
||||
}
|
||||
|
||||
var actual = new List<long>();
|
||||
var enumerator = dict.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current.Key);
|
||||
}
|
||||
|
||||
var expected = RecyclableLongListTestData.CreateTestData(itemsCount).ToList();
|
||||
_ = actual.Should().ContainInConsecutiveOrder(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromSpan()
|
||||
{
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
dict.AddRange(data);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(i + 1);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromDictionary()
|
||||
{
|
||||
using var source = new RecyclableDictionary<int, string>();
|
||||
source.Add(1, "a");
|
||||
source.Add(2, "b");
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(2);
|
||||
dict.GetKey(0).Should().Be(1);
|
||||
dict.GetValue(0).Should().Be("a");
|
||||
dict.GetKey(1).Should().Be(2);
|
||||
dict.GetValue(1).Should().Be("b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddListItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableList<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddLongListItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableLongList<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedDictionaryItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var dict = new RecyclableDictionary<int, long>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(i);
|
||||
dict.GetValue(i).Should().Be(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var dict = new RecyclableDictionary<int, long>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(i);
|
||||
dict.GetValue(i).Should().Be(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddQueueItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableQueue<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddStackItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableStack<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddLinkedListItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclableLinkedList<KeyValuePair<int, string>>(data);
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddPriorityQueueItemsInCorrectOrder()
|
||||
{
|
||||
var data = new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b"),
|
||||
new KeyValuePair<int, string>(3, "c"),
|
||||
};
|
||||
|
||||
using var source = new RecyclablePriorityQueue<KeyValuePair<int, string>>(comparer: Comparer<KeyValuePair<int, string>>.Create((x, y) => x.Key.CompareTo(y.Key)));
|
||||
foreach (var kvp in data)
|
||||
{
|
||||
source.Enqueue(kvp);
|
||||
}
|
||||
using var dict = new RecyclableDictionary<int, string>();
|
||||
|
||||
dict.AddRange(source);
|
||||
|
||||
_ = dict.Count.Should().Be(3);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
dict.GetKey(i).Should().Be(data[i].Key);
|
||||
dict.GetValue(i).Should().Be(data[i].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
public class RecyclableHashSetTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddShouldStoreUniqueItems(int itemsCount)
|
||||
{
|
||||
using var set = new RecyclableHashSet<long>();
|
||||
foreach (var item in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
set.Add(item).Should().BeTrue();
|
||||
}
|
||||
|
||||
foreach (var item in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
set.Add(item).Should().BeFalse();
|
||||
}
|
||||
|
||||
_ = set.Should().HaveCount(itemsCount);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void RemoveShouldDeleteItem(int itemsCount)
|
||||
{
|
||||
using var set = new RecyclableHashSet<long>();
|
||||
foreach (var item in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
set.Add(item);
|
||||
}
|
||||
|
||||
if (itemsCount > 0)
|
||||
{
|
||||
long value = (itemsCount + 1) / 2;
|
||||
set.Remove(value).Should().BeTrue();
|
||||
_ = set.Contains(value).Should().BeFalse();
|
||||
_ = set.Should().HaveCount(itemsCount - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
set.Remove(0).Should().BeFalse();
|
||||
_ = set.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void EnumeratorShouldYieldItems(int itemsCount)
|
||||
{
|
||||
using var set = new RecyclableHashSet<long>();
|
||||
foreach (var item in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
set.Add(item);
|
||||
}
|
||||
|
||||
var actual = new List<long>();
|
||||
var enumerator = set.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
var expected = RecyclableLongListTestData.CreateTestData(itemsCount).ToList();
|
||||
if (itemsCount == 0)
|
||||
{
|
||||
_ = actual.Should().BeEmpty();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = actual.Should().Contain(expected).And.HaveCount(itemsCount);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromSpan()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
var data = new[] { 1, 2, 3 };
|
||||
|
||||
set.AddRange(data);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().ContainInOrder(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldIgnoreDuplicates()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
var data = new[] { 1, 2, 3 };
|
||||
|
||||
set.AddRange(data);
|
||||
set.AddRange(data);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().ContainInOrder(data);
|
||||
}
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromList()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
var list = new List<int> { 1, 2, 3 };
|
||||
|
||||
set.AddRange(list);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromRecyclableList()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
using var list = new RecyclableList<int>(new[] { 1, 2, 3 });
|
||||
|
||||
set.AddRange(list);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromLongList()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
using var list = new RecyclableLongList<int>(new[] { 1, 2, 3 });
|
||||
|
||||
set.AddRange(list);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromStack()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
using var stack = new RecyclableStack<int>(new[] { 1, 2, 3 });
|
||||
|
||||
set.AddRange(stack);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromQueue()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
using var queue = new RecyclableQueue<int>(new[] { 1, 2, 3 });
|
||||
|
||||
set.AddRange(queue);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromArray()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
Array array = new int[] { 1, 2, 3 };
|
||||
|
||||
set.AddRange(array);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromICollection()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
System.Collections.ArrayList list = new System.Collections.ArrayList { 1, 2, 3 };
|
||||
|
||||
set.AddRange(list);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromGenericICollection()
|
||||
{
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
ICollection<int> list = new List<int> { 1, 2, 3 };
|
||||
|
||||
set.AddRange(list);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromSortedSet()
|
||||
{
|
||||
using var source = new RecyclableSortedSet<int>();
|
||||
source.Add(1);
|
||||
source.Add(2);
|
||||
source.Add(3);
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
|
||||
set.AddRange(source);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromLinkedList()
|
||||
{
|
||||
using var source = new RecyclableLinkedList<int>(new[] { 1, 2, 3 });
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
|
||||
set.AddRange(source);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromPriorityQueue()
|
||||
{
|
||||
using var source = new RecyclablePriorityQueue<int>();
|
||||
source.Enqueue(1);
|
||||
source.Enqueue(2);
|
||||
source.Enqueue(3);
|
||||
using var set = new RecyclableHashSet<int>();
|
||||
|
||||
set.AddRange(source);
|
||||
|
||||
_ = set.Count.Should().Be(3);
|
||||
_ = set.Should().Contain(new[] { 1, 2, 3 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromDictionary()
|
||||
{
|
||||
using var source = new RecyclableDictionary<int, string>();
|
||||
source.Add(1, "a");
|
||||
source.Add(2, "b");
|
||||
using var set = new RecyclableHashSet<KeyValuePair<int, string>>();
|
||||
|
||||
set.AddRange(source);
|
||||
|
||||
_ = set.Count.Should().Be(2);
|
||||
set.Should().Contain(new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b")
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromSortedDictionary()
|
||||
{
|
||||
using var source = new RecyclableSortedDictionary<int, string>();
|
||||
source.Add(1, "a");
|
||||
source.Add(2, "b");
|
||||
using var set = new RecyclableHashSet<KeyValuePair<int, string>>();
|
||||
|
||||
set.AddRange(source);
|
||||
|
||||
_ = set.Count.Should().Be(2);
|
||||
set.Should().Contain(new[]
|
||||
{
|
||||
new KeyValuePair<int, string>(1, "a"),
|
||||
new KeyValuePair<int, string>(2, "b")
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddItemsFromSortedList()
|
||||
{
|
||||
using var source = new RecyclableSortedList<int, string>();
|
||||
source.Add(1, "a");
|
||||
source.Add(2, "b");
|
||||
using var set = new RecyclableHashSet<(int Key, string Value)>();
|
||||
|
||||
set.AddRange(source);
|
||||
|
||||
_ = set.Count.Should().Be(2);
|
||||
set.Should().Contain(new[] { (1, "a"), (2, "b") });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
public class RecyclableLinkedListTests
|
||||
{
|
||||
private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" };
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddLastShouldPreserveOrder(int itemsCount)
|
||||
{
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
var expected = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
foreach (var item in expected)
|
||||
{
|
||||
list.AddLast(item);
|
||||
}
|
||||
|
||||
var actual = new RecyclableLongList<long>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
actual.Add(item);
|
||||
}
|
||||
|
||||
_ = actual.Should().ContainInConsecutiveOrder(expected)
|
||||
.And.BeEquivalentTo(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddFirstShouldReverseOrder(int itemsCount)
|
||||
{
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
var source = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
foreach (var item in source)
|
||||
{
|
||||
list.AddFirst(item);
|
||||
}
|
||||
|
||||
var expected = source.Reverse().ToArray();
|
||||
var actual = new RecyclableLongList<long>();
|
||||
foreach (var item in list)
|
||||
{
|
||||
actual.Add(item);
|
||||
}
|
||||
|
||||
_ = actual.Should().ContainInConsecutiveOrder(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void RemoveFirstShouldReturnItemsInOrder(int itemsCount)
|
||||
{
|
||||
var source = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var list = new RecyclableLinkedList<long>(source);
|
||||
var actual = new RecyclableLongList<long>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
actual.Add(list.RemoveFirst());
|
||||
}
|
||||
|
||||
_ = actual.Should().ContainInConsecutiveOrder(source);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void RemoveLastShouldReturnItemsInReverseOrder(int itemsCount)
|
||||
{
|
||||
var source = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var list = new RecyclableLinkedList<long>(source);
|
||||
var actual = new RecyclableLongList<long>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
actual.Add(list.RemoveLast());
|
||||
}
|
||||
|
||||
_ = actual.Should().ContainInConsecutiveOrder(source.Reverse());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableList<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableList<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddLongListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLongList<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromLongList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLongList<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddHashSetItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableHashSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromHashSet(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableHashSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddStackItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableStack<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromStack(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableStack<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedSetItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedSet(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddLinkedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLinkedList<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromLinkedList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLinkedList<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddPriorityQueueItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclablePriorityQueue<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Enqueue(item);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromPriorityQueue(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclablePriorityQueue<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Enqueue(item);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddQueueItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableQueue<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromQueue(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableQueue<long>(data);
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddDictionaryItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<KeyValuePair<int, long>>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromDictionary(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<KeyValuePair<int, long>>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<(int Key, long Value)>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
List<(int Key, long Value)> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<(int Key, long Value)>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<(int Key, long Value)> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedDictionaryItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<KeyValuePair<int, long>>();
|
||||
|
||||
list.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedDictionary(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var list = new RecyclableLinkedList<KeyValuePair<int, long>>();
|
||||
|
||||
list.AddRange(source);
|
||||
list.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
|
||||
_ = list.LongCount.Should().Be(itemsCount * 2);
|
||||
_ = list.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddArrayItems()
|
||||
{
|
||||
long[] data = { 1, 2, 3 };
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(data);
|
||||
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddCollectionItems()
|
||||
{
|
||||
ICollection collection = new long[] { 1, 2, 3 };
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(collection);
|
||||
|
||||
_ = list.Should().Equal(collection.Cast<long>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddEnumerableItems()
|
||||
{
|
||||
IEnumerable<long> enumerable = new long[] { 1, 2, 3 };
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(enumerable);
|
||||
|
||||
_ = list.Should().Equal(enumerable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddSpanItems()
|
||||
{
|
||||
long[] data = { 1, 2, 3 };
|
||||
Span<long> span = data;
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(span);
|
||||
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAddListItems()
|
||||
{
|
||||
var data = new List<long> { 1, 2, 3 };
|
||||
using var list = new RecyclableLinkedList<long>();
|
||||
|
||||
list.AddRange(data);
|
||||
|
||||
_ = list.Should().Equal(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,495 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
using System.Collections;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
public class RecyclablePriorityQueueTests
|
||||
{
|
||||
private static readonly int[] _testData = new[] { 4, 1, 3, 2, 5 };
|
||||
|
||||
[Fact]
|
||||
public void DequeueShouldReturnItemsInSortedOrder()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
foreach (var item in _testData)
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
}
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().BeInAscendingOrder();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
_ = queue.Should().BeEmpty();
|
||||
_ = queue.LongCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyAfterClear()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
foreach (var item in _testData)
|
||||
{
|
||||
queue.Enqueue(item);
|
||||
}
|
||||
|
||||
queue.Clear();
|
||||
_ = queue.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DequeueShouldThrowWhenNoElements()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
_ = Assert.Throws<ArgumentOutOfRangeException>(() => queue.Dequeue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSpanShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange((ReadOnlySpan<int>)_testData);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSpanShouldDoNothingWhenEmpty()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange(ReadOnlySpan<int>.Empty);
|
||||
_ = queue.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSpanShouldNotOverrideItems()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange((ReadOnlySpan<int>)_testData);
|
||||
queue.AddRange((ReadOnlySpan<int>)_testData);
|
||||
|
||||
var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray();
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeArrayShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange(_testData);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeListShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange(new List<int>(_testData));
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeReadOnlyMemoryShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange((ReadOnlyMemory<int>)_testData);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeMemoryShouldNotOverrideItems()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange(_testData.AsMemory());
|
||||
queue.AddRange(_testData.AsMemory());
|
||||
|
||||
var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray();
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeArrayShouldNotOverrideItems()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
queue.AddRange(_testData);
|
||||
queue.AddRange(_testData);
|
||||
|
||||
var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray();
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAcceptNulls()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<long?>();
|
||||
queue.AddRange(new long?[] { null, 1 });
|
||||
_ = queue.Should().HaveCount(2).And.Contain((long?)null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeQueueShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var source = new RecyclableQueue<int>(_testData);
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeQueueShouldNotOverrideItems()
|
||||
{
|
||||
using var source = new RecyclableQueue<int>(_testData);
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray();
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeStackShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var source = new RecyclableStack<int>(_testData);
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSortedSetShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var source = new RecyclableSortedSet<int>();
|
||||
foreach (int item in _testData)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeDictionaryShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var source = new RecyclableDictionary<int, int>();
|
||||
for (int i = 0; i < _testData.Length; i++)
|
||||
{
|
||||
source.Add(i, _testData[i]);
|
||||
}
|
||||
|
||||
using var queue = new RecyclablePriorityQueue<KeyValuePair<int, int>>(comparer: Comparer<KeyValuePair<int, int>>.Create((a, b) => a.Key.CompareTo(b.Key)));
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<KeyValuePair<int, int>>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.Select((v, i) => new KeyValuePair<int, int>(i, v)).ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeDictionaryShouldNotOverrideItems()
|
||||
{
|
||||
using var source = new RecyclableDictionary<int, int>();
|
||||
for (int i = 0; i < _testData.Length; i++)
|
||||
{
|
||||
source.Add(i, _testData[i]);
|
||||
}
|
||||
|
||||
using var queue = new RecyclablePriorityQueue<KeyValuePair<int, int>>(comparer: Comparer<KeyValuePair<int, int>>.Create((a, b) => a.Key.CompareTo(b.Key)));
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<KeyValuePair<int, int>>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.Select((v, i) => new KeyValuePair<int, int>(i, v))
|
||||
.Concat(_testData.Select((v, i) => new KeyValuePair<int, int>(i, v)))
|
||||
.ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSortedListShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var source = new RecyclableSortedList<int, int>();
|
||||
for (int i = 0; i < _testData.Length; i++)
|
||||
{
|
||||
source.Add(i, _testData[i]);
|
||||
}
|
||||
|
||||
using var queue = new RecyclablePriorityQueue<(int Key, int Value)>(comparer: Comparer<(int Key, int Value)>.Create((a, b) => a.Key.CompareTo(b.Key)));
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<(int Key, int Value)>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.Select((v, i) => (i, v)).ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSortedListShouldNotOverrideItems()
|
||||
{
|
||||
using var source = new RecyclableSortedList<int, int>();
|
||||
for (int i = 0; i < _testData.Length; i++)
|
||||
{
|
||||
source.Add(i, _testData[i]);
|
||||
}
|
||||
|
||||
using var queue = new RecyclablePriorityQueue<(int Key, int Value)>(comparer: Comparer<(int Key, int Value)>.Create((a, b) => a.Key.CompareTo(b.Key)));
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<(int Key, int Value)>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.Select((v, i) => (i, v))
|
||||
.Concat(_testData.Select((v, i) => (i, v)))
|
||||
.OrderBy(p => p.Item1)
|
||||
.ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSortedDictionaryShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var source = new RecyclableSortedDictionary<int, int>();
|
||||
for (int i = 0; i < _testData.Length; i++)
|
||||
{
|
||||
source.Add(i, _testData[i]);
|
||||
}
|
||||
|
||||
using var queue = new RecyclablePriorityQueue<KeyValuePair<int, int>>(comparer: Comparer<KeyValuePair<int, int>>.Create((a, b) => a.Key.CompareTo(b.Key)));
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<KeyValuePair<int, int>>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.Select((v, i) => new KeyValuePair<int, int>(i, v)).ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeSortedDictionaryShouldNotOverrideItems()
|
||||
{
|
||||
using var source = new RecyclableSortedDictionary<int, int>();
|
||||
for (int i = 0; i < _testData.Length; i++)
|
||||
{
|
||||
source.Add(i, _testData[i]);
|
||||
}
|
||||
|
||||
using var queue = new RecyclablePriorityQueue<KeyValuePair<int, int>>(comparer: Comparer<KeyValuePair<int, int>>.Create((a, b) => a.Key.CompareTo(b.Key)));
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<KeyValuePair<int, int>>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.Select((v, i) => new KeyValuePair<int, int>(i, v))
|
||||
.Concat(_testData.Select((v, i) => new KeyValuePair<int, int>(i, v)))
|
||||
.ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeEnumerableShouldAddItemsInSortedOrder()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
_ = queue.AddRange((System.Collections.IEnumerable)_testData);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.OrderBy(v => v).ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeEnumerableShouldNotOverrideItems()
|
||||
{
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
_ = queue.AddRange((System.Collections.IEnumerable)_testData);
|
||||
_ = queue.AddRange((System.Collections.IEnumerable)_testData);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
var expected = _testData.Concat(_testData).OrderBy(v => v).ToList();
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeReadOnlyCollectionShouldAddItemsInSortedOrder()
|
||||
{
|
||||
var source = new TestReadOnlyCollection<int>(_testData);
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(_testData.OrderBy(static value => value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeReadOnlyCollectionShouldNotOverrideItems()
|
||||
{
|
||||
var source = new TestReadOnlyCollection<int>(_testData);
|
||||
using var queue = new RecyclablePriorityQueue<int>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
var expected = _testData.Concat(_testData).OrderBy(static value => value).ToArray();
|
||||
var result = new List<int>();
|
||||
while (queue.LongCount > 0)
|
||||
{
|
||||
result.Add(queue.Dequeue());
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(expected);
|
||||
}
|
||||
|
||||
private sealed class TestReadOnlyCollection<T> : IReadOnlyCollection<T>
|
||||
{
|
||||
private readonly T[] _items;
|
||||
|
||||
public TestReadOnlyCollection(T[] items)
|
||||
{
|
||||
_items = items;
|
||||
}
|
||||
|
||||
public int Count => _items.Length;
|
||||
|
||||
public IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)_items).GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,117 +7,656 @@ namespace Recyclable.CollectionsTests
|
||||
{
|
||||
private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" };
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotBeSortedWhenCreated()
|
||||
{
|
||||
// Act
|
||||
using var list = new RecyclableQueue<string>(_testData, 2);
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void ShouldNotBeSortedWhenCreated(int itemsCount)
|
||||
{
|
||||
// Act
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var list = new RecyclableQueue<string>(data, 2);
|
||||
|
||||
// Validate
|
||||
_ = list.Should().HaveCount(_testData.Length)
|
||||
.And.ContainInConsecutiveOrder(_testData);
|
||||
_ = list.LongCount.Should().Be(_testData.Length);
|
||||
}
|
||||
// Validate
|
||||
_ = list.Should().HaveCount(itemsCount)
|
||||
.And.ContainInConsecutiveOrder(data);
|
||||
_ = list.LongCount.Should().Be(itemsCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var list = new RecyclableQueue<string>(2);
|
||||
[Fact]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var list = new RecyclableQueue<string>(2);
|
||||
|
||||
// Validate
|
||||
_ = list.Should().BeEmpty();
|
||||
_ = list.LongCount.Should().Be(0);
|
||||
}
|
||||
// Validate
|
||||
_ = list.Should().BeEmpty();
|
||||
_ = list.LongCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotBeSortedWhenInitialized()
|
||||
{
|
||||
// Act
|
||||
using var list = new RecyclableQueue<string>(2)
|
||||
{
|
||||
_testData[0],
|
||||
_testData[1],
|
||||
_testData[2],
|
||||
_testData[3],
|
||||
_testData[4]
|
||||
};
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void ShouldNotBeSortedWhenInitialized(int itemsCount)
|
||||
{
|
||||
// Act
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var list = new RecyclableQueue<string>(2)
|
||||
{
|
||||
// populate via collection initializer
|
||||
};
|
||||
foreach (var item in data)
|
||||
{
|
||||
list.Enqueue(item);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = list.Should().NotBeEmpty()
|
||||
.And.ContainInConsecutiveOrder(_testData)
|
||||
.And.BeEquivalentTo(_testData);
|
||||
}
|
||||
// Validate
|
||||
if (itemsCount == 0)
|
||||
{
|
||||
_ = list.Should().BeEmpty();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = list.Should().NotBeEmpty()
|
||||
.And.ContainInConsecutiveOrder(data)
|
||||
.And.BeEquivalentTo(data);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyAfterClear()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void ShouldBeEmptyAfterClear(int itemsCount)
|
||||
{
|
||||
// Prepare
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString());
|
||||
using var list = new RecyclableQueue<string>(data);
|
||||
|
||||
// Act
|
||||
list.Clear();
|
||||
// Act
|
||||
list.Clear();
|
||||
|
||||
// Validate
|
||||
_ = list.Should().BeEmpty();
|
||||
}
|
||||
// Validate
|
||||
_ = list.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DequeueShouldReturnItemsInTheSameOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>();
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void DequeueShouldReturnItemsInTheSameOrder(int itemsCount)
|
||||
{
|
||||
// Prepare
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var list = new RecyclableQueue<string>();
|
||||
|
||||
// Act
|
||||
for (var itemIdx = 0; itemIdx < _testData.Length; itemIdx++)
|
||||
{
|
||||
list.Enqueue(_testData[itemIdx]);
|
||||
}
|
||||
// Act
|
||||
for (var itemIdx = 0; itemIdx < data.Length; itemIdx++)
|
||||
{
|
||||
list.Enqueue(data[itemIdx]);
|
||||
}
|
||||
|
||||
var dequeuedItems = new RecyclableLongList<string>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
dequeuedItems.Add(list.Dequeue());
|
||||
}
|
||||
var dequeuedItems = new RecyclableLongList<string>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
dequeuedItems.Add(list.Dequeue());
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData)
|
||||
.And.BeEquivalentTo(_testData);
|
||||
}
|
||||
// Validate
|
||||
_ = dequeuedItems.Should().ContainInConsecutiveOrder(data)
|
||||
.And.BeEquivalentTo(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DequeueShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
[Fact]
|
||||
public void DequeueShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
|
||||
// Act
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
_ = list.Dequeue();
|
||||
}
|
||||
// Act
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
_ = list.Dequeue();
|
||||
}
|
||||
|
||||
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.Dequeue());
|
||||
_ = list.LongCount.Should().Be(0);
|
||||
}
|
||||
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.Dequeue());
|
||||
_ = list.LongCount.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetEnumeratorShouldYieldAllItemsInTheSameOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void GetEnumeratorShouldYieldAllItemsInTheSameOrder(int itemsCount)
|
||||
{
|
||||
// Prepare
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var list = new RecyclableQueue<string>(data);
|
||||
|
||||
// Act
|
||||
var actual = new RecyclableLongList<string>();
|
||||
using var enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
// Act
|
||||
var actual = new RecyclableLongList<string>();
|
||||
using var enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = actual.Should().ContainInConsecutiveOrder(_testData)
|
||||
.And.BeEquivalentTo(_testData);
|
||||
}
|
||||
}
|
||||
// Validate
|
||||
_ = actual.Should().ContainInConsecutiveOrder(data)
|
||||
.And.BeEquivalentTo(data);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddRangeShouldAcceptNulls()
|
||||
{
|
||||
using var queue = new RecyclableQueue<long?>();
|
||||
queue.AddRange(new long?[] { null, default });
|
||||
_ = queue.Should().HaveCount(2).And.AllSatisfy(x => x.Should().BeNull());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount)
|
||||
{
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((Array)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((System.Collections.ICollection)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((ICollection<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = queue.AddRange((System.Collections.IEnumerable)testData);
|
||||
}
|
||||
else if (testCase.Contains("IReadOnlyList<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((IReadOnlyList<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("ReadOnlySpan<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange(new ReadOnlySpan<long>((long[])testData));
|
||||
}
|
||||
else if (testCase.Contains("Span<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange(new Span<long>((long[])testData));
|
||||
}
|
||||
else if (testData is long[] array)
|
||||
{
|
||||
queue.AddRange(array);
|
||||
}
|
||||
else if (testData is List<long> list)
|
||||
{
|
||||
queue.AddRange(list);
|
||||
}
|
||||
else if (testData is RecyclableList<long> rList)
|
||||
{
|
||||
queue.AddRange(rList);
|
||||
}
|
||||
else if (testData is RecyclableLongList<long> rLongList)
|
||||
{
|
||||
queue.AddRange(rLongList);
|
||||
}
|
||||
else if (testData is IList<long> iList)
|
||||
{
|
||||
queue.AddRange((ICollection<long>)iList);
|
||||
}
|
||||
else if (testData is IEnumerable<long> enumerable)
|
||||
{
|
||||
queue.AddRange(enumerable);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Unknown type of test data");
|
||||
}
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(testData);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable<long> testData, int itemsCount)
|
||||
{
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((Array)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((System.Collections.ICollection)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((ICollection<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = queue.AddRange((System.Collections.IEnumerable)testData);
|
||||
}
|
||||
else if (testCase.Contains("IReadOnlyList<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((IReadOnlyList<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("ReadOnlySpan<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange(new ReadOnlySpan<long>((long[])testData));
|
||||
}
|
||||
else if (testCase.Contains("Span<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange(new Span<long>((long[])testData));
|
||||
}
|
||||
else if (testData is long[] array)
|
||||
{
|
||||
queue.AddRange(array);
|
||||
}
|
||||
else if (testData is List<long> list)
|
||||
{
|
||||
queue.AddRange(list);
|
||||
}
|
||||
else if (testData is RecyclableList<long> rList)
|
||||
{
|
||||
queue.AddRange(rList);
|
||||
}
|
||||
else if (testData is RecyclableLongList<long> rLongList)
|
||||
{
|
||||
queue.AddRange(rLongList);
|
||||
}
|
||||
else if (testData is IList<long> iList)
|
||||
{
|
||||
queue.AddRange((ICollection<long>)iList);
|
||||
}
|
||||
else if (testData is IEnumerable<long> enumerable)
|
||||
{
|
||||
queue.AddRange(enumerable);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Unknown type of test data");
|
||||
}
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(testData);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable<long> testData, int itemsCount)
|
||||
{
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
itemsCount = checked(itemsCount << 1);
|
||||
|
||||
if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((Array)testData);
|
||||
queue.AddRange((Array)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((System.Collections.ICollection)testData);
|
||||
queue.AddRange((System.Collections.ICollection)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((ICollection<long>)testData);
|
||||
queue.AddRange((ICollection<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = queue.AddRange((System.Collections.IEnumerable)testData);
|
||||
_ = queue.AddRange((System.Collections.IEnumerable)testData);
|
||||
}
|
||||
else if (testCase.Contains("IReadOnlyList<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
queue.AddRange((IReadOnlyList<long>)testData);
|
||||
queue.AddRange((IReadOnlyList<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("ReadOnlySpan<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var span = new ReadOnlySpan<long>((long[])testData);
|
||||
queue.AddRange(span);
|
||||
queue.AddRange(span);
|
||||
}
|
||||
else if (testCase.Contains("Span<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var span = new Span<long>((long[])testData);
|
||||
queue.AddRange(span);
|
||||
queue.AddRange(span);
|
||||
}
|
||||
else if (testData is long[] array)
|
||||
{
|
||||
queue.AddRange(array);
|
||||
queue.AddRange(array);
|
||||
}
|
||||
else if (testData is List<long> list)
|
||||
{
|
||||
queue.AddRange(list);
|
||||
queue.AddRange(list);
|
||||
}
|
||||
else if (testData is RecyclableList<long> rList)
|
||||
{
|
||||
queue.AddRange(rList);
|
||||
queue.AddRange(rList);
|
||||
}
|
||||
else if (testData is RecyclableLongList<long> rLongList)
|
||||
{
|
||||
queue.AddRange(rLongList);
|
||||
queue.AddRange(rLongList);
|
||||
}
|
||||
else if (testData is IList<long> iList)
|
||||
{
|
||||
queue.AddRange((ICollection<long>)iList);
|
||||
queue.AddRange((ICollection<long>)iList);
|
||||
}
|
||||
else if (testData is IEnumerable<long> enumerable)
|
||||
{
|
||||
queue.AddRange(enumerable);
|
||||
queue.AddRange(enumerable);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Unknown type of test data");
|
||||
}
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(testData);
|
||||
expected.AddRange(testData);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddStackItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableStack<long>(data);
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromStack(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableStack<long>(data);
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount * 2);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedSetItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedSet(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount * 2);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddLinkedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLinkedList<long>(data);
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromLinkedList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLinkedList<long>(data);
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount * 2);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddPriorityQueueItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclablePriorityQueue<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Enqueue(item);
|
||||
}
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromPriorityQueue(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclablePriorityQueue<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Enqueue(item);
|
||||
}
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount * 2);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddQueueItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableQueue<long>(data);
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(data);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromQueue(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableQueue<long>(data);
|
||||
using var queue = new RecyclableQueue<long>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount * 2);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddDictionaryItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var queue = new RecyclableQueue<KeyValuePair<int, long>>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromDictionary(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var queue = new RecyclableQueue<KeyValuePair<int, long>>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount * 2);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var queue = new RecyclableQueue<(int Key, long Value)>();
|
||||
|
||||
queue.AddRange(source);
|
||||
|
||||
List<(int Key, long Value)> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var queue = new RecyclableQueue<(int Key, long Value)>();
|
||||
|
||||
queue.AddRange(source);
|
||||
queue.AddRange(source);
|
||||
|
||||
List<(int Key, long Value)> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
|
||||
_ = queue.Count.Should().Be(itemsCount * 2);
|
||||
_ = queue.Should().Equal(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
public class RecyclableSortedDictionaryTests
|
||||
{
|
||||
[Fact]
|
||||
public void AddShouldStoreUniqueItemsInOrder()
|
||||
{
|
||||
using var dict = new RecyclableSortedDictionary<int, int>();
|
||||
var values = new[] { 4, 1, 3, 2, 5 };
|
||||
foreach (var v in values)
|
||||
{
|
||||
dict.Add(v, v);
|
||||
}
|
||||
|
||||
var result = new List<int>();
|
||||
foreach (var kv in dict)
|
||||
{
|
||||
result.Add(kv.Key);
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(values.OrderBy(x => x));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexerShouldUpdateValue()
|
||||
{
|
||||
using var dict = new RecyclableSortedDictionary<int, string>();
|
||||
dict.Add(1, "a");
|
||||
|
||||
dict[1] = "b";
|
||||
|
||||
_ = dict[1].Should().Be("b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RemoveShouldDeleteItem()
|
||||
{
|
||||
using var dict = new RecyclableSortedDictionary<int, string>();
|
||||
var values = new[] { 4, 1, 3, 2, 5 };
|
||||
foreach (var v in values)
|
||||
{
|
||||
dict.Add(v, v.ToString());
|
||||
}
|
||||
|
||||
dict.Remove(3).Should().BeTrue();
|
||||
_ = dict.ContainsKey(3).Should().BeFalse();
|
||||
_ = dict.Count.Should().Be(values.Length - 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumeratorShouldYieldItemsInOrder()
|
||||
{
|
||||
using var dict = new RecyclableSortedDictionary<int, int>();
|
||||
foreach (var v in new[] { 3, 1, 4, 2 })
|
||||
{
|
||||
dict.Add(v, v);
|
||||
}
|
||||
|
||||
var result = new List<int>();
|
||||
foreach (var item in dict)
|
||||
{
|
||||
result.Add(item.Key);
|
||||
}
|
||||
|
||||
_ = result.Should().Equal(new[] { 1, 2, 3, 4 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryGetValueShouldReturnValue()
|
||||
{
|
||||
using var dict = new RecyclableSortedDictionary<int, string>();
|
||||
dict.Add(1, "a");
|
||||
|
||||
var found = dict.TryGetValue(1, out var value);
|
||||
|
||||
_ = found.Should().BeTrue();
|
||||
_ = value.Should().Be("a");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyAfterClear()
|
||||
{
|
||||
using var dict = new RecyclableSortedDictionary<int, int>();
|
||||
dict.Add(1, 1);
|
||||
dict.Add(2, 2);
|
||||
|
||||
dict.Clear();
|
||||
_ = dict.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +1,97 @@
|
||||
using FluentAssertions;
|
||||
using FluentAssertions;
|
||||
using Recyclable.Collections;
|
||||
using System.Linq;
|
||||
|
||||
namespace Recyclable.CollectionsTests
|
||||
{
|
||||
public class RecyclableSortedListTests
|
||||
{
|
||||
private static readonly (int, string)[] _testData = new[] { (5, "a"), (4, "d"), (3, "c"), (2, "b"), (1, "a") };
|
||||
public class RecyclableSortedListTests
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddShouldStoreItemsSorted(int itemsCount)
|
||||
{
|
||||
using var list = new RecyclableSortedList<long, long>();
|
||||
foreach (var key in RecyclableLongListTestData.CreateTestData(itemsCount).Reverse())
|
||||
{
|
||||
list.Add(key, -key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeSortedWhenCreated()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableSortedList<int, string>(_testData, 2);
|
||||
_ = list.Should().HaveCount(itemsCount)
|
||||
.And.BeInAscendingOrder(x => x.Key);
|
||||
for (int i = 0; i < itemsCount; i++)
|
||||
{
|
||||
long expectedKey = i + 1;
|
||||
list.GetKey(i).Should().Be(expectedKey);
|
||||
list.GetValue(i).Should().Be(-expectedKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().HaveCount(_testData.Length)
|
||||
.And.BeEquivalentTo(_testData)
|
||||
.And.BeInAscendingOrder((item) => item.Key);
|
||||
}
|
||||
[Fact]
|
||||
public void IndexerShouldUpdateValue()
|
||||
{
|
||||
using var list = new RecyclableSortedList<int, string>();
|
||||
list.Add(1, "a");
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableSortedList<int, string>(2);
|
||||
list[1] = "b";
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
_ = list[1].Should().Be("b");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotBeSortedWhenInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableSortedList<int, string>(2)
|
||||
{
|
||||
_testData[0],
|
||||
_testData[1],
|
||||
_testData[2],
|
||||
_testData[3],
|
||||
_testData[4]
|
||||
};
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void RemoveShouldDeleteItem(int itemsCount)
|
||||
{
|
||||
using var list = new RecyclableSortedList<long, long>();
|
||||
foreach (var key in RecyclableLongListTestData.CreateTestData(itemsCount))
|
||||
{
|
||||
list.Add(key, key);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().NotBeEmpty()
|
||||
.And.ContainInConsecutiveOrder(_testData);
|
||||
}
|
||||
if (itemsCount > 0)
|
||||
{
|
||||
long keyToRemove = (itemsCount + 1) / 2;
|
||||
list.Remove(keyToRemove).Should().BeTrue();
|
||||
_ = list.ContainsKey(keyToRemove).Should().BeFalse();
|
||||
_ = list.Should().HaveCount(itemsCount - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Remove(0).Should().BeFalse();
|
||||
_ = list.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeSortedWhenUpdateEnds()
|
||||
{
|
||||
// Prepare
|
||||
using var sortedList = new RecyclableSortedList<int, string>(2);
|
||||
[Fact]
|
||||
public void TryGetValueShouldReturnValue()
|
||||
{
|
||||
using var list = new RecyclableSortedList<int, string>();
|
||||
list.Add(1, "a");
|
||||
|
||||
// Act
|
||||
sortedList.BeginUpdate();
|
||||
foreach (var testItem in _testData)
|
||||
{
|
||||
sortedList.Add(testItem);
|
||||
}
|
||||
var found = list.TryGetValue(1, out var value);
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeInDescendingOrder(x => x.Key);
|
||||
_ = found.Should().BeTrue();
|
||||
_ = value.Should().Be("a");
|
||||
}
|
||||
|
||||
// Act
|
||||
sortedList.EndUpdate();
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void EnumeratorShouldYieldItemsSorted(int itemsCount)
|
||||
{
|
||||
using var list = new RecyclableSortedList<long, long>();
|
||||
foreach (var key in RecyclableLongListTestData.CreateTestData(itemsCount).Reverse())
|
||||
{
|
||||
list.Add(key, key);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeInAscendingOrder(x => x.Key);
|
||||
}
|
||||
var actual = new List<long>();
|
||||
var enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current.Key);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyAfterClear()
|
||||
{
|
||||
// Prepare
|
||||
using var sortedList = new RecyclableSortedList<int, string>(_testData);
|
||||
|
||||
// Act
|
||||
sortedList.Clear();
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
var expected = RecyclableLongListTestData.CreateTestData(itemsCount).ToList();
|
||||
_ = actual.Should().ContainInConsecutiveOrder(expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,112 +7,811 @@ namespace Recyclable.CollectionsTests
|
||||
{
|
||||
private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" };
|
||||
|
||||
[Fact]
|
||||
public void GetEnumeratorShouldYieldAllItemsInReversedOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableStack<string>(_testData);
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void GetEnumeratorShouldYieldAllItemsInReversedOrder(int itemsCount)
|
||||
{
|
||||
// Prepare
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var list = new RecyclableStack<string>(data);
|
||||
|
||||
// Act
|
||||
var actual = new RecyclableLongList<string>();
|
||||
using var enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
// Act
|
||||
var actual = new RecyclableLongList<string>();
|
||||
using var enumerator = list.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
actual.Add(enumerator.Current);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = actual.Should().ContainInConsecutiveOrder(_testData.Reverse());
|
||||
}
|
||||
// Validate
|
||||
_ = actual.Should().ContainInConsecutiveOrder(data.Reverse());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotBeSortedWhenCreated()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableStack<string>(_testData, 2);
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void ShouldNotBeSortedWhenCreated(int itemsCount)
|
||||
{
|
||||
// Act
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var sortedList = new RecyclableStack<string>(data, 2);
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().HaveCount(_testData.Length)
|
||||
.And.ContainInConsecutiveOrder(_testData);
|
||||
}
|
||||
// Validate
|
||||
_ = sortedList.Should().HaveCount(itemsCount)
|
||||
.And.ContainInConsecutiveOrder(data.Reverse());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableStack<string>(2);
|
||||
[Fact]
|
||||
public void ShouldBeEmptyWhenNotInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableStack<string>(2);
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldNotBeSortedWhenInitialized()
|
||||
{
|
||||
// Act
|
||||
using var sortedList = new RecyclableStack<string>(2)
|
||||
{
|
||||
_testData[0],
|
||||
_testData[1],
|
||||
_testData[2],
|
||||
_testData[3],
|
||||
_testData[4]
|
||||
};
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void ShouldNotBeSortedWhenInitialized(int itemsCount)
|
||||
{
|
||||
// Act
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var sortedList = new RecyclableStack<string>(2);
|
||||
foreach (var item in data)
|
||||
{
|
||||
sortedList.Push(item);
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().NotBeEmpty()
|
||||
.And.ContainInConsecutiveOrder(_testData)
|
||||
.And.BeEquivalentTo(_testData);
|
||||
}
|
||||
// Validate
|
||||
if (itemsCount == 0)
|
||||
{
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = sortedList.Should().NotBeEmpty()
|
||||
.And.ContainInConsecutiveOrder(data.Reverse())
|
||||
.And.BeEquivalentTo(data);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ShouldBeEmptyAfterClear()
|
||||
{
|
||||
// Prepare
|
||||
using var sortedList = new RecyclableStack<string>(_testData);
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void ShouldBeEmptyAfterClear(int itemsCount)
|
||||
{
|
||||
// Prepare
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
using var sortedList = new RecyclableStack<string>(data);
|
||||
|
||||
// Act
|
||||
sortedList.Clear();
|
||||
// Act
|
||||
sortedList.Clear();
|
||||
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
// Validate
|
||||
_ = sortedList.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PopShouldReturnItemsInReversedOrder()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableStack<string>();
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void PopShouldReturnItemsInReversedOrder(int itemsCount)
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableStack<string>();
|
||||
|
||||
// Act
|
||||
for (var itemIdx = 0; itemIdx < _testData.Length; itemIdx++)
|
||||
{
|
||||
list.Push(_testData[itemIdx]);
|
||||
}
|
||||
// Act
|
||||
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
|
||||
for (var itemIdx = 0; itemIdx < data.Length; itemIdx++)
|
||||
{
|
||||
list.Push(data[itemIdx]);
|
||||
}
|
||||
|
||||
var dequeuedItems = new RecyclableLongList<string>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
dequeuedItems.Add(list.Pop());
|
||||
}
|
||||
var dequeuedItems = new RecyclableLongList<string>();
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
dequeuedItems.Add(list.Pop());
|
||||
}
|
||||
|
||||
// Validate
|
||||
_ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData.Reverse());
|
||||
}
|
||||
// Validate
|
||||
_ = dequeuedItems.Should().ContainInConsecutiveOrder(data.Reverse());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PopShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableQueue<string>(_testData);
|
||||
[Fact]
|
||||
public void PopShouldRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
|
||||
{
|
||||
// Prepare
|
||||
using var list = new RecyclableStack<string>(_testData);
|
||||
|
||||
// Act
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
_ = list.Dequeue();
|
||||
}
|
||||
// Act
|
||||
while (list.LongCount > 0)
|
||||
{
|
||||
_ = list.Pop();
|
||||
}
|
||||
|
||||
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.Dequeue());
|
||||
}
|
||||
}
|
||||
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.Pop());
|
||||
}
|
||||
[Fact]
|
||||
public void AddRangeShouldAcceptNulls()
|
||||
{
|
||||
using var stack = new RecyclableStack<long?>();
|
||||
stack.AddRange(new long?[] { null, default });
|
||||
_ = stack.Should().HaveCount(2).And.AllSatisfy(x => x.Should().BeNull());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddItemsInCorrectOrder(string testCase, IEnumerable<long> testData, int itemsCount)
|
||||
{
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((Array)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((System.Collections.ICollection)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((ICollection<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = stack.AddRange((System.Collections.IEnumerable)testData);
|
||||
}
|
||||
else if (testCase.Contains("IReadOnlyList<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((IReadOnlyList<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("ReadOnlySpan<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange(new ReadOnlySpan<long>((long[])testData));
|
||||
}
|
||||
else if (testCase.Contains("Span<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange(new Span<long>((long[])testData));
|
||||
}
|
||||
else if (testData is long[] array)
|
||||
{
|
||||
stack.AddRange(array);
|
||||
}
|
||||
else if (testData is List<long> list)
|
||||
{
|
||||
stack.AddRange(list);
|
||||
}
|
||||
else if (testData is RecyclableList<long> rList)
|
||||
{
|
||||
stack.AddRange(rList);
|
||||
}
|
||||
else if (testData is RecyclableLongList<long> rLongList)
|
||||
{
|
||||
stack.AddRange(rLongList);
|
||||
}
|
||||
else if (testData is IList<long> iList)
|
||||
{
|
||||
stack.AddRange((ICollection<long>)iList);
|
||||
}
|
||||
else if (testData is IEnumerable<long> enumerable)
|
||||
{
|
||||
stack.AddRange(enumerable);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Unknown type of test data");
|
||||
}
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(testData);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.EmptySourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldDoNothingWhenSourceIsEmpty(string testCase, IEnumerable<long> testData, int itemsCount)
|
||||
{
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((Array)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((System.Collections.ICollection)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((ICollection<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = stack.AddRange((System.Collections.IEnumerable)testData);
|
||||
}
|
||||
else if (testCase.Contains("IReadOnlyList<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((IReadOnlyList<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("ReadOnlySpan<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange(new ReadOnlySpan<long>((long[])testData));
|
||||
}
|
||||
else if (testCase.Contains("Span<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange(new Span<long>((long[])testData));
|
||||
}
|
||||
else if (testData is long[] array)
|
||||
{
|
||||
stack.AddRange(array);
|
||||
}
|
||||
else if (testData is List<long> list)
|
||||
{
|
||||
stack.AddRange(list);
|
||||
}
|
||||
else if (testData is RecyclableList<long> rList)
|
||||
{
|
||||
stack.AddRange(rList);
|
||||
}
|
||||
else if (testData is RecyclableLongList<long> rLongList)
|
||||
{
|
||||
stack.AddRange(rLongList);
|
||||
}
|
||||
else if (testData is IList<long> iList)
|
||||
{
|
||||
stack.AddRange((ICollection<long>)iList);
|
||||
}
|
||||
else if (testData is IEnumerable<long> enumerable)
|
||||
{
|
||||
stack.AddRange(enumerable);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Unknown type of test data");
|
||||
}
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(testData);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.SourceDataVariants), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItems(string testCase, IEnumerable<long> testData, int itemsCount)
|
||||
{
|
||||
using var stack = new RecyclableStack<long>();
|
||||
itemsCount = checked(itemsCount << 1);
|
||||
|
||||
if (testCase.Contains("Array[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((Array)testData);
|
||||
stack.AddRange((Array)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((System.Collections.ICollection)testData);
|
||||
stack.AddRange((System.Collections.ICollection)testData);
|
||||
}
|
||||
else if (testCase.Contains("ICollection<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((ICollection<long>)testData);
|
||||
stack.AddRange((ICollection<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("IEnumerable[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
_ = stack.AddRange((System.Collections.IEnumerable)testData);
|
||||
_ = stack.AddRange((System.Collections.IEnumerable)testData);
|
||||
}
|
||||
else if (testCase.Contains("IReadOnlyList<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
stack.AddRange((IReadOnlyList<long>)testData);
|
||||
stack.AddRange((IReadOnlyList<long>)testData);
|
||||
}
|
||||
else if (testCase.Contains("ReadOnlySpan<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var span = new ReadOnlySpan<long>((long[])testData);
|
||||
stack.AddRange(span);
|
||||
stack.AddRange(span);
|
||||
}
|
||||
else if (testCase.Contains("Span<T>[", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var span = new Span<long>((long[])testData);
|
||||
stack.AddRange(span);
|
||||
stack.AddRange(span);
|
||||
}
|
||||
else if (testData is long[] array)
|
||||
{
|
||||
stack.AddRange(array);
|
||||
stack.AddRange(array);
|
||||
}
|
||||
else if (testData is List<long> list)
|
||||
{
|
||||
stack.AddRange(list);
|
||||
stack.AddRange(list);
|
||||
}
|
||||
else if (testData is RecyclableList<long> rList)
|
||||
{
|
||||
stack.AddRange(rList);
|
||||
stack.AddRange(rList);
|
||||
}
|
||||
else if (testData is RecyclableLongList<long> rLongList)
|
||||
{
|
||||
stack.AddRange(rLongList);
|
||||
stack.AddRange(rLongList);
|
||||
}
|
||||
else if (testData is IList<long> iList)
|
||||
{
|
||||
stack.AddRange((ICollection<long>)iList);
|
||||
stack.AddRange((ICollection<long>)iList);
|
||||
}
|
||||
else if (testData is IEnumerable<long> enumerable)
|
||||
{
|
||||
stack.AddRange(enumerable);
|
||||
stack.AddRange(enumerable);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidCastException("Unknown type of test data");
|
||||
}
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(testData);
|
||||
expected.AddRange(testData);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableList<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableList<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddLongListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLongList<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromLongList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLongList<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddHashSetItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableHashSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromHashSet(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableHashSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddStackItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableStack<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromStack(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableStack<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedSetItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedSet(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedSet<long>();
|
||||
foreach (long item in data)
|
||||
{
|
||||
source.Add(item);
|
||||
}
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddLinkedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLinkedList<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromLinkedList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableLinkedList<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddQueueItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableQueue<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromQueue(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableQueue<long>(data);
|
||||
using var stack = new RecyclableStack<long>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<long> expected = new(itemsCount * 2);
|
||||
expected.AddRange(data);
|
||||
expected.AddRange(data);
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddDictionaryItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var stack = new RecyclableStack<KeyValuePair<int, long>>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromDictionary(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var stack = new RecyclableStack<KeyValuePair<int, long>>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedListItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var stack = new RecyclableStack<(int Key, long Value)>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<(int Key, long Value)> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedList(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedList<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var stack = new RecyclableStack<(int Key, long Value)>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<(int Key, long Value)> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add((i, data[i]));
|
||||
}
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldAddSortedDictionaryItemsInCorrectOrder(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var stack = new RecyclableStack<KeyValuePair<int, long>>();
|
||||
|
||||
stack.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
|
||||
public void AddRangeShouldNotOverrideItemsFromSortedDictionary(int itemsCount)
|
||||
{
|
||||
long[] data = RecyclableLongListTestData.CreateTestData(itemsCount).ToArray();
|
||||
using var source = new RecyclableSortedDictionary<int, long>();
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
source.Add(i, data[i]);
|
||||
}
|
||||
using var stack = new RecyclableStack<KeyValuePair<int, long>>();
|
||||
|
||||
stack.AddRange(source);
|
||||
stack.AddRange(source);
|
||||
|
||||
List<KeyValuePair<int, long>> expected = new(itemsCount * 2);
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
for (int i = 0; i < data.Length; i++)
|
||||
{
|
||||
expected.Add(new KeyValuePair<int, long>(i, data[i]));
|
||||
}
|
||||
expected.Reverse();
|
||||
|
||||
_ = stack.Count.Should().Be(itemsCount * 2);
|
||||
_ = stack.Should().Equal(expected);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace VersionedCollectionsGenerator
|
||||
{
|
||||
[Generator]
|
||||
public sealed class VersionedCollectionsGenerator : ISourceGenerator
|
||||
{
|
||||
private const string AttributeText = """
|
||||
using System;
|
||||
|
||||
namespace VersionedCollectionsGenerator
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
|
||||
internal sealed class GenerateVersionedAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
|
||||
internal sealed class CloneForVersionedAttribute : Attribute
|
||||
{
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
|
||||
internal sealed class RewriteForVersionedAttribute : Attribute
|
||||
{
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
public void Initialize(GeneratorInitializationContext context)
|
||||
{
|
||||
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
|
||||
}
|
||||
|
||||
public void Execute(GeneratorExecutionContext context)
|
||||
{
|
||||
context.AddSource("GenerateVersionedAttribute.g.cs", SourceText.From(AttributeText, Encoding.UTF8));
|
||||
|
||||
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var candidate in receiver.Candidates)
|
||||
{
|
||||
var model = context.Compilation.GetSemanticModel(candidate.SyntaxTree);
|
||||
var symbol = model.GetDeclaredSymbol(candidate) as INamedTypeSymbol;
|
||||
if (symbol is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == "VersionedCollectionsGenerator.GenerateVersionedAttribute"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string originalName = symbol.Name;
|
||||
string newName = $"{originalName}Versioned";
|
||||
|
||||
var partials = GetAllPartials(context.Compilation, originalName);
|
||||
int index = 0;
|
||||
foreach (var partial in partials)
|
||||
{
|
||||
var tree = partial.SyntaxTree;
|
||||
var root = tree.GetRoot();
|
||||
var rewriter = new RenameRewriter(originalName, newName);
|
||||
var newRoot = rewriter.Visit(root);
|
||||
var text = newRoot.ToFullString()
|
||||
.Replace("#if WITH_VERSIONING", string.Empty)
|
||||
.Replace("#endif", string.Empty);
|
||||
context.AddSource($"{newName}_{index++}.g.cs", SourceText.From(text, Encoding.UTF8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<ClassDeclarationSyntax> GetAllPartials(Compilation compilation, string className)
|
||||
{
|
||||
foreach (var tree in compilation.SyntaxTrees)
|
||||
{
|
||||
var root = tree.GetRoot();
|
||||
foreach (var cls in root.DescendantNodes().OfType<ClassDeclarationSyntax>())
|
||||
{
|
||||
if (cls.Identifier.ValueText == className)
|
||||
{
|
||||
yield return cls;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RenameRewriter : CSharpSyntaxRewriter
|
||||
{
|
||||
private readonly string _oldName;
|
||||
private readonly string _newName;
|
||||
|
||||
public RenameRewriter(string oldName, string newName)
|
||||
{
|
||||
_oldName = oldName;
|
||||
_newName = newName;
|
||||
}
|
||||
|
||||
public override SyntaxToken VisitToken(SyntaxToken token)
|
||||
{
|
||||
if (token.ValueText == _oldName)
|
||||
{
|
||||
return SyntaxFactory.Identifier(token.LeadingTrivia, _newName, token.TrailingTrivia);
|
||||
}
|
||||
|
||||
return base.VisitToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SyntaxReceiver : ISyntaxReceiver
|
||||
{
|
||||
public List<ClassDeclarationSyntax> Candidates { get; } = new();
|
||||
|
||||
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
|
||||
{
|
||||
if (syntaxNode is ClassDeclarationSyntax classDecl && classDecl.AttributeLists.Count > 0)
|
||||
{
|
||||
Candidates.Add(classDecl);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in new issue
Block a user