Author SHA1 Message Date
Marek Lemanczyk b5a574355e Migrated to git.privacyguard.company 2026-09-08 22:49:08 +02:00
Marek Lemańczyk 4b17a4c827 Merge pull request #25 from mlemanczyk/codex/add-addrange-method-to-recyclablepriorityqueue
AddRange improvements for priority queue
2025-09-17 16:20:08 +02:00
Marek Lemańczyk 37f147a262 Fix priority queue tests compilation 2025-09-16 14:20:18 +02:00
Marek Lemańczyk b0e7f96496 Add memory-based AddRange overloads 2025-09-16 14:20:11 +02:00
Marek Lemańczyk f6640deaf1 Add IReadOnlyCollection AddRange overload 2025-09-16 13:52:42 +02:00
Marek Lemańczyk 050d7fd386 Add non-generic enumerable AddRange to priority queue 2025-09-16 13:52:36 +02:00
Marek Lemańczyk 33187c1053 Add dictionary overloads for priority queue 2025-09-16 13:52:29 +02:00
Marek Lemańczyk ff38aa302a Add more PriorityQueue AddRange overloads 2025-09-16 13:52:21 +02:00
Marek Lemańczyk 6d80b36d70 Implement standalone AddRange overloads 2025-09-16 13:52:15 +02:00
Marek Lemańczyk d8440fe8c9 Add AddRange overloads for priority queue 2025-09-16 13:52:10 +02:00
Marek Lemańczyk 67630e3797 Optimize AddRange copy 2025-07-29 17:07:17 +02:00
Marek Lemańczyk 8cc2e49ae4 Merge pull request #24 from mlemanczyk/codex/implementuj-metody-addrange-dla-recyclablelinkedlist
Expand LinkedList AddRange overloads
2025-07-28 22:19:32 +02:00
Marek Lemańczyk 4ea3df4cca Add SortedList and SortedDictionary support to AddRange 2025-07-28 22:05:14 +02:00
Marek Lemańczyk 9d1afe3e32 Implement specialized AddRange overloads 2025-07-28 21:43:50 +02:00
Marek Lemańczyk 1ee464d6b8 Merge pull request #23 from mlemanczyk/codex/add-addrange-method-to-recyclabledictionary
AddRange span overload for HashSet
2025-07-17 15:51:31 +02:00
Marek Lemańczyk 26f2920f33 Enhance HashSet AddRange with typed source detection 2025-07-17 15:37:04 +02:00
Marek Lemańczyk aa48407bba Add remaining AddRange overloads for HashSet 2025-07-17 15:36:59 +02:00
Marek Lemańczyk 3dea509958 Add additional AddRange overloads for hash set 2025-07-17 15:36:54 +02:00
Marek Lemańczyk 1dc6c479ae Add more AddRange overloads for RecyclableHashSet 2025-07-17 15:36:49 +02:00
Marek Lemańczyk 12b01c32ba Add more AddRange overloads for RecyclableHashSet 2025-07-17 15:36:44 +02:00
Marek Lemańczyk ea5c4eee28 Implement Span and array AddRange for HashSet 2025-07-17 15:36:39 +02:00
Marek Lemańczyk c24d8d54ea Add span AddRange for hash set 2025-07-17 08:34:21 +02:00
Marek Lemańczyk a178956761 Merge pull request #22 from mlemanczyk/codex/zaktualizuj-strukture-recyclablequeue-o-metody-addrange
Optimize list AddRange
2025-07-17 01:35:20 +02:00
Marek Lemańczyk 78e9373491 Optimize stack AddRange for sorted dictionary 2025-07-17 00:21:51 +02:00
Marek Lemańczyk 56a5c3375a Optimize sorted-list and read-only list AddRange 2025-07-17 00:21:45 +02:00
Marek Lemańczyk aacfed4e7e Optimize queue and dictionary AddRange overloads for stack 2025-07-17 00:21:40 +02:00
Marek Lemańczyk 414e482aaf Refine stack AddRange preallocation 2025-07-17 00:21:34 +02:00
Marek Lemańczyk 2546d6bf3f Optimize hash set and stack AddRange for RecyclableStack 2025-07-17 00:21:30 +02:00
Marek Lemańczyk 2de3736f94 Optimize span-based stack AddRange 2025-07-17 00:21:25 +02:00
Marek Lemańczyk 136c6408f3 Fix stack AddRange capacity growth 2025-07-17 00:21:20 +02:00
Marek Lemańczyk 1aedfb2cbc Optimize long-list AddRange for stack 2025-07-17 00:21:15 +02:00
Marek Lemańczyk 4274f4dcbc Fix stack enumerator for empty chunks 2025-07-16 00:20:25 +02:00
Marek Lemańczyk 7e71abdcf2 Merge pull request #21 from mlemanczyk/codex/update-recyclablestack-with-addrange-methods
AddRange performance tweaks for RecyclableStack
2025-07-15 00:46:49 +02:00
Marek Lemańczyk ffcbfb0493 Improve AddRange for ICollection 2025-07-15 00:40:06 +02:00
Marek Lemańczyk 037271acc8 Merge pull request #20 from mlemanczyk/d2kqjh-codex/dodaj-oznaczenia-wersjonowania-w-recyclablelonglist
Implement versioned enumerator retrieval
2025-07-14 23:13:48 +02:00
Marek Lemańczyk 25bf466f77 Optimize indexing helpers 2025-07-14 23:02:13 +02:00
Marek Lemańczyk c2affa4ad7 Implement versioned enumerator retrieval 2025-07-14 22:43:43 +02:00
Marek Lemańczyk a18caf42d6 Merge pull request #19 from mlemanczyk/codex/dodaj-oznaczenia-wersjonowania-w-recyclablelonglist
Add versioning to RecyclableLongList
2025-07-14 22:40:13 +02:00
Marek Lemańczyk 1e64c71af3 Add versioning support for RecyclableLongList 2025-07-14 22:01:03 +02:00
Marek Lemańczyk ed23fd7f69 Merge pull request #18 from mlemanczyk/codex/stworz-generator-kodu-zrodowego-w-c#
Mark version increments for source generation
2025-07-14 21:34:13 +02:00
Marek Lemańczyk 29a5db476e Mark version increments with directives and update generator 2025-07-14 21:28:52 +02:00
Marek Lemańczyk 1abc07e9a2 Merge pull request #17 from mlemanczyk/codex/add-optimized-addrange-methods
Add optimized AddRange overloads
2025-07-14 19:40:34 +02:00
Marek Lemańczyk dc0b3b92e5 Remove AddRange interface and optimize casting 2025-07-14 19:27:41 +02:00
Marek Lemańczyk b571a2df59 Use interface for AddRange optimizations 2025-07-14 19:27:33 +02:00
Marek Lemańczyk 1d4d5e9217 Avoid dynamic in AddRange detection 2025-07-14 19:27:25 +02:00
Marek Lemańczyk 976c5eb78b Add missing AddRange overloads for dictionary and sorted list 2025-07-14 18:17:37 +02:00
Marek Lemańczyk 40074ce368 Merge pull request #16 from mlemanczyk/codex/dodaj-przeciazenia-metod-addrange
Add optimized AddRange overloads
2025-07-14 17:46:21 +02:00
Marek Lemańczyk f4e2285734 Add optimized AddRange overloads for recyclable collections 2025-07-14 17:30:16 +02:00
Marek Lemańczyk 35f1124d6f Merge branch 'main' of https://github.com/mlemanczyk/Recyclable.Collections 2025-07-14 17:09:03 +02:00
Marek Lemańczyk 801ab88c01 Specification for agents updated. 2025-07-14 17:07:37 +02:00
Marek Lemańczyk e1298c817d Merge pull request #15 from mlemanczyk/codex/implement-recyclablesorteddictionary-class
Add RecyclableSortedDictionary
2025-07-14 16:31:41 +02:00
Marek Lemańczyk 66443480b7 Merge pull request #14 from mlemanczyk/codex/add-recyclablesortedlist-implementation-and-tests
Add recyclable sorted list
2025-07-14 16:31:14 +02:00
Marek Lemańczyk 941cf4f3fd Add RecyclableSortedDictionary with tests 2025-07-14 16:25:29 +02:00
Marek Lemańczyk 0d28f6515d Implement recyclable sorted list using pooled arrays 2025-07-14 16:24:24 +02:00
Marek Lemańczyk 0496b63e5a Specification for agents updated 2025-07-14 16:23:11 +02:00
Marek Lemańczyk 9aa96ccb19 Merge pull request #13 from mlemanczyk/codex/analyze-tests-for-new-generic-classes
Add dataset coverage and fix stack pop test
2025-07-14 13:55:13 +02:00
Marek Lemańczyk 53c34d0fa7 Fix stack test for empty pop 2025-07-14 13:49:26 +02:00
Marek Lemańczyk 80b4b0e890 Merge pull request #12 from mlemanczyk/codex/implement-recyclablesortedsett
Implement RecyclableSortedSet
2025-07-14 11:43:26 +02:00
Marek Lemańczyk 9133867d39 Add sort tests for RecyclableLongList 2025-07-14 11:34:12 +02:00
Marek Lemańczyk 80499d0936 Add RecyclableSortedSet and long list sort extensions 2025-07-14 11:22:58 +02:00
Marek Lemańczyk 06987cfb0f Merge pull request #11 from mlemanczyk/codex/implementuj-recyclablepriorityqueuet-z-testami
Implement RecyclablePriorityQueue
2025-07-14 10:39:35 +02:00
Marek Lemańczyk 8ff09f12a5 Add RecyclablePriorityQueue with tests 2025-07-14 10:31:51 +02:00
Marek Lemańczyk 7128a73e2e Merge pull request #10 from mlemanczyk/codex/zaimplementuj-recyclablehashsett
Fix initial bucket allocation for HashSet
2025-07-14 10:04:05 +02:00
Marek Lemańczyk a97a81b50c Remove unreachable bucket pooling check 2025-07-14 10:02:05 +02:00
Marek Lemańczyk f203f19312 Merge pull request #9 from mlemanczyk/codex/dodaj-klase-recyclabledictionary
Implement RecyclableDictionary with array-only storage
2025-07-14 06:26:32 +02:00
Marek Lemańczyk f9af1086e5 Use pooled arrays in RecyclableDictionary 2025-07-14 06:24:40 +02:00
Marek Lemańczyk 0f2d05f60d Simplify block allocation logic 2025-07-14 06:05:08 +02:00
Marek Lemańczyk 98c24430ba Merge pull request #8 from mlemanczyk/codex/zaimplementuj-recyclablequeue-z-linkedlistt
Fix missing blank line in RecyclableLinkedList
2025-07-14 02:31:22 +02:00
Marek Lemańczyk 6934dbd02e Fix blank line after if block in RecyclableLinkedList 2025-07-14 02:29:00 +02:00
Marek Lemańczyk 6f9ffd7916 Merge pull request #7 from mlemanczyk/codex/implementuj-klase-recyclablequeue
Implement queue using pooled chunks
2025-07-14 00:30:50 +02:00
Marek Lemańczyk 713a8ffdba Rename queue chunk to BiDirectional prefix 2025-07-14 00:28:03 +02:00
Marek Lemańczyk 3a712dbe57 Implement chunk-based queue 2025-07-13 23:59:39 +02:00
Marek Lemańczyk 93261808aa Merge pull request #6 from mlemanczyk/codex/optymalizuj-klase-recyclablestack-dla-wydajnosci
Document test workflow update
2025-07-13 22:30:28 +02:00
Marek Lemańczyk 770ae2d2f6 Document test command usage and update workflow 2025-07-13 22:28:21 +02:00
Marek Lemańczyk e9887957f9 Merge pull request #5 from mlemanczyk/codex/zmodyfikuj-klase-chunk-i-zaktualizuj-agents.md
Polish chunk API docs and style
2025-07-13 20:50:15 +02:00
Marek Lemańczyk fc0afdcca8 Finalize chunk APIs 2025-07-13 20:49:29 +02:00
Marek Lemańczyk aa52bddd66 Merge pull request #4 from mlemanczyk/codex/implementuj-klase-recyclablestack
Refactor stack enumeration
2025-07-12 03:22:12 +02:00
Marek Lemańczyk ac163014d8 Add chunk pooling and optimize stack growth 2025-07-12 03:17:00 +02:00
Marek Lemańczyk 1b4b9f534b Align stack enumerator with .NET Stack 2025-07-12 02:46:17 +02:00
Marek Lemańczyk 210f0df68c Merge pull request #3 from mlemanczyk/codex/add-nuget-package-links-to-agents.md-and-readme.md
Add NuGet package links
2025-07-10 13:24:27 +02:00
Marek Lemańczyk 64482ca13c Add NuGet package links 2025-07-10 13:23:53 +02:00
Marek Lemańczyk 21634eca68 Merge pull request #2 from mlemanczyk/codex/zmodyfikuj-repozytoria-na-.net-8.0
Remove .NET 9 target frameworks
2025-07-10 13:05:28 +02:00
Marek Lemańczyk a9ee409a18 Remove .NET 9 targets 2025-07-10 13:04:33 +02:00
Marek Lemańczyk 6944fe25b3 Merge pull request #1 from mlemanczyk/codex/sprawdz-metody-listt-w-kolekcjach
Add EnsureCapacity to RecyclableList
2025-07-10 12:17:35 +02:00
Marek Lemańczyk 29b809cead Add .NET 8 and 9 target frameworks 2025-07-10 12:16:48 +02:00
Marek Lemańczyk 877b363381 Add EnsureCapacity method 2025-07-10 12:03:34 +02:00
Marek Lemańczyk c3205debed Create dotnet.yml pipeline 2024-01-18 00:00:15 +01:00
54 changed files with 14791 additions and 753 deletions

No files matched your search

+28
View File
@@ -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
+1 -1
View File
@@ -10,7 +10,7 @@
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. // 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": [], "args": [],
"cwd": "${workspaceFolder}/Recyclable.Collections.Benchmarks", "cwd": "${workspaceFolder}/Recyclable.Collections.Benchmarks",
// For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+1
View File
@@ -0,0 +1 @@
{}
+29
View File
@@ -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)
+8
View File
@@ -5,6 +5,14 @@
* `RecyclableList<T>` * `RecyclableList<T>`
* `RecyclableLongList<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 # Milestones
1. ✅ Create basic classes 1. ✅ Create basic classes
1. ✅ `RecyclableList<T>` 1. ✅ `RecyclableList<T>`
@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<Platforms>AnyCPU;x64</Platforms> <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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<DefineConstants>$(DefineConstants);WITH_VERSIONING</DefineConstants>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>Recyclable.Collections</PackageId> <PackageId>Recyclable.Collections</PackageId>
<PackageVersion>0.0.6</PackageVersion> <PackageVersion>0.0.6</PackageVersion>
@@ -30,8 +31,12 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.5" /> <PackageReference Include="Microsoft.Extensions.ObjectPool" Version="7.0.5" />
<PackageReference Include="morelinq" Version="3.4.2" /> <PackageReference Include="morelinq" Version="3.4.2" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VersionedCollectionsGenerator\VersionedCollectionsGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>
</Project> </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
+362
View File
@@ -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;
}
}
}
}
+416 -27
View File
@@ -1,13 +1,82 @@
using System.Collections; using System.Collections;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Linq.Expressions;
using System.Reflection;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
public static class zRecyclableListAddRange public static class zRecyclableListAddRange
{ {
private static void AddRangeEnumerated<T>(this RecyclableList<T> list, IEnumerable<T> items, int growByCount)
{ 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(); var enumerator = items.GetEnumerator();
if (!enumerator.MoveNext()) if (!enumerator.MoveNext())
{ {
@@ -57,8 +126,9 @@ namespace Recyclable.Collections
list._count = currentItemsCount; list._count = currentItemsCount;
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void AddRange<T>(this RecyclableList<T> list, in Array items) [VersionedCollectionsGenerator.RewriteForVersioned]
public static void AddRange<T>(this RecyclableList<T> list, in Array items)
{ {
if (list._capacity < list._count + items.Length) 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); Array.Copy(items, items.GetLowerBound(0), list._memoryBlock, list._count, items.Length);
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [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)); new ReadOnlySpan<T>(items).CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length));
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -93,7 +167,9 @@ namespace Recyclable.Collections
items.CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length)); items.CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length));
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -106,7 +182,9 @@ namespace Recyclable.Collections
items.CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length)); items.CopyTo(new Span<T>(list._memoryBlock, list._count, items.Length));
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -119,7 +197,9 @@ namespace Recyclable.Collections
items.CopyTo(list._memoryBlock, list._count); items.CopyTo(list._memoryBlock, list._count);
list._count += items.Count; list._count += items.Count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -132,7 +212,9 @@ namespace Recyclable.Collections
items.CopyTo(list._memoryBlock, list._count); items.CopyTo(list._memoryBlock, list._count);
list._count += items.Count; list._count += items.Count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -145,7 +227,9 @@ namespace Recyclable.Collections
items.CopyTo(list._memoryBlock, list._count); items.CopyTo(list._memoryBlock, list._count);
list._count += items.Count; list._count += items.Count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [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)); new ReadOnlySpan<T>(items._memoryBlock, 0, items._count).CopyTo(new Span<T>(list._memoryBlock, list._count, items._count));
list._count += items._count; list._count += items._count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
public static void AddRange<T>(this RecyclableList<T> list, RecyclableLongList<T> items) public static void AddRange<T>(this RecyclableList<T> list, RecyclableLongList<T> items)
{ {
if (items._longCount == 0) if (items._longCount == 0)
{ {
return; return;
} }
int oldCount = list._count; int oldCount = list._count;
long sourceItemsCount = items._longCount; long sourceItemsCount = items._longCount;
@@ -208,9 +294,281 @@ namespace Recyclable.Collections
itemsSpan.CopyTo(targetSpan); itemsSpan.CopyTo(targetSpan);
} }
list._count = targetCapacity; list._count = targetCapacity;
list._version++; #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) 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); AddRange(list, sourceList);
} }
else if (items is ICollection<T> sourceICollection) else if (items is ICollection<T> sourceICollection)
{ {
AddRange(list, sourceICollection); AddRange(list, sourceICollection);
} }
else if (items is ICollection sourceICollectionWithObjects) else if (items is ICollection sourceICollectionWithObjects)
{ {
AddRange(list, sourceICollectionWithObjects); AddRange(list, sourceICollectionWithObjects);
} }
else if (items is IReadOnlyList<T> sourceIReadOnlyList) else if (items is RecyclableHashSet<T> sourceHashSet)
{ {
AddRange(list, sourceIReadOnlyList); 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) else if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity) && requiredAdditionalCapacity != 0)
{ {
AddRangeWithKnownCount(list, items, list._count, requiredAdditionalCapacity); AddRangeWithKnownCount(list, items, list._count, requiredAdditionalCapacity);
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
else else
{ {
AddRangeEnumerated(list, items, growByCount); AddRangeEnumerated(list, items, growByCount);
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
} }
} }
@@ -79,7 +79,9 @@ namespace Recyclable.Collections
} }
list._count += requiredAdditionalCapacity; list._count += requiredAdditionalCapacity;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [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(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
Array.Copy(items, items.GetLowerBound(0), memoryBlock, index, items.Length); Array.Copy(items, items.GetLowerBound(0), memoryBlock, index, items.Length);
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -109,7 +113,9 @@ namespace Recyclable.Collections
Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index); Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
new ReadOnlySpan<T>(items).CopyTo(new Span<T>(memoryBlock, index, items.Length)); new ReadOnlySpan<T>(items).CopyTo(new Span<T>(memoryBlock, index, items.Length));
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -124,7 +130,9 @@ namespace Recyclable.Collections
Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index); Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
items.CopyTo(new Span<T>(memoryBlock, index, items.Length)); items.CopyTo(new Span<T>(memoryBlock, index, items.Length));
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -139,7 +147,9 @@ namespace Recyclable.Collections
Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index); Array.Copy(memoryBlock, index, memoryBlock, index + items.Length, list._count - index);
items.CopyTo(new Span<T>(memoryBlock, index, items.Length)); items.CopyTo(new Span<T>(memoryBlock, index, items.Length));
list._count += items.Length; list._count += items.Length;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -154,7 +164,9 @@ namespace Recyclable.Collections
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index);
items.CopyTo(memoryBlock, index); items.CopyTo(memoryBlock, index);
list._count += items.Count; list._count += items.Count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -169,7 +181,9 @@ namespace Recyclable.Collections
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index);
items.CopyTo(list._memoryBlock, index); items.CopyTo(list._memoryBlock, index);
list._count += items.Count; list._count += items.Count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -184,7 +198,9 @@ namespace Recyclable.Collections
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index);
items.CopyTo(memoryBlock, index); items.CopyTo(memoryBlock, index);
list._count += items.Count; list._count += items.Count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -199,7 +215,9 @@ namespace Recyclable.Collections
Array.Copy(memoryBlock, index, memoryBlock, index + items.Count, list._count - index); 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)); new ReadOnlySpan<T>(items._memoryBlock, 0, items._count).CopyTo(new(list._memoryBlock, index, items._count));
list._count += items._count; list._count += items._count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
[MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.AggressiveOptimization)]
@@ -251,7 +269,9 @@ namespace Recyclable.Collections
} }
list._count = targetCapacity; list._count = targetCapacity;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static IEnumerator InsertRange<T>(this RecyclableList<T> list, int index, IEnumerable source, int growByCount = RecyclableDefaults.MinPooledArrayLength) 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; list._count -= foundItemsCount;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
return foundItemsCount; return foundItemsCount;
} }
@@ -125,43 +127,57 @@ namespace Recyclable.Collections
} }
list._count -= count; list._count -= count;
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static void Reverse<T>(this RecyclableList<T> list) public static void Reverse<T>(this RecyclableList<T> list)
{ {
Array.Reverse(list._memoryBlock, 0, list._count); Array.Reverse(list._memoryBlock, 0, list._count);
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static void Reverse<T>(this RecyclableList<T> list, int index, int count) public static void Reverse<T>(this RecyclableList<T> list, int index, int count)
{ {
Array.Reverse(list._memoryBlock, index, count); Array.Reverse(list._memoryBlock, index, count);
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static void Sort<T>(this RecyclableList<T> list) public static void Sort<T>(this RecyclableList<T> list)
{ {
Array.Sort(list._memoryBlock, 0, list._count); Array.Sort(list._memoryBlock, 0, list._count);
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static void Sort<T>(this RecyclableList<T> list, Comparison<T> comparison) public static void Sort<T>(this RecyclableList<T> list, Comparison<T> comparison)
{ {
Array.Sort(list._memoryBlock, 0, list._count, new ComparisonToComparerAdapter<T>(comparison)); Array.Sort(list._memoryBlock, 0, list._count, new ComparisonToComparerAdapter<T>(comparison));
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static void Sort<T>(this RecyclableList<T> list, IComparer<T>? comparer) public static void Sort<T>(this RecyclableList<T> list, IComparer<T>? comparer)
{ {
Array.Sort(list._memoryBlock, 0, list._count, comparer); Array.Sort(list._memoryBlock, 0, list._count, comparer);
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static void Sort<T>(this RecyclableList<T> list, int index, int count, IComparer<T>? comparer) public static void Sort<T>(this RecyclableList<T> list, int index, int count, IComparer<T>? comparer)
{ {
Array.Sort(list._memoryBlock, index, count, comparer); Array.Sort(list._memoryBlock, index, count, comparer);
#if WITH_VERSIONING
list._version++; list._version++;
#endif
} }
public static T[] ToArray<T>(this RecyclableList<T> list) public static T[] ToArray<T>(this RecyclableList<T> list)
@@ -6,9 +6,10 @@ namespace Recyclable.Collections
{ {
public partial class RecyclableList<T> public partial class RecyclableList<T>
{ {
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct Enumerator : IEnumerator<T> [VersionedCollectionsGenerator.CloneForVersioned]
{ public struct Enumerator : IEnumerator<T>
{
private static readonly bool _isReferenceType = !typeof(T).IsValueType; private static readonly bool _isReferenceType = !typeof(T).IsValueType;
private int _currentItemIndex; private int _currentItemIndex;
@@ -3,8 +3,9 @@ using System.Runtime.CompilerServices;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
public static class RecyclableListExtensions [VersionedCollectionsGenerator.GenerateVersioned]
{ public static class RecyclableListExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableList<T> ToRecyclableList<T>(this Array values) => new(values); 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> 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)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct VersionedEnumerator : IEnumerator<T> [VersionedCollectionsGenerator.CloneForVersioned]
{ public struct VersionedEnumerator : IEnumerator<T>
{
private int _currentItemIndex; private int _currentItemIndex;
private readonly ulong _enumeratorVersion; private readonly ulong _enumeratorVersion;
private readonly RecyclableList<T> _list; private readonly RecyclableList<T> _list;
+69 -18
View File
@@ -5,8 +5,9 @@ using Recyclable.Collections.Pools;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
[Serializable] [Serializable]
public sealed partial class RecyclableList<T> : IList<T>, IReadOnlyList<T>, IDisposable [VersionedCollectionsGenerator.GenerateVersioned]
public sealed partial class RecyclableList<T> : IList<T>, IReadOnlyList<T>, IDisposable
{ {
internal static readonly bool _needsClearing = !typeof(T).IsValueType; internal static readonly bool _needsClearing = !typeof(T).IsValueType;
@@ -17,7 +18,8 @@ namespace Recyclable.Collections
#nullable restore #nullable restore
internal ulong _version; internal ulong _version;
public ulong Version => _version; [VersionedCollectionsGenerator.CloneForVersioned]
public ulong Version => _version;
public RecyclableList() public RecyclableList()
{ {
@@ -172,7 +174,9 @@ namespace Recyclable.Collections
set set
{ {
_memoryBlock[index] = value; _memoryBlock[index] = value;
#if WITH_VERSIONING
_version++; _version++;
#endif
} }
} }
@@ -185,17 +189,19 @@ namespace Recyclable.Collections
if (_capacity != value) if (_capacity != value)
{ {
_capacity = RecyclableListHelpers<T>.EnsureCapacity(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)value))); _capacity = RecyclableListHelpers<T>.EnsureCapacity(this, _count, checked((int)BitOperations.RoundUpToPowerOf2((uint)value)));
#if WITH_VERSIONING
_version++; _version++;
#endif
} }
} }
} }
internal int _count; internal int _count;
public int Count public int Count
{ {
get => _count; get => _count;
set set
{ {
if (_count != value) if (_count != value)
{ {
if (value > _capacity) if (value > _capacity)
@@ -204,12 +210,30 @@ namespace Recyclable.Collections
} }
_count = value; _count = value;
#if WITH_VERSIONING
_version++; _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)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(T item) public void Add(T item)
@@ -227,7 +251,9 @@ namespace Recyclable.Collections
} }
_memoryBlock[_count++] = item; _memoryBlock[_count++] = item;
#if WITH_VERSIONING
_version++; _version++;
#endif
} }
public T[] AsArray => _memoryBlock; public T[] AsArray => _memoryBlock;
@@ -246,7 +272,9 @@ namespace Recyclable.Collections
} }
_count = 0; _count = 0;
#if WITH_VERSIONING
_version++; _version++;
#endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -279,7 +307,9 @@ namespace Recyclable.Collections
_memoryBlock[index] = item; _memoryBlock[index] = item;
_count++; _count++;
#if WITH_VERSIONING
_version++; _version++;
#endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@@ -299,7 +329,9 @@ namespace Recyclable.Collections
_memoryBlock[_count] = default; _memoryBlock[_count] = default;
} }
#if WITH_VERSIONING
_version++; _version++;
#endif
return true; return true;
} }
@@ -326,21 +358,40 @@ namespace Recyclable.Collections
_memoryBlock[_count] = default; _memoryBlock[_count] = default;
} }
#if WITH_VERSIONING
_version++; _version++;
#endif
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public Enumerator GetEnumerator() => new(this); [VersionedCollectionsGenerator.CloneForVersioned]
#if WITH_VERSIONING
public VersionedEnumerator GetEnumerator() => new(this);
#else
public Enumerator GetEnumerator() => new(this);
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); [VersionedCollectionsGenerator.CloneForVersioned]
#if WITH_VERSIONING
IEnumerator IEnumerable.GetEnumerator() => new VersionedEnumerator(this);
#else
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this);
#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this); [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() public void Dispose()
{ {
#if WITH_VERSIONING
_version++; _version++;
#endif
if (_count != 0) if (_count != 0)
{ {
if (_needsClearing) if (_needsClearing)
@@ -1,13 +1,83 @@
using System.Collections; using System.Collections;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using System.Linq.Expressions;
using System.Reflection;
using Recyclable.Collections.Pools; using Recyclable.Collections.Pools;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
public static class zRecyclableLongListAddRange public static class zRecyclableLongListAddRange
{ {
private static void AddRangeEnumerated<T>(this RecyclableLongList<T> targetList, IEnumerable<T> items)
{ 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(); using IEnumerator<T> enumerator = items.GetEnumerator();
if (!enumerator.MoveNext()) if (!enumerator.MoveNext())
{ {
@@ -56,7 +126,9 @@ namespace Recyclable.Collections
} }
targetList._capacity = capacity; targetList._capacity = capacity;
targetList._version++; #if WITH_VERSIONING
targetList._version++;
#endif
return; return;
} }
@@ -116,10 +188,13 @@ namespace Recyclable.Collections
targetList._nextItemBlockIndex = targetBlockIdx; targetList._nextItemBlockIndex = targetBlockIdx;
targetList._nextItemIndex = targetItemIdx; targetList._nextItemIndex = targetItemIdx;
targetList._lastBlockWithData = targetBlockIdx - (targetItemIdx > 0 ? 0 : 1); 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) if (items.LongLength == 0)
{ {
@@ -170,7 +245,9 @@ namespace Recyclable.Collections
} }
targetList._longCount = targetCapacity; targetList._longCount = targetCapacity;
targetList._version++; #if WITH_VERSIONING
targetList._version++;
#endif
} }
public static void AddRange<T>(this RecyclableLongList<T> targetList, ICollection items) 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); 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) 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); 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) public static void AddRange<T>(this RecyclableLongList<T> targetList, IEnumerable source)
@@ -405,27 +486,56 @@ namespace Recyclable.Collections
targetList.AddRange(sourceICollection); targetList.AddRange(sourceICollection);
return; return;
case ICollection sourceICollectionWithObjects: case ICollection sourceICollectionWithObjects:
targetList.AddRange(sourceICollectionWithObjects); targetList.AddRange(sourceICollectionWithObjects);
return; return;
case IReadOnlyList<T> sourceIReadOnlyList: case IReadOnlyList<T> sourceIReadOnlyList:
targetList.AddRange(sourceIReadOnlyList); targetList.AddRange(sourceIReadOnlyList);
return; return;
default: case RecyclableHashSet<T> sourceHashSet:
if (items.TryGetNonEnumeratedCount(out var requiredAdditionalCapacity)) targetList.AddRange(sourceHashSet);
{ return;
targetList.AddRangeWithKnownCount(items, requiredAdditionalCapacity);
}
else
{
targetList.AddRangeEnumerated(items);
}
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) public static void AddRange<T>(this RecyclableLongList<T> targetList, IReadOnlyList<T> items)
{ {
@@ -517,7 +627,9 @@ namespace Recyclable.Collections
targetList._nextItemBlockIndex = targetBlockIndex; targetList._nextItemBlockIndex = targetBlockIndex;
targetList._longCount = targetCapacity; targetList._longCount = targetCapacity;
targetList._lastBlockWithData = targetBlockIndex - (targetList._nextItemIndex > 0 ? 0 : 1); 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) public static void AddRange<T>(this RecyclableLongList<T> targetList, ReadOnlySpan<T> items)
@@ -572,7 +684,9 @@ namespace Recyclable.Collections
targetList._longCount = targetCapacity; targetList._longCount = targetCapacity;
targetList._lastBlockWithData = targetBlockIndex; targetList._lastBlockWithData = targetBlockIndex;
targetList._version++; #if WITH_VERSIONING
targetList._version++;
#endif
} }
public static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableList<T> items) public static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableList<T> items)
@@ -629,7 +743,9 @@ namespace Recyclable.Collections
} }
targetList._longCount = targetCapacity; targetList._longCount = targetCapacity;
targetList._version++; #if WITH_VERSIONING
targetList._version++;
#endif
} }
public static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableLongList<T> items) public static void AddRange<T>(this RecyclableLongList<T> targetList, RecyclableLongList<T> items)
@@ -702,7 +818,9 @@ namespace Recyclable.Collections
} }
targetList._longCount = targetCapacity; targetList._longCount = targetCapacity;
targetList._version++; #if WITH_VERSIONING
targetList._version++;
#endif
} }
public static void AddRange<T>(this RecyclableLongList<T> targetList, Span<T> items) public static void AddRange<T>(this RecyclableLongList<T> targetList, Span<T> items)
@@ -757,15 +875,17 @@ namespace Recyclable.Collections
targetList._longCount = targetCapacity; targetList._longCount = targetCapacity;
targetList._lastBlockWithData = targetBlockIndex; targetList._lastBlockWithData = targetBlockIndex;
targetList._version++; #if WITH_VERSIONING
targetList._version++;
#endif
} }
public static void AddRange<T>(this RecyclableLongList<T> targetList, in T[] items) public static void AddRange<T>(this RecyclableLongList<T> targetList, in T[] items)
{ {
if (items.LongLength == 0) if (items.LongLength == 0)
{ {
return; return;
} }
int targetBlockIndex = targetList._nextItemBlockIndex; int targetBlockIndex = targetList._nextItemBlockIndex;
@@ -812,8 +932,362 @@ namespace Recyclable.Collections
} }
targetList._longCount = targetCapacity; targetList._longCount = targetCapacity;
targetList._lastBlockWithData = targetBlockIndex; targetList._lastBlockWithData = targetBlockIndex;
targetList._version++; #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 partial class RecyclableLongList<T>
{ {
public struct Enumerator : IEnumerator<T> [VersionedCollectionsGenerator.CloneForVersioned]
{ public struct Enumerator : IEnumerator<T>
{
#nullable disable #nullable disable
private static readonly T _default = default; private static readonly T _default = default;
#nullable restore #nullable restore
@@ -3,8 +3,9 @@ using System.Runtime.CompilerServices;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
public static class RecyclableLongListExtensions [VersionedCollectionsGenerator.GenerateVersioned]
{ public static class RecyclableLongListExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static RecyclableLongList<T> ToRecyclableLongList<T>(this Array values, int minBlockSize = RecyclableDefaults.BlockSize) => new(values, minBlockSize); 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 Recyclable.Collections.Pools;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace Recyclable.Collections namespace Recyclable.Collections
@@ -76,11 +77,11 @@ namespace Recyclable.Collections
return; return;
} }
ReadOnlySpan<T> sourceBlockMemory; ReadOnlySpan<T> sourceBlockMemory;
Span<T> destinationArrayMemory; Span<T> destinationArrayMemory;
// TODO: Replace with bit-shifting int blockSizePow2Shift = BitOperations.TrailingZeroCount((uint)blockSize);
int startingBlockIndex = (int)(startingIndex / blockSize); int startingBlockIndex = (int)(startingIndex >> blockSizePow2Shift);
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize != 0 ? 1 : 0)) - 1; int lastBlockIndex = (int)((itemsCount >> blockSizePow2Shift) + ((itemsCount & (blockSize - 1)) != 0 ? 1 : 0)) - 1;
ReadOnlySpan<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); ReadOnlySpan<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1);
destinationArrayMemory = new Span<T>(destinationArray, destinationArrayIndex, (int)Math.Min(destinationArray.Length - destinationArrayIndex, itemsCount)); destinationArrayMemory = new Span<T>(destinationArray, destinationArrayIndex, (int)Math.Min(destinationArray.Length - destinationArrayIndex, itemsCount));
int memoryBlockIndex; int memoryBlockIndex;
@@ -105,9 +106,9 @@ namespace Recyclable.Collections
return; return;
} }
// TODO: Replace with bit-shifting int blockSizePow2Shift = BitOperations.TrailingZeroCount((uint)blockSize);
int startingBlockIndex = (int)(startingIndex / blockSize); int startingBlockIndex = (int)(startingIndex >> blockSizePow2Shift);
int lastBlockIndex = (int)((itemsCount / blockSize) + (itemsCount % blockSize != 0 ? 1 : 0)) - 1; int lastBlockIndex = (int)((itemsCount >> blockSizePow2Shift) + ((itemsCount & (blockSize - 1)) != 0 ? 1 : 0)) - 1;
var destinationIndex = destinationArrayIndex; var destinationIndex = destinationArrayIndex;
ReadOnlySpan<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1); ReadOnlySpan<T[]> sourceMemoryBlocksSpan = new(sourceMemoryBlocks, startingBlockIndex, lastBlockIndex + 1);
@@ -5,11 +5,14 @@ namespace Recyclable.Collections
{ {
public partial class RecyclableLongList<T> : IRecyclableVersionedLongList<T> public partial class RecyclableLongList<T> : IRecyclableVersionedLongList<T>
{ {
public VersionedEnumerator GetVersionedEnumerator() => new(this); [VersionedCollectionsGenerator.CloneForVersioned]
VersionedEnumerator IRecyclableVersionedLongList<T>.GetEnumerator() => new(this); 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 #nullable disable
private static readonly T _default = default; private static readonly T _default = default;
#nullable restore #nullable restore
+93 -50
View File
@@ -9,7 +9,8 @@ using Recyclable.Collections.Pools;
namespace Recyclable.Collections 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; private static readonly bool _defaultIsNull = default(T) == null;
internal static readonly bool _needsClearing = !typeof(T).IsValueType; internal static readonly bool _needsClearing = !typeof(T).IsValueType;
@@ -25,7 +26,7 @@ namespace Recyclable.Collections
#nullable restore #nullable restore
internal int _nextItemBlockIndex; internal int _nextItemBlockIndex;
internal int _nextItemIndex; internal int _nextItemIndex;
internal ulong _version; internal ulong _version;
public int BlockSize => _blockSize; public int BlockSize => _blockSize;
public int BlockSizeMinus1 => _blockSizeMinus1; public int BlockSizeMinus1 => _blockSizeMinus1;
@@ -35,15 +36,19 @@ namespace Recyclable.Collections
get => _capacity; get => _capacity;
private set private set
{ {
_capacity = value; _capacity = value;
_version++; #if WITH_VERSIONING
if (_capacity != value) _version++;
{ #endif
_capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)value))); if (_capacity != value)
_version++; {
} _capacity = Helpers.Resize(this, _blockSize, _blockSizePow2BitShift, checked((long)BitOperations.RoundUpToPowerOf2((ulong)value)));
} #if WITH_VERSIONING
} _version++;
#endif
}
}
}
public int Count => checked((int)_longCount); public int Count => checked((int)_longCount);
public bool IsReadOnly => false; public bool IsReadOnly => false;
@@ -51,7 +56,8 @@ namespace Recyclable.Collections
public long LongCount => _longCount; public long LongCount => _longCount;
public int NextItemBlockIndex => _nextItemBlockIndex; public int NextItemBlockIndex => _nextItemBlockIndex;
public int NextItemIndex => _nextItemIndex; public int NextItemIndex => _nextItemIndex;
public ulong Version => _version; [VersionedCollectionsGenerator.CloneForVersioned]
public ulong Version => _version;
public RecyclableLongList(int minBlockSize = RecyclableDefaults.BlockSize, long initialCapacity = RecyclableDefaults.InitialCapacity) public RecyclableLongList(int minBlockSize = RecyclableDefaults.BlockSize, long initialCapacity = RecyclableDefaults.InitialCapacity)
{ {
@@ -251,20 +257,24 @@ namespace Recyclable.Collections
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1];
set set
{ {
new Span<T>(_memoryBlocks[(int)(index >> _blockSizePow2BitShift)])[(int)(index & _blockSizeMinus1)] = value; new Span<T>(_memoryBlocks[(int)(index >> _blockSizePow2BitShift)])[(int)(index & _blockSizeMinus1)] = value;
_version++; #if WITH_VERSIONING
} _version++;
} #endif
}
}
public T this[int index] public T this[int index]
{ {
get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1]; get => _memoryBlocks[index >> _blockSizePow2BitShift][index & _blockSizeMinus1];
set set
{ {
new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value; new Span<T>(_memoryBlocks[index >> _blockSizePow2BitShift])[index & _blockSizeMinus1] = value;
_version++; #if WITH_VERSIONING
} _version++;
} #endif
}
}
public T[][] AsArray => _memoryBlocks; public T[][] AsArray => _memoryBlocks;
@@ -292,9 +302,11 @@ namespace Recyclable.Collections
_lastBlockWithData++; _lastBlockWithData++;
} }
_longCount++; _longCount++;
_version++; #if WITH_VERSIONING
} _version++;
#endif
}
public void Clear() public void Clear()
{ {
@@ -326,9 +338,11 @@ namespace Recyclable.Collections
_nextItemBlockIndex = 0; _nextItemBlockIndex = 0;
_nextItemIndex = 0; _nextItemIndex = 0;
_lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex; _lastBlockWithData = RecyclableDefaults.ItemNotFoundIndex;
_longCount = 0; _longCount = 0;
_version++; #if WITH_VERSIONING
} _version++;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Contains(T item) 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 void CopyTo(T[] array, int arrayIndex) => Helpers.CopyTo(_memoryBlocks, 0, _blockSize, _longCount, array, arrayIndex);
public Enumerator GetEnumerator() => new(this); [VersionedCollectionsGenerator.CloneForVersioned]
IEnumerator IEnumerable.GetEnumerator() => new Enumerator(this); #if WITH_VERSIONING
IEnumerator<T> IEnumerable<T>.GetEnumerator() => new Enumerator(this); 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)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int IndexOf(T item) public int IndexOf(T item)
@@ -433,8 +462,10 @@ namespace Recyclable.Collections
_lastBlockWithData++; _lastBlockWithData++;
} }
_version++; #if WITH_VERSIONING
} _version++;
#endif
}
public void Insert(long index, T item) public void Insert(long index, T item)
{ {
@@ -472,8 +503,10 @@ namespace Recyclable.Collections
_lastBlockWithData++; _lastBlockWithData++;
} }
_version++; #if WITH_VERSIONING
} _version++;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public long LongIndexOf(T item) public long LongIndexOf(T item)
@@ -543,9 +576,11 @@ namespace Recyclable.Collections
#nullable restore #nullable restore
} }
_version++; #if WITH_VERSIONING
return true; _version++;
} #endif
return true;
}
return false; return false;
} }
@@ -585,8 +620,10 @@ namespace Recyclable.Collections
#nullable restore #nullable restore
} }
_version++; #if WITH_VERSIONING
} _version++;
#endif
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public void RemoveAt(long index) public void RemoveAt(long index)
@@ -623,8 +660,10 @@ namespace Recyclable.Collections
#nullable restore #nullable restore
} }
_version++; #if WITH_VERSIONING
} _version++;
#endif
}
public void RemoveBlock(int index) public void RemoveBlock(int index)
{ {
@@ -644,17 +683,21 @@ namespace Recyclable.Collections
_nextItemBlockIndex--; _nextItemBlockIndex--;
} }
_lastBlockWithData--; _lastBlockWithData--;
_version++; #if WITH_VERSIONING
} _version++;
#endif
}
public void Dispose() public void Dispose()
{ {
_version++; #if WITH_VERSIONING
if (_capacity > 0) _version++;
{ #endif
Clear(); if (_capacity > 0)
if (_memoryBlocks.Length >= RecyclableDefaults.MinPooledArrayLength) {
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. // If anything, it has been already cleared by .Clear(), Remove() or RemoveAt() methods, as the list was modified / disposed.
RecyclableArrayPool<T[]>.ReturnShared(_memoryBlocks, false); 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;
}
}
}
}
+220 -174
View File
@@ -1,211 +1,257 @@
using System.Buffers;
using System.Collections; using System.Collections;
using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Recyclable.Collections.Pools;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
internal class RecyclableQueue<T> : IRecyclableOwner<T>, IList<T>, IDisposable internal sealed class RecyclableQueue<T> : IEnumerable<T>, IDisposable
{ {
private static readonly ArrayPool<T> _arrayPool = ArrayPool<T>.Create(); internal static readonly bool _needsClearing = !typeof(T).IsValueType;
private static readonly IEqualityComparer<T> _equalityComparer = EqualityComparer<T>.Default;
private bool _disposedValue; [MethodImpl(MethodImplOptions.AggressiveInlining)]
private RecyclableList<T[]> Memory { get; } 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)] chunk.Value = size >= RecyclableDefaults.MinPooledArrayLength
private static T Dequeue(RecyclableQueue<T> queue) ? RecyclableArrayPool<T>.RentShared(size)
{ : new T[size];
if (queue.LongCount <= 0) }
{
throw new ArgumentOutOfRangeException(nameof(queue), "There are no items in the queue");
}
var toRemove = queue[0]; chunk.Top = 0;
queue.RemovedCount++; chunk.Bottom = 0;
queue.LongCount--; chunk.Previous = previous;
if (queue.Capacity - queue.LongCount == queue.BlockSize) chunk.Next = null;
{ return chunk;
queue.RemoveBlock(0); }
queue.RemovedCount -= queue.BlockSize;
}
return toRemove; [MethodImpl(MethodImplOptions.AggressiveInlining)]
} private static void ReturnChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
{
BiDirectionalRecyclableArrayPoolChunkPool<T>.Return(chunk);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool TryDequeue(RecyclableQueue<T> queue, out T? item) private static void DisposeChunk(BiDirectionalRecyclableArrayPoolChunk<T> chunk)
{ {
if (queue.LongCount > 0) BiDirectionalRecyclableArrayPoolChunkPool<T>.Dispose(chunk, _needsClearing);
{ }
item = queue.Dequeue();
return true;
}
item = default; internal BiDirectionalRecyclableArrayPoolChunk<T> _head;
return false; 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)] if (!BitOperations.IsPow2((uint)initialCapacity))
protected static long GetAbsoluteIndex(long index, long removedCount) => index + removedCount; {
initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] _head = RentChunk(initialCapacity, null);
protected long GetRelativeIndex(long index) => index - RemovedCount; _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) public int Count => checked((int)_count);
{ public long LongCount => _count;
BlockSize = blockSize;
Memory = new(1);
foreach (var item in source)
{
Enqueue(item);
}
}
public RecyclableQueue(int blockSize = RecyclableDefaults.BlockSize) public void Enqueue(T item)
{ {
BlockSize = blockSize; if (_tail.Top == _tail.Value.Length)
Memory = new(1); {
} Grow();
}
public int BlockSize { get; protected set; } _tail.Value[_tail.Top++] = item;
public long Capacity { get; set; } _count++;
}
public int Count public void Add(T item) => Enqueue(item);
{
get => checked((int)LongCount);
set => LongCount = value;
}
public long LongCount { get; set; } public T Dequeue()
public bool IsReadOnly { get; } {
if (_count == 0)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(_count), "Queue is empty");
}
public T this[int index] _count--;
{ var value = _head.Value[_head.Bottom];
get => GetItem(index); if (_needsClearing)
set => SetItem(index, value); {
} _head.Value[_head.Bottom] = default!;
}
public T this[long index] _head.Bottom++;
{ if (_head.Bottom == _head.Top && _head.Next != null)
get => GetItem(index); {
set => SetItem(index, value); ReleaseHeadChunk();
} }
else if (_head.Bottom == _head.Top)
{
_head.Bottom = 0;
_head.Top = 0;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] return value;
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;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryDequeue(out T? item)
private T GetItem(long index) {
{ if (_count > 0)
long absoluteIndex = GetAbsoluteIndex(index, RemovedCount); {
int arrayIndex = RecyclableQueueHelpers<T>.ToArrayIndex(absoluteIndex, BlockSize); item = Dequeue();
long itemIndex = RecyclableQueueHelpers<T>.ToItemIndex(absoluteIndex, BlockSize); return true;
return Memory[arrayIndex][itemIndex]; }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] item = default;
public void Enqueue(T item) return false;
{ }
if (LongCount == Capacity)
{
AddBlock();
}
SetItem(LongCount, item); public void Clear()
LongCount++; {
} while (_head.Next != null)
{
ReleaseHeadChunk();
}
public void AddBlock() if (_needsClearing && _head.Top > _head.Bottom)
{ {
T[] newArray = (BlockSize >= RecyclableDefaults.MinPooledArrayLength) Array.Clear(_head.Value, _head.Bottom, _head.Top - _head.Bottom);
? _arrayPool.Rent(BlockSize) }
: new T[BlockSize];
Memory.Add(newArray); _capacity = _head.Value.Length;
Capacity += BlockSize; _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() var newChunk = RentChunk(newSize, _tail);
{ _tail.Next = newChunk;
try _tail = newChunk;
{ _capacity += newSize;
// 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();
}
}
#pragma warning disable CS0618 private void ReleaseHeadChunk()
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); var toReturn = _head;
public IEnumerable<T> Enumerate() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount); _head = toReturn.Next!;
public IEnumerator<T> GetEnumerator() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount).GetEnumerator(); _head.Previous = null;
IEnumerator IEnumerable.GetEnumerator() => RecyclableQueueHelpers<T>.Enumerate(Memory, BlockSize, LongCount).GetEnumerator(); _capacity -= toReturn.Value.Length;
public int IndexOf(T item) => checked((int)GetRelativeIndex(RecyclableQueueHelpers<T>.LongIndexOf(Memory, BlockSize, item, _equalityComparer))); ReturnChunk(toReturn);
public long LongIndexOf(T item) => GetRelativeIndex(RecyclableQueueHelpers<T>.LongIndexOf(Memory, BlockSize, item, _equalityComparer)); }
#pragma warning restore CS0618
public void Insert(int index, T item) => throw new NotSupportedException(); public void Dispose()
public void RemoveAt(int index) => throw new NotSupportedException(); {
public bool TryDequeue(out T? item) => TryDequeue(this, out item); if (_disposed)
public bool Remove(T item) => TryDequeue(out var _); {
public void RemoveBlock(int index) return;
{ }
try
{
if (Memory[index].LongLength is > RecyclableDefaults.MinPooledArrayLength and <= int.MaxValue)
{
_arrayPool.Return(Memory[index], NeedsClearing);
}
Memory.RemoveAt(index); var chunk = _head;
} while (chunk != null)
finally {
{ var next = chunk.Next;
Capacity -= BlockSize; DisposeChunk(chunk);
//LongCount -= BlockSize; chunk = next;
} }
}
public void Dispose() _disposed = true;
{ GC.SuppressFinalize(this);
if (!_disposedValue) }
{
Clear();
_disposedValue = 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(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()
{
}
}
}
}
+266 -66
View File
@@ -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 namespace Recyclable.Collections
{ {
internal class RecyclableSortedList<TKey, TValue> : RecyclableLongList<(TKey Key, TValue Value)> internal sealed class RecyclableSortedList<TKey, TValue> : IEnumerable<(TKey Key, TValue Value)>, IDisposable
where TKey : notnull where TKey : notnull
{ {
private static readonly IComparer<(TKey, TValue)> _comparer = new FrequencyTupleComparer<TKey, TValue>(); internal static readonly bool _needsClearing = !typeof(TKey).IsValueType || !typeof(TValue).IsValueType;
private uint _isUpdating;
private readonly SystemRandomNumberGenerator _randomNumberGenerator = new();
protected void ListUpdated() internal TKey[] _keys;
{ internal TValue[] _values;
QuickSort(); 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) if (!BitOperations.IsPow2((uint)initialCapacity))
: this(source.Select(x => (x.Key, x.Value)), blockSize) {
{ initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
} }
public RecyclableSortedList(IEnumerable<(TKey Key, TValue Value)> source, int blockSize = RecyclableDefaults.BlockSize) _keys = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
: base(source, blockSize) ? RecyclableArrayPool<TKey>.RentShared(initialCapacity)
{ : new TKey[initialCapacity];
QuickSort(); _values = initialCapacity >= RecyclableDefaults.MinPooledArrayLength
} ? RecyclableArrayPool<TValue>.RentShared(initialCapacity)
: new TValue[initialCapacity];
_count = 0;
_comparer = comparer ?? Comparer<TKey>.Default;
}
public RecyclableSortedList(int blockSize = RecyclableDefaults.BlockSize) public int Count => _count;
: base(blockSize)
{
}
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() [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public void Add(TKey key, TValue value)
BeginUpdate(); {
try int index = BinarySearch(key);
{ if (index >= 0)
base.Clear(); {
} ThrowHelper.ThrowArgumentOutOfRangeException(nameof(key), "An element with the same key already exists.");
finally }
{
EndUpdate();
}
}
public void EndUpdate(bool raiseListUpdated = true) Insert(~index, key, value);
{ }
if (_isUpdating > 0)
{
_isUpdating--;
if (raiseListUpdated && _isUpdating == 0)
{
ListUpdated();
}
}
}
public void QuickSort() [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool ContainsKey(TKey key) => BinarySearch(key) >= 0;
BeginUpdate();
try [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ public bool TryGetValue(TKey key, out TValue value)
QuickSortExtensions<(TKey, TValue)>.QuickSort(this, _comparer, _randomNumberGenerator); {
} int index = BinarySearch(key);
finally if (index >= 0)
{ {
EndUpdate(false); 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
+220 -76
View File
@@ -1,94 +1,238 @@
using System.Collections; using System.Collections;
using System.Numerics;
using System.Runtime.CompilerServices;
using Recyclable.Collections.Pools;
namespace Recyclable.Collections namespace Recyclable.Collections
{ {
internal class RecyclableStack<T> : IList<T>, IDisposable internal sealed class RecyclableStack<T> : IEnumerable<T>, IDisposable
{ {
private static readonly bool _needsClearing = !typeof(T).IsValueType; internal static readonly bool _needsClearing = !typeof(T).IsValueType;
private bool _disposedValue;
protected RecyclableLongList<T> List { get; } [MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Count => checked((int)List._longCount); private static RecyclableArrayPoolChunk<T> RentChunk(int size, RecyclableArrayPoolChunk<T>? previous)
public long LongCount {
{ var chunk = RecyclableArrayPoolChunkPool<T>.Rent();
get => List._longCount; if (chunk.Value.Length < size)
set => List._longCount = value; {
} if (chunk.Value.Length >= RecyclableDefaults.MinPooledArrayLength)
public bool IsReadOnly => false; {
RecyclableArrayPool<T>.ReturnShared(chunk.Value, _needsClearing);
}
public T this[int index] { get => this[(long)index]; set => this[(long)index] = value; } chunk.Value = size >= RecyclableDefaults.MinPooledArrayLength
public T this[long index] { get => List[index]; set => List[index] = value; } ? RecyclableArrayPool<T>.RentShared(size)
: new T[size];
}
chunk.Index = 0;
chunk.Previous = previous;
chunk.Next = null;
return chunk;
}
public RecyclableStack(int blockSize = RecyclableDefaults.BlockSize) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ private static void ReturnChunk(RecyclableArrayPoolChunk<T> chunk)
List = new(blockSize); {
} RecyclableArrayPoolChunkPool<T>.Return(chunk);
}
public RecyclableStack(IEnumerable<T> list, int blockSize = RecyclableDefaults.BlockSize) [MethodImpl(MethodImplOptions.AggressiveInlining)]
{ private static void DisposeChunk(RecyclableArrayPoolChunk<T> chunk)
List = new(list, blockSize); {
} RecyclableArrayPoolChunkPool<T>.Dispose(chunk, _needsClearing);
}
public RecyclableStack(RecyclableLongList<T> list, int blockSize = RecyclableDefaults.BlockSize) internal RecyclableArrayPoolChunk<T> _current;
{ internal bool _disposed;
List = new(list, blockSize); internal long _capacity;
} internal long _count;
public void Push(T item) public RecyclableStack(int initialCapacity = RecyclableDefaults.InitialCapacity)
{ {
List.Add(item); if (initialCapacity < 1)
} {
initialCapacity = 1;
}
public T Pop() if (!BitOperations.IsPow2((uint)initialCapacity))
{ {
RecyclableLongList<T> list = List; initialCapacity = (int)BitOperations.RoundUpToPowerOf2((uint)initialCapacity);
var itemIndex = (int)(list.LongCount & list._blockSizeMinus1) - 1; }
var blockIndex = (int)(LongCount >> list._blockSizePow2BitShift);
var toRemove = list._memoryBlocks[blockIndex][itemIndex];
LongCount--; _current = RentChunk(initialCapacity, null);
_capacity = initialCapacity;
_count = 0;
}
if (_needsClearing) public RecyclableStack(IEnumerable<T> source, int initialCapacity = RecyclableDefaults.InitialCapacity) : this(initialCapacity)
{ {
#nullable disable foreach (var item in source)
new Span<T>(list._memoryBlocks[blockIndex])[itemIndex] = default; {
#nullable restore Push(item);
} }
}
if (itemIndex == 0) public int Count => checked((int)_count);
{ public long LongCount => _count;
list.RemoveBlock(blockIndex);
}
return toRemove; public void Push(T item)
} {
if (_current.Index == _current.Value.Length)
{
Grow();
}
public int IndexOf(T item) => List.IndexOf(item); _current.Value[_current.Index++] = item;
public long LongIndexOf(T item) => List.LongIndexOf(item); _count++;
public void Insert(int index, T item) => List.Insert(index, item); }
public void RemoveAt(int index) => List.RemoveAt(index);
public void Add(T item) => List.Add(item);
public void Clear() => List.Clear();
public bool Contains(T item) => List.Contains(item);
public void CopyTo(T[] array, int arrayIndex) => List.CopyTo(array, arrayIndex);
public bool Remove(T item) => List.Remove(item);
public IEnumerator<T> GetEnumerator() => List.Reverse().GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
protected virtual void Dispose(bool disposing) public void Add(T item) => Push(item);
{
if (!_disposedValue)
{
Clear();
_disposedValue = true;
}
}
public void Dispose() public T Pop()
{ {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method if (_count == 0)
Dispose(disposing: true); {
GC.SuppressFinalize(this); 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks> <TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))]
public void SetByIndexShouldReturnCorrectItemWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize) public void SetByIndexShouldReturnCorrectItemWhenLong(string testCase, IEnumerable<long> testData, long itemsCount, int targetBlockSize)
{ {
// Prepare // Prepare
using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount); using var list = new RecyclableLongList<long>(testData, minBlockSize: targetBlockSize, itemsCount);
var expectedData = testData.ToList(); var expectedData = testData.ToList();
// Act & Validate // Act & Validate
foreach (long item in testData) foreach (long item in testData)
@@ -941,11 +941,96 @@ namespace Recyclable.CollectionsTests
list[item - 1] = item + 1; list[item - 1] = item + 1;
// Validate // Validate
expectedData[(int)item - 1] = item + 1; expectedData[(int)item - 1] = item + 1;
list[item - 1].Should().Be(item + 1); list[item - 1].Should().Be(item + 1);
list.Should().Equal(expectedData); 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] [Theory]
[MemberData(nameof(RecyclableLongListTestData.SourceDataWithBlockSizeVariants), MemberType = typeof(RecyclableLongListTestData))] [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" }; private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" };
[Fact] [Theory]
public void ShouldNotBeSortedWhenCreated() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void ShouldNotBeSortedWhenCreated(int itemsCount)
// Act {
using var list = new RecyclableQueue<string>(_testData, 2); // Act
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
using var list = new RecyclableQueue<string>(data, 2);
// Validate // Validate
_ = list.Should().HaveCount(_testData.Length) _ = list.Should().HaveCount(itemsCount)
.And.ContainInConsecutiveOrder(_testData); .And.ContainInConsecutiveOrder(data);
_ = list.LongCount.Should().Be(_testData.Length); _ = list.LongCount.Should().Be(itemsCount);
} }
[Fact] [Fact]
public void ShouldBeEmptyWhenNotInitialized() public void ShouldBeEmptyWhenNotInitialized()
{ {
// Act // Act
using var list = new RecyclableQueue<string>(2); using var list = new RecyclableQueue<string>(2);
// Validate // Validate
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
_ = list.LongCount.Should().Be(0); _ = list.LongCount.Should().Be(0);
} }
[Fact] [Theory]
public void ShouldNotBeSortedWhenInitialized() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void ShouldNotBeSortedWhenInitialized(int itemsCount)
// Act {
using var list = new RecyclableQueue<string>(2) // Act
{ var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
_testData[0], using var list = new RecyclableQueue<string>(2)
_testData[1], {
_testData[2], // populate via collection initializer
_testData[3], };
_testData[4] foreach (var item in data)
}; {
list.Enqueue(item);
}
// Validate // Validate
_ = list.Should().NotBeEmpty() if (itemsCount == 0)
.And.ContainInConsecutiveOrder(_testData) {
.And.BeEquivalentTo(_testData); _ = list.Should().BeEmpty();
} }
else
{
_ = list.Should().NotBeEmpty()
.And.ContainInConsecutiveOrder(data)
.And.BeEquivalentTo(data);
}
}
[Fact] [Theory]
public void ShouldBeEmptyAfterClear() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void ShouldBeEmptyAfterClear(int itemsCount)
// Prepare {
using var list = new RecyclableQueue<string>(_testData); // Prepare
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString());
using var list = new RecyclableQueue<string>(data);
// Act // Act
list.Clear(); list.Clear();
// Validate // Validate
_ = list.Should().BeEmpty(); _ = list.Should().BeEmpty();
} }
[Fact] [Theory]
public void DequeueShouldReturnItemsInTheSameOrder() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void DequeueShouldReturnItemsInTheSameOrder(int itemsCount)
// Prepare {
using var list = new RecyclableQueue<string>(); // Prepare
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
using var list = new RecyclableQueue<string>();
// Act // Act
for (var itemIdx = 0; itemIdx < _testData.Length; itemIdx++) for (var itemIdx = 0; itemIdx < data.Length; itemIdx++)
{ {
list.Enqueue(_testData[itemIdx]); list.Enqueue(data[itemIdx]);
} }
var dequeuedItems = new RecyclableLongList<string>(); var dequeuedItems = new RecyclableLongList<string>();
while (list.LongCount > 0) while (list.LongCount > 0)
{ {
dequeuedItems.Add(list.Dequeue()); dequeuedItems.Add(list.Dequeue());
} }
// Validate // Validate
_ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData) _ = dequeuedItems.Should().ContainInConsecutiveOrder(data)
.And.BeEquivalentTo(_testData); .And.BeEquivalentTo(data);
} }
[Fact] [Fact]
public void DequeueShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound() public void DequeueShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
{ {
// Prepare // Prepare
using var list = new RecyclableQueue<string>(_testData); using var list = new RecyclableQueue<string>(_testData);
// Act // Act
while (list.LongCount > 0) while (list.LongCount > 0)
{ {
_ = list.Dequeue(); _ = list.Dequeue();
} }
_ = Assert.Throws<ArgumentOutOfRangeException>(() => list.Dequeue()); _ = Assert.Throws<ArgumentOutOfRangeException>(() => list.Dequeue());
_ = list.LongCount.Should().Be(0); _ = list.LongCount.Should().Be(0);
} }
[Fact] [Theory]
public void GetEnumeratorShouldYieldAllItemsInTheSameOrder() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void GetEnumeratorShouldYieldAllItemsInTheSameOrder(int itemsCount)
// Prepare {
using var list = new RecyclableQueue<string>(_testData); // Prepare
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
using var list = new RecyclableQueue<string>(data);
// Act // Act
var actual = new RecyclableLongList<string>(); var actual = new RecyclableLongList<string>();
using var enumerator = list.GetEnumerator(); using var enumerator = list.GetEnumerator();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
actual.Add(enumerator.Current); actual.Add(enumerator.Current);
} }
// Validate // Validate
_ = actual.Should().ContainInConsecutiveOrder(_testData) _ = actual.Should().ContainInConsecutiveOrder(data)
.And.BeEquivalentTo(_testData); .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 Recyclable.Collections;
using System.Linq;
namespace Recyclable.CollectionsTests namespace Recyclable.CollectionsTests
{ {
public class RecyclableSortedListTests public class RecyclableSortedListTests
{ {
private static readonly (int, string)[] _testData = new[] { (5, "a"), (4, "d"), (3, "c"), (2, "b"), (1, "a") }; [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] _ = list.Should().HaveCount(itemsCount)
public void ShouldBeSortedWhenCreated() .And.BeInAscendingOrder(x => x.Key);
{ for (int i = 0; i < itemsCount; i++)
// Act {
using var sortedList = new RecyclableSortedList<int, string>(_testData, 2); long expectedKey = i + 1;
list.GetKey(i).Should().Be(expectedKey);
list.GetValue(i).Should().Be(-expectedKey);
}
}
// Validate [Fact]
_ = sortedList.Should().HaveCount(_testData.Length) public void IndexerShouldUpdateValue()
.And.BeEquivalentTo(_testData) {
.And.BeInAscendingOrder((item) => item.Key); using var list = new RecyclableSortedList<int, string>();
} list.Add(1, "a");
[Fact] list[1] = "b";
public void ShouldBeEmptyWhenNotInitialized()
{
// Act
using var sortedList = new RecyclableSortedList<int, string>(2);
// Validate _ = list[1].Should().Be("b");
_ = sortedList.Should().BeEmpty(); }
}
[Fact] [Theory]
public void ShouldNotBeSortedWhenInitialized() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void RemoveShouldDeleteItem(int itemsCount)
// Act {
using var sortedList = new RecyclableSortedList<int, string>(2) using var list = new RecyclableSortedList<long, long>();
{ foreach (var key in RecyclableLongListTestData.CreateTestData(itemsCount))
_testData[0], {
_testData[1], list.Add(key, key);
_testData[2], }
_testData[3],
_testData[4]
};
// Validate if (itemsCount > 0)
_ = sortedList.Should().NotBeEmpty() {
.And.ContainInConsecutiveOrder(_testData); 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] [Fact]
public void ShouldBeSortedWhenUpdateEnds() public void TryGetValueShouldReturnValue()
{ {
// Prepare using var list = new RecyclableSortedList<int, string>();
using var sortedList = new RecyclableSortedList<int, string>(2); list.Add(1, "a");
// Act var found = list.TryGetValue(1, out var value);
sortedList.BeginUpdate();
foreach (var testItem in _testData)
{
sortedList.Add(testItem);
}
// Validate _ = found.Should().BeTrue();
_ = sortedList.Should().BeInDescendingOrder(x => x.Key); _ = value.Should().Be("a");
}
// Act [Theory]
sortedList.EndUpdate(); [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 var actual = new List<long>();
_ = sortedList.Should().BeInAscendingOrder(x => x.Key); var enumerator = list.GetEnumerator();
} while (enumerator.MoveNext())
{
actual.Add(enumerator.Current.Key);
}
[Fact] var expected = RecyclableLongListTestData.CreateTestData(itemsCount).ToList();
public void ShouldBeEmptyAfterClear() _ = actual.Should().ContainInConsecutiveOrder(expected);
{ }
// Prepare }
using var sortedList = new RecyclableSortedList<int, string>(_testData);
// Act
sortedList.Clear();
// Validate
_ = sortedList.Should().BeEmpty();
}
}
} }
@@ -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" }; private static readonly string[] _testData = new[] { "a", "d", "c", "b", "a" };
[Fact] [Theory]
public void GetEnumeratorShouldYieldAllItemsInReversedOrder() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void GetEnumeratorShouldYieldAllItemsInReversedOrder(int itemsCount)
// Prepare {
using var list = new RecyclableStack<string>(_testData); // Prepare
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
using var list = new RecyclableStack<string>(data);
// Act // Act
var actual = new RecyclableLongList<string>(); var actual = new RecyclableLongList<string>();
using var enumerator = list.GetEnumerator(); using var enumerator = list.GetEnumerator();
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
actual.Add(enumerator.Current); actual.Add(enumerator.Current);
} }
// Validate // Validate
_ = actual.Should().ContainInConsecutiveOrder(_testData.Reverse()); _ = actual.Should().ContainInConsecutiveOrder(data.Reverse());
} }
[Fact] [Theory]
public void ShouldNotBeSortedWhenCreated() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void ShouldNotBeSortedWhenCreated(int itemsCount)
// Act {
using var sortedList = new RecyclableStack<string>(_testData, 2); // Act
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
using var sortedList = new RecyclableStack<string>(data, 2);
// Validate // Validate
_ = sortedList.Should().HaveCount(_testData.Length) _ = sortedList.Should().HaveCount(itemsCount)
.And.ContainInConsecutiveOrder(_testData); .And.ContainInConsecutiveOrder(data.Reverse());
} }
[Fact] [Fact]
public void ShouldBeEmptyWhenNotInitialized() public void ShouldBeEmptyWhenNotInitialized()
{ {
// Act // Act
using var sortedList = new RecyclableStack<string>(2); using var sortedList = new RecyclableStack<string>(2);
// Validate // Validate
_ = sortedList.Should().BeEmpty(); _ = sortedList.Should().BeEmpty();
} }
[Fact] [Theory]
public void ShouldNotBeSortedWhenInitialized() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void ShouldNotBeSortedWhenInitialized(int itemsCount)
// Act {
using var sortedList = new RecyclableStack<string>(2) // Act
{ var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
_testData[0], using var sortedList = new RecyclableStack<string>(2);
_testData[1], foreach (var item in data)
_testData[2], {
_testData[3], sortedList.Push(item);
_testData[4] }
};
// Validate // Validate
_ = sortedList.Should().NotBeEmpty() if (itemsCount == 0)
.And.ContainInConsecutiveOrder(_testData) {
.And.BeEquivalentTo(_testData); _ = sortedList.Should().BeEmpty();
} }
else
{
_ = sortedList.Should().NotBeEmpty()
.And.ContainInConsecutiveOrder(data.Reverse())
.And.BeEquivalentTo(data);
}
}
[Fact] [Theory]
public void ShouldBeEmptyAfterClear() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void ShouldBeEmptyAfterClear(int itemsCount)
// Prepare {
using var sortedList = new RecyclableStack<string>(_testData); // Prepare
var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
using var sortedList = new RecyclableStack<string>(data);
// Act // Act
sortedList.Clear(); sortedList.Clear();
// Validate // Validate
_ = sortedList.Should().BeEmpty(); _ = sortedList.Should().BeEmpty();
} }
[Fact] [Theory]
public void PopShouldReturnItemsInReversedOrder() [MemberData(nameof(RecyclableLongListTestData.ItemsCountTestCases), MemberType = typeof(RecyclableLongListTestData))]
{ public void PopShouldReturnItemsInReversedOrder(int itemsCount)
// Prepare {
using var list = new RecyclableStack<string>(); // Prepare
using var list = new RecyclableStack<string>();
// Act // Act
for (var itemIdx = 0; itemIdx < _testData.Length; itemIdx++) var data = RecyclableLongListTestData.CreateTestData(itemsCount).Select(i => i.ToString()).ToArray();
{ for (var itemIdx = 0; itemIdx < data.Length; itemIdx++)
list.Push(_testData[itemIdx]); {
} list.Push(data[itemIdx]);
}
var dequeuedItems = new RecyclableLongList<string>(); var dequeuedItems = new RecyclableLongList<string>();
while (list.LongCount > 0) while (list.LongCount > 0)
{ {
dequeuedItems.Add(list.Pop()); dequeuedItems.Add(list.Pop());
} }
// Validate // Validate
_ = dequeuedItems.Should().ContainInConsecutiveOrder(_testData.Reverse()); _ = dequeuedItems.Should().ContainInConsecutiveOrder(data.Reverse());
} }
[Fact] [Fact]
public void PopShoudRaiseArgumentOutOfRangeWhenNoMoreElementsFound() public void PopShouldRaiseArgumentOutOfRangeWhenNoMoreElementsFound()
{ {
// Prepare // Prepare
using var list = new RecyclableQueue<string>(_testData); using var list = new RecyclableStack<string>(_testData);
// Act // Act
while (list.LongCount > 0) while (list.LongCount > 0)
{ {
_ = list.Dequeue(); _ = 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"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules> <Nullable>enable</Nullable>
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <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>