From f4d34e9bec0c75cba5f701527694d220aef16371 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:12:19 +0100 Subject: [PATCH 01/36] Bump Microsoft.CodeAnalysis from 4.11.0 to 4.12.0 (#185) Bumps [Microsoft.CodeAnalysis](https://github.com/dotnet/roslyn) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index bf2997f..331743b 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -11,7 +11,7 @@ - + From 8bdc1311dc2c6efa0dd55055835ca57e7655b33a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:12:44 +0100 Subject: [PATCH 02/36] Bump MSTest.TestFramework and Verify.MSTest (#184) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) and [Verify.MSTest](https://github.com/VerifyTests/Verify). These dependencies needed to be updated together. Updates `MSTest.TestFramework` from 3.6.3 to 3.6.3 - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.6.3...v3.6.3) Updates `Verify.MSTest` from 27.0.1 to 28.4.0 - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/compare/27.0.1...28.4.0) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Verify.MSTest dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 7f16983..c133d39 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From eb10bf7d6aa781927dd82e441a006fa1fadbb185 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:13:10 +0100 Subject: [PATCH 03/36] Bump PolySharp from 1.14.1 to 1.15.0 (#183) Bumps [PolySharp](https://github.com/Sergio0694/PolySharp) from 1.14.1 to 1.15.0. - [Release notes](https://github.com/Sergio0694/PolySharp/releases) - [Commits](https://github.com/Sergio0694/PolySharp/compare/1.14.1...1.15.0) --- updated-dependencies: - dependency-name: PolySharp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj index 242542b..c98e082 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj index bd17e32..ce9334b 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj @@ -13,7 +13,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From e5f00e9f41dac92f02730d3b52185fb7d2678005 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 18:13:59 +0100 Subject: [PATCH 04/36] Bump System.Formats.Asn1 from 8.0.1 to 9.0.0 (#182) Bumps [System.Formats.Asn1](https://github.com/dotnet/runtime) from 8.0.1 to 9.0.0. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v8.0.1...v9.0.0) --- updated-dependencies: - dependency-name: System.Formats.Asn1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index 331743b..90cb940 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -18,7 +18,7 @@ - + From 5012d2db1736c08079eadcd30c0f68c37f9c9e32 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 21:27:48 +0100 Subject: [PATCH 05/36] Bump MSTest.TestFramework from 3.6.3 to 3.6.4 (#187) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.6.3 to 3.6.4. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.6.3...v3.6.4) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 2 +- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index d090f59..d41ab85 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index 90cb940..e12acd6 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -10,7 +10,7 @@ - + diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index c133d39..1873bd2 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 8e5da37402d038c7f057667f0a42dcbb99310246 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 21:36:14 +0100 Subject: [PATCH 06/36] Bump Microsoft.CodeAnalysis.CSharp from 4.11.0 to 4.12.0 (#188) Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn) from 4.11.0 to 4.12.0. - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.CSharp dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.csproj | 4 ++-- .../Akade.IndexedSet.InternalSourceGenerator.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj index c98e082..a01677c 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj @@ -11,8 +11,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj index ce9334b..a8c2888 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj @@ -11,8 +11,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive From 0d96b0b872cf69d5658008a019093a05e006e700 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:54:41 +0100 Subject: [PATCH 07/36] Bump MSTest.TestFramework and Verify.MSTest (#194) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) and [Verify.MSTest](https://github.com/VerifyTests/Verify). These dependencies needed to be updated together. Updates `MSTest.TestFramework` from 3.6.4 to 3.7.0 - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.6.4...v3.7.0) Updates `Verify.MSTest` from 28.4.0 to 28.8.1 - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/commits) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Verify.MSTest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 1873bd2..1325fca 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -10,13 +10,13 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 8ae3fff04b5281b9a81db520ae1c4e652dd6a33f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 16:56:03 +0100 Subject: [PATCH 08/36] Bump dotnet-reportgenerator-globaltool from 5.4.1 to 5.4.3 (#192) Bumps [dotnet-reportgenerator-globaltool](https://github.com/danielpalme/ReportGenerator) from 5.4.1 to 5.4.3. - [Release notes](https://github.com/danielpalme/ReportGenerator/releases) - [Commits](https://github.com/danielpalme/ReportGenerator/compare/v5.4.1...v5.4.3) --- updated-dependencies: - dependency-name: dotnet-reportgenerator-globaltool dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e44f1ad..3d0ce8b 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-reportgenerator-globaltool": { - "version": "5.4.1", + "version": "5.4.3", "commands": [ "reportgenerator" ] From 3566826cc2422ad86d1cbd8e7e0502ca86ef9eee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 17:02:55 +0100 Subject: [PATCH 09/36] Bump coverlet.collector from 6.0.2 to 6.0.3 (#193) Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 6.0.2 to 6.0.3. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.2...v6.0.3) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index d41ab85..e6fbc6f 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 1325fca..051dcfe 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From be7f83ec91b30899dc76a046d7be602ee9bd2cf8 Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sun, 19 Jan 2025 17:48:09 +0100 Subject: [PATCH 10/36] Optimize dictionary access with CollectionsMarshal.GetValueRefOrAddDefault within Trie and Lookup (#195) --- Akade.IndexedSet/DataStructures/Lookup.cs | 10 +++++----- Akade.IndexedSet/DataStructures/Trie.cs | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Akade.IndexedSet/DataStructures/Lookup.cs b/Akade.IndexedSet/DataStructures/Lookup.cs index b9648e1..e9a577f 100644 --- a/Akade.IndexedSet/DataStructures/Lookup.cs +++ b/Akade.IndexedSet/DataStructures/Lookup.cs @@ -1,4 +1,6 @@ -namespace Akade.IndexedSet.DataStructures; +using System.Runtime.InteropServices; + +namespace Akade.IndexedSet.DataStructures; /// /// Modifiable lookup based on with as value collection per key. @@ -11,10 +13,8 @@ internal class Lookup public bool Add(TKey key, TValue value) { - if (!_values.TryGetValue(key, out HashSet? keySet)) - { - keySet = _values[key] = []; - } + ref HashSet? keySet = ref CollectionsMarshal.GetValueRefOrAddDefault(_values, key, out _); + keySet ??= []; if (keySet.Add(value)) { diff --git a/Akade.IndexedSet/DataStructures/Trie.cs b/Akade.IndexedSet/DataStructures/Trie.cs index a98bd21..d98b10e 100644 --- a/Akade.IndexedSet/DataStructures/Trie.cs +++ b/Akade.IndexedSet/DataStructures/Trie.cs @@ -1,6 +1,7 @@ using Akade.IndexedSet.Extensions; using Akade.IndexedSet.Utils; using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; namespace Akade.IndexedSet.DataStructures; @@ -177,11 +178,10 @@ internal bool Add(ReadOnlySpan key, TElement element) else { _children ??= []; - if (!_children.TryGetValue(key[0], out TrieNode? trieNode)) - { - _children[key[0]] = trieNode = new(); - } + ref TrieNode? trieNode = ref CollectionsMarshal.GetValueRefOrAddDefault(_children, key[0], out _); + + trieNode ??= new(); return trieNode.Add(key[1..], element); } } From 2e1508bde2b4e4e40777f8a28b9197ab1949fe6e Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:46:41 +0100 Subject: [PATCH 11/36] Manual update to the latest of all packages (#196) --- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 4 ++-- .../Akade.IndexedSet.Analyzers.Test.csproj | 6 +++--- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index e6fbc6f..8051b6c 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -10,8 +10,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index e12acd6..e3ba397 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -9,8 +9,8 @@ - - + + @@ -18,7 +18,7 @@ - + diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 051dcfe..595228a 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -9,14 +9,14 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 89fdf1f66658d4a3d8293c8eef8234f1e40d3795 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 14:29:34 +0100 Subject: [PATCH 12/36] Bump MSTest.TestFramework from 3.7.1 to 3.7.2 (#199) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.7.1 to 3.7.2. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.7.1...v3.7.2) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 2 +- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index 8051b6c..aae353b 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index e3ba397..6438fa0 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -10,7 +10,7 @@ - + diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 595228a..487ee63 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 8dd2d555610433e6690b8cc84cda4fcd15bdf04c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 12:25:08 +0100 Subject: [PATCH 13/36] Bump coverlet.collector from 6.0.3 to 6.0.4 (#197) Bumps [coverlet.collector](https://github.com/coverlet-coverage/coverlet) from 6.0.3 to 6.0.4. - [Release notes](https://github.com/coverlet-coverage/coverlet/releases) - [Commits](https://github.com/coverlet-coverage/coverlet/compare/v6.0.3...v6.0.4) --- updated-dependencies: - dependency-name: coverlet.collector dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index aae353b..a223cb3 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -12,7 +12,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 487ee63..6cc13f5 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 75a453dd7afbffa0d41edec93d4d50973c92e5de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 14:44:45 +0100 Subject: [PATCH 14/36] Bump MSTest.TestFramework from 3.7.2 to 3.7.3 (#201) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.7.2 to 3.7.3. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.7.2...v3.7.3) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 2 +- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index a223cb3..4d3750f 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -11,7 +11,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index 6438fa0..be6608a 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -10,7 +10,7 @@ - + diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 6cc13f5..e96736e 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -10,7 +10,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 369ae87f5ba835dd22ba0f4c58b897e11a8db93e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 14:12:16 +0100 Subject: [PATCH 15/36] Bump MSTest.TestAdapter from 3.7.1 to 3.7.3 (#200) Bumps [MSTest.TestAdapter](https://github.com/microsoft/testfx) from 3.7.1 to 3.7.3. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.7.1...v3.7.3) --- updated-dependencies: - dependency-name: MSTest.TestAdapter dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index be6608a..1eec99b 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -9,7 +9,7 @@ - + diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index e96736e..6ddc4b4 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -9,7 +9,7 @@ - + all From e6061ae946d011f501bb8332b24b0e5ea88dee61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:53:24 +0100 Subject: [PATCH 16/36] Bump MSTest.TestFramework and Verify.MSTest (#203) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) and [Verify.MSTest](https://github.com/VerifyTests/Verify). These dependencies needed to be updated together. Updates `MSTest.TestFramework` from 3.7.3 to 3.7.3 - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.7.3...v3.7.3) Updates `Verify.MSTest` from 28.9.0 to 28.10.1 - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/compare/28.9.0...28.10.1) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Verify.MSTest dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 6ddc4b4..167bf98 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From d36eabcddf026b441bb2cdbd8a0d74ace2ea8759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 20:56:00 +0100 Subject: [PATCH 17/36] Bump dotnet-reportgenerator-globaltool from 5.4.3 to 5.4.4 (#202) Bumps [dotnet-reportgenerator-globaltool](https://github.com/danielpalme/ReportGenerator) from 5.4.3 to 5.4.4. - [Release notes](https://github.com/danielpalme/ReportGenerator/releases) - [Commits](https://github.com/danielpalme/ReportGenerator/compare/v5.4.3...v5.4.4) --- updated-dependencies: - dependency-name: dotnet-reportgenerator-globaltool dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 3d0ce8b..e921b3c 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-reportgenerator-globaltool": { - "version": "5.4.3", + "version": "5.4.4", "commands": [ "reportgenerator" ] From b4c825bc88bbf79ef11260e866d5c22a82df0346 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:59:47 +0100 Subject: [PATCH 18/36] Bump Bogus from 35.6.1 to 35.6.2 (#209) Bumps [Bogus](https://github.com/bchavez/Bogus) from 35.6.1 to 35.6.2. - [Release notes](https://github.com/bchavez/Bogus/releases) - [Changelog](https://github.com/bchavez/Bogus/blob/master/HISTORY.md) - [Commits](https://github.com/bchavez/Bogus/compare/v35.6.1...v35.6.2) --- updated-dependencies: - dependency-name: Bogus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj | 2 +- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj b/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj index 38dd733..6189961 100644 --- a/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj +++ b/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj @@ -10,7 +10,7 @@ - + diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index 4d3750f..35e9537 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -8,7 +8,7 @@ - + From 4f19c00d439f89b748a70276d3cae61fc0ec5c70 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Feb 2025 13:15:55 +0100 Subject: [PATCH 19/36] Bump Microsoft.NET.Test.Sdk from 17.12.0 to 17.13.0 (#207) Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 17.12.0 to 17.13.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Changelog](https://github.com/microsoft/vstest/blob/main/docs/releases.md) - [Commits](https://github.com/microsoft/vstest/compare/v17.12.0...v17.13.0) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index 1eec99b..c6afada 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -8,7 +8,7 @@ - + diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index 167bf98..a71f5d6 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -8,7 +8,7 @@ - + From 113ba0bf5ff90cf4fb4fd66ebab626f90bb83a1d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Mar 2025 21:40:42 +0100 Subject: [PATCH 20/36] Bump System.Formats.Asn1 from 9.0.1 to 9.0.3 (#214) Bumps [System.Formats.Asn1](https://github.com/dotnet/runtime) from 9.0.1 to 9.0.3. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v9.0.1...v9.0.3) --- updated-dependencies: - dependency-name: System.Formats.Asn1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index c6afada..9bf842a 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -18,7 +18,7 @@ - + From a6a96cb78b83426733a9165aaf77bbd70a13c7fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 23 Mar 2025 21:41:09 +0100 Subject: [PATCH 21/36] Bump Microsoft.CodeAnalysis.CSharp, Microsoft.CodeAnalysis.CSharp.Workspaces and Microsoft.NETFramework.ReferenceAssemblies (#212) Bumps [Microsoft.CodeAnalysis.CSharp](https://github.com/dotnet/roslyn), [Microsoft.CodeAnalysis.CSharp.Workspaces](https://github.com/dotnet/roslyn) and [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet). These dependencies needed to be updated together. Updates `Microsoft.CodeAnalysis.CSharp` from 4.12.0 to 4.13.0 - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) Updates `Microsoft.CodeAnalysis.CSharp.Workspaces` from 4.12.0 to 4.13.0 - [Release notes](https://github.com/dotnet/roslyn/releases) - [Changelog](https://github.com/dotnet/roslyn/blob/main/docs/Breaking%20API%20Changes.md) - [Commits](https://github.com/dotnet/roslyn/commits) Updates `Microsoft.NETFramework.ReferenceAssemblies` from 1.0.3 to 1.0.3 - [Commits](https://github.com/Microsoft/dotnet/commits) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.CSharp dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.CodeAnalysis.CSharp.Workspaces dependency-type: direct:production update-type: version-update:semver-minor - dependency-name: Microsoft.NETFramework.ReferenceAssemblies dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- .../Akade.IndexedSet.Analyzers.csproj | 4 ++-- .../Akade.IndexedSet.InternalSourceGenerator.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index 9bf842a..256431b 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -11,7 +11,7 @@ - + diff --git a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj index a01677c..d85ba10 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj @@ -11,8 +11,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj index a8c2888..88dd122 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj @@ -11,8 +11,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive From f46efaa4ecc43313402534ecc2ef8b35b2ecaa1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:24:58 +0200 Subject: [PATCH 22/36] Bump Bogus from 35.6.2 to 35.6.3 (#219) Bumps [Bogus](https://github.com/bchavez/Bogus) from 35.6.2 to 35.6.3. - [Release notes](https://github.com/bchavez/Bogus/releases) - [Changelog](https://github.com/bchavez/Bogus/blob/master/HISTORY.md) - [Commits](https://github.com/bchavez/Bogus/compare/v35.6.2...v35.6.3) --- updated-dependencies: - dependency-name: Bogus dependency-version: 35.6.3 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Benchmarks.csproj | 4 ++-- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj b/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj index 6189961..7c656cd 100644 --- a/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj +++ b/Akade.IndexedSet.Benchmarks/Akade.IndexedSet.Benchmarks.csproj @@ -1,4 +1,4 @@ - + Exe @@ -10,7 +10,7 @@ - + diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index 35e9537..17af911 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -8,7 +8,7 @@ - + From 114f9615c66077cd01e7fe62bf49d3fe5145a340 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:25:53 +0200 Subject: [PATCH 23/36] Bump System.Formats.Asn1 from 9.0.3 to 9.0.4 (#218) Bumps [System.Formats.Asn1](https://github.com/dotnet/runtime) from 9.0.3 to 9.0.4. - [Release notes](https://github.com/dotnet/runtime/releases) - [Commits](https://github.com/dotnet/runtime/compare/v9.0.3...v9.0.4) --- updated-dependencies: - dependency-name: System.Formats.Asn1 dependency-version: 9.0.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Test.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index 256431b..a4a120a 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -18,7 +18,7 @@ - + From 950a153d395aea5f2c2f9aa4e2750419c0018e8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:34:57 +0200 Subject: [PATCH 24/36] Bump Microsoft.NETFramework.ReferenceAssemblies and Microsoft.VSSDK.BuildTools (#204) Bumps [Microsoft.NETFramework.ReferenceAssemblies](https://github.com/Microsoft/dotnet) and Microsoft.VSSDK.BuildTools. These dependencies needed to be updated together. Updates `Microsoft.NETFramework.ReferenceAssemblies` from 1.0.3 to 1.0.3 - [Commits](https://github.com/Microsoft/dotnet/commits) Updates `Microsoft.VSSDK.BuildTools` from 17.12.2069 to 17.13.2126 --- updated-dependencies: - dependency-name: Microsoft.NETFramework.ReferenceAssemblies dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Microsoft.VSSDK.BuildTools dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.Analyzers.Vsix.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj index 9d4b485..8d8ebca 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj @@ -19,7 +19,7 @@ - + From a6acc085133ca83fa78655f3e6889ae77a8b43c6 Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:35:46 +0200 Subject: [PATCH 25/36] Custom comparers (#215) * Comparers for all indices CharEqualityComparers API for easy ignore case access * - Custom comparers - Some test cleanup * Fixed unit test * Continued * Unit tests for comparers & fixed discovered bug of incorrect FullTextIndex.StartsWith * All unit tests fixed --- .editorconfig | 5 +- .../BaseIndexTest.Contains.cs | 22 ++-- .../BaseIndexTest.RangeQueries.cs | 53 ++++---- .../CommonIndexTests/BaseIndexTest.Single.cs | 25 ++-- .../BaseIndexTest.StartsWith.cs | 22 ++-- .../CommonIndexTests/BaseIndexTest.Where.cs | 10 +- .../CommonIndexTests/BaseIndexTest.cs | 31 +++-- .../CommonIndexTests.FullText.cs | 23 ++-- .../CommonIndexTests.MultiRange.cs | 22 ++-- .../CommonIndexTests.NonUnique.cs | 20 +-- .../CommonIndexTests.Prefix.cs | 23 ++-- .../CommonIndexTests.Range.cs | 87 +++++++++---- .../CommonIndexTests.Unique.cs | 20 +-- .../DataStructures/BinaryHeapTests.cs | 22 ++-- .../DataStructures/SuffixTrieTests.cs | 36 +++--- .../DataStructures/TrieTests.cs | 46 ++++--- Akade.IndexedSet.Tests/FullTextIndices.cs | 57 ++++++--- Akade.IndexedSet.Tests/GeneralTests.cs | 2 +- Akade.IndexedSet.Tests/MultiValueIndices.cs | 38 +++++- Akade.IndexedSet.Tests/NonUniqueIndices.cs | 29 ++++- Akade.IndexedSet.Tests/PrefixIndices.cs | 73 ++++++----- Akade.IndexedSet.Tests/RangeIndices.cs | 27 +++- .../Samples/Appointments/AppointmentSample.cs | 17 ++- .../Samples/Graph/GraphSample.cs | 2 +- .../Samples/Leaderboard/LeaderboardSample.cs | 6 +- .../TypeaheadSample/TypeaheadSample.cs | 7 +- .../TestUtilities/ComparerUtils.cs | 121 ++++++++++++++++++ .../TestUtilities/IEnumerableExtensions.cs | 76 ++++++++++- Akade.IndexedSet.Tests/TestsWithoutIndices.cs | 25 ++-- Akade.IndexedSet.Tests/UniqueIndices.cs | 32 +++-- .../Utils/LevensteinDistanceTests.cs | 62 ++++++--- Akade.IndexedSet/DataStructures/BinaryHeap.cs | 4 +- Akade.IndexedSet/DataStructures/Lookup.cs | 4 +- .../DataStructures/SortedLookup.cs | 7 +- Akade.IndexedSet/DataStructures/SuffixTrie.cs | 4 +- Akade.IndexedSet/DataStructures/Trie.cs | 20 +-- Akade.IndexedSet/Extensions/SpanExtensions.cs | 21 +++ Akade.IndexedSet/IndexedSet.cs | 11 +- Akade.IndexedSet/IndexedSetBuilder.cs | 96 ++++++++------ Akade.IndexedSet/Indices/FullTextIndex.cs | 35 +++-- Akade.IndexedSet/Indices/MultiRangeIndex.cs | 4 +- Akade.IndexedSet/Indices/NonUniqueIndex.cs | 4 +- Akade.IndexedSet/Indices/PrefixIndex.cs | 4 +- Akade.IndexedSet/Indices/RangeIndex.cs | 4 +- Akade.IndexedSet/Indices/UniqueIndex.cs | 4 +- .../PublicAPI/PublicAPI.Shipped.txt | 40 +++--- .../PublicAPI/PublicAPI.Unshipped.txt | 3 + .../StringUtilities/CharComparer.cs | 51 ++++++++ Akade.IndexedSet/Utils/LevenshteinDistance.cs | 4 +- README.md | 7 +- 50 files changed, 954 insertions(+), 414 deletions(-) create mode 100644 Akade.IndexedSet.Tests/TestUtilities/ComparerUtils.cs create mode 100644 Akade.IndexedSet/Extensions/SpanExtensions.cs create mode 100644 Akade.IndexedSet/StringUtilities/CharComparer.cs diff --git a/.editorconfig b/.editorconfig index 62bcca3..6c50859 100644 --- a/.editorconfig +++ b/.editorconfig @@ -41,6 +41,8 @@ csharp_prefer_static_local_function = true:suggestion csharp_style_prefer_not_pattern = true:suggestion csharp_style_prefer_top_level_statements = true:silent csharp_style_conditional_delegate_call = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion [*.{cs,vb}] dotnet_style_prefer_auto_properties= true:suggestion dotnet_style_prefer_conditional_expression_over_assignment= false:suggestion @@ -124,4 +126,5 @@ dotnet_style_prefer_inferred_tuple_names = true:suggestion dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_compound_assignment = true:suggestion dotnet_style_prefer_simplified_interpolation = true:suggestion -dotnet_style_namespace_match_folder = true:suggestion \ No newline at end of file +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion \ No newline at end of file diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs index 5e4ad8e..5d3fb05 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs @@ -1,9 +1,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.CommonIndexTests; -internal abstract partial class BaseIndexTest +internal abstract partial class BaseIndexTest { - [TestMethod] + [BaseTestMethod] public void Contains_based_methods_should_throw_if_not_supported() { if (!SupportsContainsQueries) @@ -13,7 +13,7 @@ public void Contains_based_methods_should_throw_if_not_supported() } } - [TestMethod] + [BaseTestMethod] public void Contains_returns_empty_set_if_set_is_empty() { if (SupportsContainsQueries) @@ -23,7 +23,7 @@ public void Contains_returns_empty_set_if_set_is_empty() } } - [TestMethod] + [BaseTestMethod] public void Contains_returns_empty_set_if_no_matching_key_is_available() { if (SupportsContainsQueries) @@ -33,7 +33,7 @@ public void Contains_returns_empty_set_if_no_matching_key_is_available() } } - [TestMethod] + [BaseTestMethod] public void Contains_returns_matching_item() { if (SupportsContainsQueries) @@ -45,7 +45,7 @@ public void Contains_returns_matching_item() } } - [TestMethod] + [BaseTestMethod] public void Contains_returns_multiple_matching_item() { if (SupportsContainsQueries) @@ -61,7 +61,7 @@ public void Contains_returns_multiple_matching_item() } } - [TestMethod] + [BaseTestMethod] public void FuzzyContains_based_methods_should_throw_if_not_supported() { if (!SupportsContainsQueries) @@ -71,7 +71,7 @@ public void FuzzyContains_based_methods_should_throw_if_not_supported() } } - [TestMethod] + [BaseTestMethod] public void FuzzyContains_returns_empty_set_if_set_is_empty() { if (SupportsContainsQueries) @@ -81,7 +81,7 @@ public void FuzzyContains_returns_empty_set_if_set_is_empty() } } - [TestMethod] + [BaseTestMethod] public void FuzzyContains_returns_empty_set_if_no_matching_key_is_available() { if (SupportsContainsQueries) @@ -91,7 +91,7 @@ public void FuzzyContains_returns_empty_set_if_no_matching_key_is_available() } } - [TestMethod] + [BaseTestMethod] public void FuzzyContains_returns_matching_item() { if (SupportsContainsQueries) @@ -108,7 +108,7 @@ public void FuzzyContains_returns_matching_item() } } - [TestMethod] + [BaseTestMethod] public void FuzzyContains_returns_multiple_matching_item() { if (SupportsContainsQueries) diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs index 2030c10..b2276c6 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs @@ -1,9 +1,10 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Akade.IndexedSet.Tests.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.CommonIndexTests; -internal abstract partial class BaseIndexTest +internal abstract partial class BaseIndexTest { - [TestMethod] + [BaseTestMethod] public void Range_based_methods_should_throw_if_not_supported() { if (!SupportsRangeBasedQueries) @@ -23,7 +24,7 @@ public void Range_based_methods_should_throw_if_not_supported() } } - [TestMethod] + [BaseTestMethod] public void Range_returns_empty_result_if_not_present() { if (SupportsRangeBasedQueries) @@ -33,7 +34,7 @@ public void Range_returns_empty_result_if_not_present() } } - [TestMethod] + [BaseTestMethod] public void Range_returns_sorted_elements_respecting_boundary_parameters_for_unique_data() { if (SupportsRangeBasedQueries) @@ -42,7 +43,7 @@ public void Range_returns_sorted_elements_respecting_boundary_parameters_for_uni TElement[] data = GetUniqueData(); AddElements(data, index); - TElement[] orderedElements = [.. data.OrderBy(_keyAccessor)]; + TElement[] orderedElements = [.. data.OrderBy(_keyAccessor, ComparerUtils.GetComparer(_comparer))]; TIndexKey rangeStart = _keyAccessor(orderedElements[1]); TIndexKey rangeEnd = _keyAccessor(orderedElements[^2]); @@ -53,7 +54,7 @@ public void Range_returns_sorted_elements_respecting_boundary_parameters_for_uni } } - [TestMethod] + [BaseTestMethod] public void Comparison_queries_return_empty_result_if_no_element_is_present() { if (SupportsRangeBasedQueries) @@ -66,7 +67,7 @@ public void Comparison_queries_return_empty_result_if_no_element_is_present() } } - [TestMethod] + [BaseTestMethod] public void Comparison_queries_return_sorted_elements_respecting_boundary() { if (SupportsRangeBasedQueries) @@ -74,17 +75,17 @@ public void Comparison_queries_return_sorted_elements_respecting_boundary() TIndex index = CreateIndex(); TElement[] data = GetUniqueData(); AddElements(data, index); - TElement[] orderedElements = [.. data.OrderBy(_keyAccessor)]; + TElement[] orderedElements = [.. data.OrderBy(_keyAccessor, ComparerUtils.GetComparer(_comparer))]; - TIndexKey boundary = _keyAccessor(orderedElements[3]); - CollectionAssert.AreEqual(orderedElements[0..3], index.LessThan(boundary).ToArray()); - CollectionAssert.AreEqual(orderedElements[0..4], index.LessThanOrEqual(boundary).ToArray()); - CollectionAssert.AreEqual(orderedElements[4..], index.GreaterThan(boundary).ToArray()); - CollectionAssert.AreEqual(orderedElements[3..], index.GreaterThanOrEqual(boundary).ToArray()); + TIndexKey boundary = _keyAccessor(orderedElements[2]); + CollectionAssert.AreEqual(orderedElements[0..2], index.LessThan(boundary).ToArray()); + CollectionAssert.AreEqual(orderedElements[0..3], index.LessThanOrEqual(boundary).ToArray()); + CollectionAssert.AreEqual(orderedElements[3..], index.GreaterThan(boundary).ToArray()); + CollectionAssert.AreEqual(orderedElements[2..], index.GreaterThanOrEqual(boundary).ToArray()); } } - [TestMethod] + [BaseTestMethod] public void MaxMin_throw_if_the_set_is_empty() { if (SupportsRangeBasedQueries) @@ -95,7 +96,7 @@ public void MaxMin_throw_if_the_set_is_empty() } } - [TestMethod] + [BaseTestMethod] public void MaxMin_return_empty_enumerable_if_the_set_is_empty() { if (SupportsRangeBasedQueries) @@ -106,7 +107,7 @@ public void MaxMin_return_empty_enumerable_if_the_set_is_empty() } } - [TestMethod] + [BaseTestMethod] public void MaxMin_return_correct_key_and_values() { if (SupportsRangeBasedQueries) @@ -115,7 +116,7 @@ public void MaxMin_return_correct_key_and_values() TElement[] data = GetUniqueData(); AddElements(data, index); - TElement[] orderedElements = [.. data.OrderBy(_keyAccessor)]; + TElement[] orderedElements = [.. data.OrderBy(_keyAccessor, ComparerUtils.GetComparer(_comparer))]; Assert.AreEqual(_keyAccessor(orderedElements[0]), index.Min()); Assert.AreEqual(orderedElements[0], index.MinBy().Single()); @@ -125,7 +126,7 @@ public void MaxMin_return_correct_key_and_values() } } - [TestMethod] + [BaseTestMethod] public void OrderBy_returns_empty_values_with_no_data() { if (SupportsRangeBasedQueries) @@ -135,7 +136,7 @@ public void OrderBy_returns_empty_values_with_no_data() } } - [TestMethod] + [BaseTestMethod] public void OrderBy_throws_if_skip_value_is_too_large() { if (SupportsRangeBasedQueries) @@ -147,7 +148,7 @@ public void OrderBy_throws_if_skip_value_is_too_large() } } - [TestMethod] + [BaseTestMethod] public void OrderBy_returns_sorted_values() { if (SupportsRangeBasedQueries) @@ -155,7 +156,7 @@ public void OrderBy_returns_sorted_values() TIndex index = CreateIndex(); TElement[] data = GetUniqueData(); AddElements(data, index); - TElement[] orderedElements = [.. data.OrderBy(_keyAccessor)]; + TElement[] orderedElements = [.. data.OrderBy(_keyAccessor, ComparerUtils.GetComparer(_comparer))]; for (int i = 0; i < orderedElements.Length; i++) { @@ -164,7 +165,7 @@ public void OrderBy_returns_sorted_values() } } - [TestMethod] + [BaseTestMethod] public void OrderByDescending_returns_empty_values_with_no_data() { if (SupportsRangeBasedQueries) @@ -174,7 +175,7 @@ public void OrderByDescending_returns_empty_values_with_no_data() } } - [TestMethod] + [BaseTestMethod] public void OrderByDescending_throws_if_skip_value_is_too_large() { if (SupportsRangeBasedQueries) @@ -186,7 +187,7 @@ public void OrderByDescending_throws_if_skip_value_is_too_large() } } - [TestMethod] + [BaseTestMethod] public void OrderByDescending_returns_sorted_values() { if (SupportsRangeBasedQueries) @@ -194,7 +195,7 @@ public void OrderByDescending_returns_sorted_values() TIndex index = CreateIndex(); TElement[] data = GetUniqueData(); AddElements(data, index); - TElement[] orderedElements = [.. data.OrderByDescending(_keyAccessor)]; + TElement[] orderedElements = [.. data.OrderByDescending(_keyAccessor, ComparerUtils.GetComparer(_comparer))]; for (int i = 0; i < orderedElements.Length; i++) { diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs index 0dc79d4..a4473a2 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs @@ -1,10 +1,11 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Akade.IndexedSet.Tests.TestUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.CommonIndexTests; -internal abstract partial class BaseIndexTest +internal abstract partial class BaseIndexTest { - [TestMethod] + [BaseTestMethod] public void Single_should_return_matching_element() { TElement[] data = GetUniqueData(); @@ -12,47 +13,47 @@ public void Single_should_return_matching_element() Assert.AreEqual(data[0], index.Single(_keyAccessor(data[0]))); } - [TestMethod] + [BaseTestMethod] public void Single_should_throw_if_empty() { TIndex index = CreateIndex(); _ = Assert.ThrowsException(() => index.Single(GetNotExistingKey())); } - [TestMethod] + [BaseTestMethod] public void Single_should_throw_if_not_found() { TIndex index = CreateIndexWithData(GetUniqueData()); _ = Assert.ThrowsException(() => index.Single(GetNotExistingKey())); } - [TestMethod] + [BaseTestMethod] public void Single_should_throw_if_multiple_entries_are_found() { if (SupportsNonUniqueKeys) { TElement[] data = GetNonUniqueData(); TIndex index = CreateIndexWithData(data); - TIndexKey nonUniqueKey = data.GroupBy(_keyAccessor).Where(x => x.Count() > 1).First().Key; + TIndexKey nonUniqueKey = data.GroupByWithSortBasedFallback(_keyAccessor, _comparer).Where(x => x.Count() > 1).First().Key; _ = Assert.ThrowsException(() => index.Single(nonUniqueKey)); } } - [TestMethod] + [BaseTestMethod] public void TryGetSingle_should_return_false_if_empty() { TIndex index = CreateIndex(); Assert.IsFalse(index.TryGetSingle(GetNotExistingKey(), out _)); } - [TestMethod] + [BaseTestMethod] public void TryGetSingle_should_return_false_if_key_is_not_present() { TIndex index = CreateIndexWithData(GetUniqueData()); Assert.IsFalse(index.TryGetSingle(GetNotExistingKey(), out _)); } - [TestMethod] + [BaseTestMethod] public void TryGetSingle_should_return_true_for_matching_element() { TElement[] data = GetUniqueData(); @@ -61,14 +62,14 @@ public void TryGetSingle_should_return_true_for_matching_element() Assert.AreEqual(data[0], element); } - [TestMethod] + [BaseTestMethod] public void TryGetSingle_should_return_false_if_multiple_entries_are_found() { if (SupportsNonUniqueKeys) { TElement[] data = GetNonUniqueData(); TIndex index = CreateIndexWithData(data); - TIndexKey nonUniqueKey = data.GroupBy(_keyAccessor).Where(x => x.Count() > 1).First().Key; + TIndexKey nonUniqueKey = data.GroupByWithSortBasedFallback(_keyAccessor, _comparer).Where(x => x.Count() > 1).First().Key; Assert.IsFalse(index.TryGetSingle(nonUniqueKey, out _)); } } diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs index 712a67d..cdab460 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs @@ -1,9 +1,9 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.CommonIndexTests; -internal abstract partial class BaseIndexTest +internal abstract partial class BaseIndexTest { - [TestMethod] + [BaseTestMethod] public void StartsWith_based_methods_should_throw_if_not_supported() { if (!SupportsStartsWithQueries) @@ -13,7 +13,7 @@ public void StartsWith_based_methods_should_throw_if_not_supported() } } - [TestMethod] + [BaseTestMethod] public void StartsWith_returns_empty_set_if_set_is_empty() { if (SupportsStartsWithQueries) @@ -23,7 +23,7 @@ public void StartsWith_returns_empty_set_if_set_is_empty() } } - [TestMethod] + [BaseTestMethod] public void StartsWith_returns_empty_set_if_no_matching_key_is_available() { if (SupportsStartsWithQueries) @@ -34,7 +34,7 @@ public void StartsWith_returns_empty_set_if_no_matching_key_is_available() } } - [TestMethod] + [BaseTestMethod] public void StartsWith_returns_matching_item() { if (SupportsStartsWithQueries) @@ -46,7 +46,7 @@ public void StartsWith_returns_matching_item() } } - [TestMethod] + [BaseTestMethod] public void StartsWith_returns_multiple_matching_item() { if (SupportsStartsWithQueries) @@ -62,7 +62,7 @@ public void StartsWith_returns_multiple_matching_item() } } - [TestMethod] + [BaseTestMethod] public void FuzzyStartsWith_based_methods_should_throw_if_not_supported() { if (!SupportsStartsWithQueries) @@ -72,7 +72,7 @@ public void FuzzyStartsWith_based_methods_should_throw_if_not_supported() } } - [TestMethod] + [BaseTestMethod] public void FuzzyStartsWith_returns_empty_set_if_set_is_empty() { if (SupportsStartsWithQueries) @@ -82,7 +82,7 @@ public void FuzzyStartsWith_returns_empty_set_if_set_is_empty() } } - [TestMethod] + [BaseTestMethod] public void FuzzyStartsWith_returns_empty_set_if_no_matching_key_is_available() { if (SupportsStartsWithQueries) @@ -93,7 +93,7 @@ public void FuzzyStartsWith_returns_empty_set_if_no_matching_key_is_available() } } - [TestMethod] + [BaseTestMethod] public void FuzzyStartsWith_returns_matching_item() { if (SupportsStartsWithQueries) @@ -110,7 +110,7 @@ public void FuzzyStartsWith_returns_matching_item() } } - [TestMethod] + [BaseTestMethod] public void FuzzyStartsWith_returns_multiple_matching_item() { if (SupportsStartsWithQueries) diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Where.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Where.cs index 0bc4c89..047f01e 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Where.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Where.cs @@ -1,23 +1,23 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.CommonIndexTests; -internal abstract partial class BaseIndexTest +internal abstract partial class BaseIndexTest { - [TestMethod] + [BaseTestMethod] public void Where_should_return_empty_result_when_no_data_is_present() { TIndex index = CreateIndex(); Assert.IsFalse(index.Where(GetNotExistingKey()).Any()); } - [TestMethod] + [BaseTestMethod] public void Where_should_return_empty_result_when_key_is_not_present() { TIndex index = CreateIndexWithData(GetUniqueData()); Assert.IsFalse(index.Where(GetNotExistingKey()).Any()); } - [TestMethod] + [BaseTestMethod] public void Where_should_return_single_element_for_unique_key() { TElement[] data = GetUniqueData(); @@ -25,7 +25,7 @@ public void Where_should_return_single_element_for_unique_key() Assert.AreEqual(data[0], index.Where(_keyAccessor(data[0])).Single()); } - [TestMethod] + [BaseTestMethod] public void Where_should_return_multiple_element_for_unique_key() { if (SupportsNonUniqueKeys) diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs index 4035c16..3caf6b4 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs @@ -3,11 +3,13 @@ using System.Reflection; namespace Akade.IndexedSet.Tests.CommonIndexTests; -internal abstract partial class BaseIndexTest(Func keyAccessor) +internal abstract partial class BaseIndexTest(Func keyAccessor, TComparer comparer) where TIndex : TypedIndex where TIndexKey : notnull + where TComparer : notnull { private readonly Func _keyAccessor = keyAccessor; + protected readonly TComparer _comparer = comparer; protected abstract TElement[] GetUniqueData(); @@ -38,7 +40,7 @@ protected void AddElements(TElement[] elements, TIndex index) index.AddRange(elements.Select(element => KeyValuePair.Create(_keyAccessor(element), element))); } - [TestMethod] + [BaseTestMethod] public void Adding_unique_data_should_throw_if_nonunique_keys_are_not_supported() { if (!SupportsNonUniqueKeys) @@ -48,20 +50,33 @@ public void Adding_unique_data_should_throw_if_nonunique_keys_are_not_supported( } } -public static class BaseIndexTest +internal interface IBaseIndexTest + where TComparer : notnull { - public static IEnumerable GetTestMethods() + static abstract IEnumerable GetComparers(); +} + +[AttributeUsage(AttributeTargets.Method)] +internal class BaseTestMethodAttribute : Attribute { } + +internal static class BaseIndexTest +{ + public static IEnumerable GetTestMethods() + where T : IBaseIndexTest + where TComparer : notnull { return typeof(T).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) .Where(m => m.Name != "Test") - .Where(m => m.GetCustomAttribute(typeof(TestMethodAttribute)) is not null) - .Select(m => new object[] { m.Name }) + .Where(m => m.GetCustomAttribute() is not null) + .SelectMany(method => T.GetComparers().Select(comparer => new object[] { method.Name, comparer })) .ToArray(); } - public static void RunTest(string method) + public static void RunTest(string method, object comparer) + where T : IBaseIndexTest + where TComparer : notnull { - T testClass = Activator.CreateInstance(); + var testClass = (T)(Activator.CreateInstance(typeof(T), comparer) ?? throw new InvalidOperationException()); MethodInfo methodInfo = typeof(T).GetMethod(method, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) ?? throw new ArgumentOutOfRangeException(nameof(method)); diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.FullText.cs b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.FullText.cs index 4b98a66..0330cf5 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.FullText.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.FullText.cs @@ -1,17 +1,15 @@ using Akade.IndexedSet.Indices; +using Akade.IndexedSet.StringUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.CommonIndexTests; public partial class CommonIndexTests { - internal class FullTextIndexTest : BaseIndexTest, FullTextIndex>> + internal class FullTextIndexTest(IEqualityComparer comparer) + : BaseIndexTest, FullTextIndex>, IEqualityComparer>(x => x.Value, comparer) + , IBaseIndexTest> { - public FullTextIndexTest() : base(x => x.Value) - { - - } - protected override bool SupportsNonUniqueKeys => true; protected override bool SupportsRangeBasedQueries => false; @@ -20,7 +18,7 @@ public FullTextIndexTest() : base(x => x.Value) protected override FullTextIndex> CreateIndex() { - return new FullTextIndex>(x => x.Value, "Test"); + return new FullTextIndex>(x => x.Value, _comparer, "Test"); } protected override string GetNotExistingKey() @@ -54,17 +52,22 @@ protected override Container[] GetUniqueData() new("Possum"), ]; } + + public static IEnumerable> GetComparers() + { + return [EqualityComparer.Default, CharEqualityComparer.OrdinalIgnoreCase]; + } } [DataTestMethod] [DynamicData(nameof(GetFullTextIndexTestMethods), DynamicDataSourceType.Method)] - public void FullTextIndex(string method) + public void FullTextIndex(string method, object comparer) { - BaseIndexTest.RunTest(method); + BaseIndexTest.RunTest>(method, comparer); } public static IEnumerable GetFullTextIndexTestMethods() { - return BaseIndexTest.GetTestMethods(); + return BaseIndexTest.GetTestMethods>(); } } diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.MultiRange.cs b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.MultiRange.cs index b0f469f..4d90307 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.MultiRange.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.MultiRange.cs @@ -5,20 +5,22 @@ namespace Akade.IndexedSet.Tests.CommonIndexTests; public partial class CommonIndexTests { - internal class MultiRangeIndexTest : BaseIndexTest, MultiRangeIndex, int>> + internal class MultiRangeIndexTest(IComparer comparer) + : BaseIndexTest, MultiRangeIndex, int>, IComparer>(x => x.Value, comparer) + , IBaseIndexTest> { - public MultiRangeIndexTest() : base(x => x.Value) - { - - } - protected override bool SupportsNonUniqueKeys => true; protected override bool SupportsRangeBasedQueries => true; + public static IEnumerable> GetComparers() + { + return [Comparer.Default]; + } + protected override MultiRangeIndex, int> CreateIndex() { - return new MultiRangeIndex, int>("Test"); + return new MultiRangeIndex, int>(_comparer, "Test"); } protected override Container[] GetNonUniqueData() @@ -50,13 +52,13 @@ protected override Container[] GetUniqueData() [DataTestMethod] [DynamicData(nameof(GetMultiRangeIndexTestMethods), DynamicDataSourceType.Method)] - public void MultiRangeIndex(string method) + public void MultiRangeIndex(string method, object comparer) { - BaseIndexTest.RunTest(method); + BaseIndexTest.RunTest>(method, comparer); } public static IEnumerable GetMultiRangeIndexTestMethods() { - return BaseIndexTest.GetTestMethods(); + return BaseIndexTest.GetTestMethods>(); } } \ No newline at end of file diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.NonUnique.cs b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.NonUnique.cs index 19fe286..85f001f 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.NonUnique.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.NonUnique.cs @@ -5,18 +5,20 @@ namespace Akade.IndexedSet.Tests.CommonIndexTests; public partial class CommonIndexTests { - internal class NonUniqueIndexTest : BaseIndexTest, NonUniqueIndex, int>> + internal class NonUniqueIndexTest(IEqualityComparer comparer) + : BaseIndexTest, NonUniqueIndex, int>, IEqualityComparer>(x => x.Value, comparer) + , IBaseIndexTest> { - public NonUniqueIndexTest() : base(x => x.Value) - { + protected override bool SupportsNonUniqueKeys => true; + public static IEnumerable> GetComparers() + { + return [EqualityComparer.Default]; } - protected override bool SupportsNonUniqueKeys => true; - protected override NonUniqueIndex, int> CreateIndex() { - return new NonUniqueIndex, int>("x => x.Value"); + return new NonUniqueIndex, int>(_comparer, "x => x.Value"); } protected override Container[] GetNonUniqueData() @@ -48,13 +50,13 @@ protected override Container[] GetUniqueData() [DataTestMethod] [DynamicData(nameof(GetNonUniqueIndexTestMethods), DynamicDataSourceType.Method)] - public void NonUniqueIndex(string method) + public void NonUniqueIndex(string method, object comparer) { - BaseIndexTest.RunTest(method); + BaseIndexTest.RunTest>(method, comparer); } public static IEnumerable GetNonUniqueIndexTestMethods() { - return BaseIndexTest.GetTestMethods(); + return BaseIndexTest.GetTestMethods>(); } } \ No newline at end of file diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Prefix.cs b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Prefix.cs index dfb95d7..00b4c95 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Prefix.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Prefix.cs @@ -1,25 +1,30 @@ using Akade.IndexedSet.Indices; +using Akade.IndexedSet.StringUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.CommonIndexTests; public partial class CommonIndexTests { - internal class PrefixIndexTest : BaseIndexTest, PrefixIndex>> - { - public PrefixIndexTest() : base(x => x.Value) - { + internal class PrefixIndexTest(IEqualityComparer comparer) + : BaseIndexTest, PrefixIndex>, IEqualityComparer>(x => x.Value, comparer) + , IBaseIndexTest> - } + { protected override bool SupportsNonUniqueKeys => true; protected override bool SupportsRangeBasedQueries => false; protected override bool SupportsStartsWithQueries => true; + public static IEnumerable> GetComparers() + { + return [EqualityComparer.Default, CharEqualityComparer.OrdinalIgnoreCase]; + } + protected override PrefixIndex> CreateIndex() { - return new PrefixIndex>("Test"); + return new PrefixIndex>(_comparer, "Test"); } protected override string GetNotExistingKey() @@ -57,13 +62,13 @@ protected override Container[] GetUniqueData() [DataTestMethod] [DynamicData(nameof(GetPrefixIndexTestMethods), DynamicDataSourceType.Method)] - public void PrefixIndex(string method) + public void PrefixIndex(string method, object comparer) { - BaseIndexTest.RunTest(method); + BaseIndexTest.RunTest>(method, comparer); } public static IEnumerable GetPrefixIndexTestMethods() { - return BaseIndexTest.GetTestMethods(); + return BaseIndexTest.GetTestMethods>(); } } \ No newline at end of file diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Range.cs b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Range.cs index b75c00b..2b83d6c 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Range.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Range.cs @@ -5,58 +5,89 @@ namespace Akade.IndexedSet.Tests.CommonIndexTests; public partial class CommonIndexTests { - internal class RangeIndexTest : BaseIndexTest, RangeIndex, int>> + internal class RangeIndexTest(IComparer comparer) + : BaseIndexTest, RangeIndex, int>, IComparer>(x => x.Value, comparer) + , IBaseIndexTest> { - public RangeIndexTest() : base(x => x.Value) - { - - } - protected override bool SupportsNonUniqueKeys => true; protected override bool SupportsRangeBasedQueries => true; + public static IEnumerable> GetComparers() + { + return [Comparer.Default, Comparer.Create((a, b) => (a % 5).CompareTo(b % 5))]; + } + protected override RangeIndex, int> CreateIndex() { - return new RangeIndex, int>("Test"); + return new RangeIndex, int>(_comparer, "Test"); } protected override Container[] GetNonUniqueData() { - return - [ - new(11), - new(11), - new(12), - new(12), - new(13), - new(13), - ]; + if(_comparer == Comparer.Default) + { + return + [ + new(11), + new(11), + new(12), + new(12), + new(13), + new(13), + ]; + } + else + { + return + [ + new(11), // 1 + new(21), // 1 + new(12), // 2 + new(17), // 2 + new(13), // 3 + new(23), // 3 + ]; + } } protected override Container[] GetUniqueData() { - return - [ - new(1), - new(2), - new(3), - new(4), - new(5), - new(6), - ]; + if (_comparer == Comparer.Default) + { + return + [ + new(1), + new(2), + new(3), + new(4), + new(5), + new(6), + ]; + } + else + { + // 1-4 under modulo 5 + return + [ + new(1), + new(12), + new(3), + new(24), + ]; + } } } [DataTestMethod] [DynamicData(nameof(GetRangeIndexTestMethods), DynamicDataSourceType.Method)] - public void RangeIndex(string method) + public void RangeIndex(string method, object comparer) { - BaseIndexTest.RunTest(method); + BaseIndexTest.RunTest>(method, comparer); } public static IEnumerable GetRangeIndexTestMethods() { - return BaseIndexTest.GetTestMethods(); + return BaseIndexTest.GetTestMethods>(); } } \ No newline at end of file diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Unique.cs b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Unique.cs index 792c28a..bf1a268 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Unique.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/CommonIndexTests.Unique.cs @@ -6,18 +6,20 @@ namespace Akade.IndexedSet.Tests.CommonIndexTests; [TestClass] public partial class CommonIndexTests { - internal class UniqueIndexTest : BaseIndexTest, UniqueIndex, int>> + internal class UniqueIndexTest(IEqualityComparer comparer) + : BaseIndexTest, UniqueIndex, int>, IEqualityComparer>(x => x.Value, comparer) + , IBaseIndexTest> { - public UniqueIndexTest() : base(x => x.Value) - { + protected override bool SupportsNonUniqueKeys => false; + public static IEnumerable> GetComparers() + { + return [EqualityComparer.Default]; } - protected override bool SupportsNonUniqueKeys => false; - protected override UniqueIndex, int> CreateIndex() { - return new UniqueIndex, int>("x => x.Value"); + return new UniqueIndex, int>(_comparer, "x => x.Value"); } protected override Container[] GetNonUniqueData() @@ -49,13 +51,13 @@ protected override Container[] GetUniqueData() [DataTestMethod] [DynamicData(nameof(GetUniqueIndexTestMethods), DynamicDataSourceType.Method)] - public void UniqueIndex(string method) + public void UniqueIndex(string method, object comparer) { - BaseIndexTest.RunTest(method); + BaseIndexTest.RunTest>(method, comparer); } public static IEnumerable GetUniqueIndexTestMethods() { - return BaseIndexTest.GetTestMethods(); + return BaseIndexTest.GetTestMethods>(); } } \ No newline at end of file diff --git a/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs b/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs index f2dcda0..ccb1495 100644 --- a/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs +++ b/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs @@ -13,11 +13,11 @@ public class BinaryHeapTests [TestInitialize] public void TestInitialize() { - _heap = []; + _heap = new(Comparer.Default); } [TestMethod] - public void adding_random_values_including_duplicates_gives_sorted_list_and_correct_insertion_positions() + public void Adding_random_values_including_duplicates_gives_sorted_list_and_correct_insertion_positions() { Assert.AreEqual(0, _heap.Add(2)); // 2 Assert.AreEqual(1, _heap.Add(3)); // 2 3 @@ -30,7 +30,7 @@ public void adding_random_values_including_duplicates_gives_sorted_list_and_corr } [TestMethod] - public void removing_random_values_from_heap_preserves_sorted_list_and_reports_correct_removal_positions() + public void Removing_random_values_from_heap_preserves_sorted_list_and_reports_correct_removal_positions() { _heap.AddRange([1, 2, 4, 4, 6, 6, 8, 9]); @@ -42,7 +42,7 @@ public void removing_random_values_from_heap_preserves_sorted_list_and_reports_c } [TestMethod] - public void querying_by_single_values_returns_correct_ranges() + public void Querying_by_single_values_returns_correct_ranges() { _heap.AddRange([1, 2, 4, 4, 4, 4, 6, 6, 8, 9]); @@ -57,13 +57,13 @@ public void querying_by_single_values_returns_correct_ranges() [TestMethod] - public void querying_by_single_values_returns_correct_position_with_zero_length_when_empty() + public void Querying_by_single_values_returns_correct_position_with_zero_length_when_empty() { Assert.AreEqual(0..0, _heap.GetRange(2)); } [TestMethod] - public void querying_by_single_values_returns_correct_position_with_zero_length_when_no_matching_value_is_found() + public void Querying_by_single_values_returns_correct_position_with_zero_length_when_no_matching_value_is_found() { _ = _heap.Add(5); _ = _heap.Add(8); @@ -71,13 +71,13 @@ public void querying_by_single_values_returns_correct_position_with_zero_length_ } [TestMethod] - public void querying_by_range_returns_empty_range_when_empty() + public void Querying_by_range_returns_empty_range_when_empty() { Assert.AreEqual(0..0, _heap.GetRange(3, 7, inclusiveStart: true, inclusiveEnd: true)); } [TestMethod] - public void querying_by_range_values_returns_correct_position_with_zero_length_when_no_matching_value_is_found() + public void Querying_by_range_values_returns_correct_position_with_zero_length_when_no_matching_value_is_found() { _ = _heap.Add(5); _ = _heap.Add(8); @@ -85,7 +85,7 @@ public void querying_by_range_values_returns_correct_position_with_zero_length_w } [TestMethod] - public void querying_by_range_returns_correct_ranges() + public void Querying_by_range_returns_correct_ranges() { // 0 1 2 3 4 5 6 7 8 9 _heap.AddRange([1, 2, 4, 4, 4, 4, 6, 6, 8, 9]); @@ -102,7 +102,7 @@ public void querying_by_range_returns_correct_ranges() } [TestMethod] - public void querying_by_range_returns_correct_ranges_respecting_inclusive_or_exclusive_boundaries_when_boundaries_are_elements() + public void Querying_by_range_returns_correct_ranges_respecting_inclusive_or_exclusive_boundaries_when_boundaries_are_elements() { // 0 1 2 3 4 5 6 7 8 9 10 _heap.AddRange([1, 2, 4, 4, 4, 4, 5, 6, 6, 8, 9]); @@ -118,7 +118,7 @@ public void querying_by_range_returns_correct_ranges_respecting_inclusive_or_exc } [TestMethod] - public void querying_by_range_returns_correct_ranges_respecting_inclusive_or_exclusive_boundaries_when_boundaries_are_not_elements() + public void Querying_by_range_returns_correct_ranges_respecting_inclusive_or_exclusive_boundaries_when_boundaries_are_not_elements() { // 0 1 2 3 4 5 6 7 8 9 10 _heap.AddRange([1, 2, 4, 4, 4, 4, 5, 6, 6, 8, 9]); diff --git a/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs b/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs index 1d45955..59b2dba 100644 --- a/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs +++ b/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs @@ -13,7 +13,7 @@ public class SuffixTrieTests [TestInitialize] public void TestInitializer() { - _trie = new(); + _trie = new(EqualityComparer.Default); _ = AddToStringTrie(_trie, "Tiger"); _ = AddToStringTrie(_trie, "Tarantula"); _ = AddToStringTrie(_trie, "Penguin"); @@ -24,7 +24,7 @@ public void TestInitializer() } [TestMethod] - public void querying_common_prefixes_return_correct_elements() + public void Querying_common_prefixes_return_correct_elements() { CollectionAssert.AreEquivalent(new string[] { "Tiger", "Tarantula" }, _trie.GetAll("T").ToArray()); CollectionAssert.AreEquivalent(new string[] { "Penguin", "Panther", "Pangolin", "Parrot" }, _trie.GetAll("P").ToArray()); @@ -34,14 +34,14 @@ public void querying_common_prefixes_return_correct_elements() } [TestMethod] - public void querying_common_infixes_return_correct_elements() + public void Querying_common_infixes_return_correct_elements() { CollectionAssert.AreEquivalent(new string[] { "Panther", "Pangolin", "Tarantula" }, _trie.GetAll("an").ToArray()); CollectionAssert.AreEquivalent(new string[] { "Chihuahua" }, _trie.GetAll("hua").ToArray()); } [TestMethod] - public void querying_common_suffixes_return_correct_elements() + public void Querying_common_suffixes_return_correct_elements() { CollectionAssert.AreEquivalent(new string[] { "Panther", "Tiger" }, _trie.GetAll("er").ToArray()); CollectionAssert.AreEquivalent(new string[] { "Chihuahua" }, _trie.GetAll("hua").ToArray()); @@ -49,18 +49,18 @@ public void querying_common_suffixes_return_correct_elements() } [TestMethod] - public void adding_the_same_element_return_false() + public void Adding_the_same_element_return_false() { - SuffixTrie trie = new(); + SuffixTrie trie = new(EqualityComparer.Default); Assert.IsTrue(AddToStringTrie(trie, "Cat")); Assert.IsFalse(AddToStringTrie(trie, "Cat")); } [TestMethod] - public void contains_returns_correct_value_when_adding_elements() + public void Contains_returns_correct_value_when_adding_elements() { - SuffixTrie trie = new(); + SuffixTrie trie = new(EqualityComparer.Default); Assert.IsFalse(ContainsInStringTrie(trie, "Cat")); Assert.IsTrue(AddToStringTrie(trie, "Cat")); @@ -68,9 +68,9 @@ public void contains_returns_correct_value_when_adding_elements() } [TestMethod] - public void contains_returns_correct_value_when_removing_elements() + public void Contains_returns_correct_value_when_removing_elements() { - SuffixTrie trie = new(); + SuffixTrie trie = new(EqualityComparer.Default); _ = AddToStringTrie(trie, "Cat"); Assert.IsTrue(ContainsInStringTrie(trie, "Cat")); @@ -79,21 +79,21 @@ public void contains_returns_correct_value_when_removing_elements() } [TestMethod] - public void removing_returns_false_if_the_element_is_not_present() + public void Removing_returns_false_if_the_element_is_not_present() { - SuffixTrie trie = new(); + SuffixTrie trie = new(EqualityComparer.Default); Assert.IsFalse(RemoveFromStringTrie(trie, "Cat")); } [TestMethod] - public void exact_fuzzy_search_with_single_result() + public void Exact_fuzzy_search_with_single_result() { IEnumerable result = _trie.FuzzySearch("rantul", 1, true); Assert.AreEqual("Tarantula", result.Single()); } [TestMethod] - public void exact_fuzzy_search_without_results() + public void Exact_fuzzy_search_without_results() { IEnumerable result = _trie.FuzzySearch("Panner", 1, true); Assert.IsFalse(result.Any()); @@ -101,7 +101,7 @@ public void exact_fuzzy_search_without_results() [TestMethod] - public void inexact_fuzzy_search_and_single_result() + public void Inexact_fuzzy_search_and_single_result() { IEnumerable result = _trie.FuzzySearch("Pangolin", 2, false); CollectionAssert.AreEquivalent(new[] { "Pangolin" }, result.ToArray()); @@ -109,21 +109,21 @@ public void inexact_fuzzy_search_and_single_result() [TestMethod] - public void inexact_fuzzy_search_and_multiple_result() + public void Inexact_fuzzy_search_and_multiple_result() { IEnumerable result = _trie.FuzzySearch("Pan", 2, false); CollectionAssert.AreEquivalent(new[] { "Penguin", "Panther", "Pangolin", "Parrot", "Tarantula", "Chihuahua" }, result.ToArray()); } [TestMethod] - public void inexact_fuzzy_search_without_result() + public void Inexact_fuzzy_search_without_result() { IEnumerable result = _trie.FuzzySearch("Non", 1, false); Assert.IsFalse(result.Any()); } [TestMethod] - public void inexact_fuzzy_search_and_multiple_result_with_first_character_changed() + public void Inexact_fuzzy_search_and_multiple_result_with_first_character_changed() { IEnumerable result = _trie.FuzzySearch("Zan", 1, false); CollectionAssert.AreEquivalent(new[] { "Panther", "Pangolin", "Tarantula" }, result.ToArray()); diff --git a/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs b/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs index 191d947..632f6d7 100644 --- a/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs +++ b/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs @@ -1,4 +1,5 @@ using Akade.IndexedSet.DataStructures; +using Akade.IndexedSet.StringUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Diagnostics.CodeAnalysis; @@ -12,7 +13,7 @@ public class TrieTests private static Trie GetAnimalTrie() { - Trie trie = new(); + Trie trie = new(EqualityComparer.Default); _ = AddToStringTrie(trie, "Tiger"); _ = AddToStringTrie(trie, "Tarantula"); @@ -24,7 +25,7 @@ private static Trie GetAnimalTrie() } [TestMethod] - public void querying_common_prefixes_return_correct_elements() + public void Querying_common_prefixes_return_correct_elements() { CollectionAssert.AreEquivalent(new string[] { "Tiger", "Tarantula" }, _trie.GetAll("T").ToArray()); CollectionAssert.AreEquivalent(new string[] { "Penguin", "Panther", "Pangolin", "Parrot" }, _trie.GetAll("P").ToArray()); @@ -34,18 +35,18 @@ public void querying_common_prefixes_return_correct_elements() } [TestMethod] - public void adding_the_same_element_returns_false() + public void Adding_the_same_element_returns_false() { - Trie trie = new(); + Trie trie = new(EqualityComparer.Default); Assert.IsTrue(AddToStringTrie(trie, "Cat")); Assert.IsFalse(AddToStringTrie(trie, "Cat")); } [TestMethod] - public void contains_returns_correct_value_when_adding_elements() + public void Contains_returns_correct_value_when_adding_elements() { - Trie trie = new(); + Trie trie = new(EqualityComparer.Default); Assert.IsFalse(ContainsInStringTrie(trie, "Cat")); Assert.IsTrue(AddToStringTrie(trie, "Cat")); @@ -53,9 +54,9 @@ public void contains_returns_correct_value_when_adding_elements() } [TestMethod] - public void contains_returns_correct_value_when_removing_elements() + public void Contains_returns_correct_value_when_removing_elements() { - Trie trie = new(); + Trie trie = new(EqualityComparer.Default); _ = AddToStringTrie(trie, "Cat"); Assert.IsTrue(ContainsInStringTrie(trie, "Cat")); @@ -64,21 +65,21 @@ public void contains_returns_correct_value_when_removing_elements() } [TestMethod] - public void removing_returns_false_if_the_element_is_not_present() + public void Removing_returns_false_if_the_element_is_not_present() { - Trie trie = new(); + Trie trie = new(EqualityComparer.Default); Assert.IsFalse(RemoveFromStringTrie(trie, "Cat")); } [TestMethod] - public void exact_fuzzy_search_with_single_result() + public void Exact_fuzzy_search_with_single_result() { IEnumerable result = _trie.FuzzySearch("Panter", 1, true); Assert.AreEqual("Panther", result.Single()); } [TestMethod] - public void exact_fuzzy_search_without_results() + public void Exact_fuzzy_search_without_results() { IEnumerable result = _trie.FuzzySearch("Panner", 1, true); Assert.IsFalse(result.Any()); @@ -86,33 +87,46 @@ public void exact_fuzzy_search_without_results() [TestMethod] - public void inexact_fuzzy_search_and_single_result() + public void Inexact_fuzzy_search_and_single_result() { IEnumerable result = _trie.FuzzySearch("Pangolin", 2, false); CollectionAssert.AreEquivalent(new[] { "Pangolin" }, result.ToArray()); } [TestMethod] - public void inexact_fuzzy_search_and_multiple_result() + public void Inexact_fuzzy_search_and_multiple_result() { IEnumerable result = _trie.FuzzySearch("Pan", 2, false); CollectionAssert.AreEquivalent(new[] { "Penguin", "Panther", "Pangolin", "Parrot", "Tarantula" }, result.ToArray()); } [TestMethod] - public void inexact_fuzzy_search_without_result() + public void Inexact_fuzzy_search_without_result() { IEnumerable result = _trie.FuzzySearch("Non", 1, false); Assert.IsFalse(result.Any()); } [TestMethod] - public void inexact_fuzzy_search_and_multiple_result_with_first_character_changed() + public void Inexact_fuzzy_search_and_multiple_result_with_first_character_changed() { IEnumerable result = _trie.FuzzySearch("Zan", 1, false); CollectionAssert.AreEquivalent(new[] { "Panther", "Pangolin" }, result.ToArray()); } + [TestMethod] + public void Case_insensitive_exact_match() + { + // the keys are the same as the values, i.e. the different capitalizations are stored under the same key as different values + Trie trie = new(CharEqualityComparer.OrdinalIgnoreCase); + _ = AddToStringTrie(trie, "cat"); + _ = AddToStringTrie(trie, "Cat"); + _ = AddToStringTrie(trie, "CAT"); + + IEnumerable result = trie.Get("cat"); + CollectionAssert.AreEquivalent(new[] { "cat", "Cat", "CAT" }, result.ToArray()); + } + private static bool AddToStringTrie(Trie stringTrie, string value) { return stringTrie.Add(value, value); diff --git a/Akade.IndexedSet.Tests/FullTextIndices.cs b/Akade.IndexedSet.Tests/FullTextIndices.cs index 0a2c260..3a5f175 100644 --- a/Akade.IndexedSet.Tests/FullTextIndices.cs +++ b/Akade.IndexedSet.Tests/FullTextIndices.cs @@ -1,3 +1,4 @@ +using Akade.IndexedSet.StringUtilities; using Akade.IndexedSet.Tests.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Diagnostics.CodeAnalysis; @@ -10,6 +11,7 @@ public class FullTextIndices private record class Animal(string Name, string Category); private IndexedSet _indexedSet = null!; + private Animal[] _allAnimals = null!; private readonly Animal _bonobo = new("Bonobo", "Mammal"); private readonly Animal _booby = new("Booby", "Bird"); private readonly Animal _boomslang = new("Boomslang", "Reptile"); @@ -25,7 +27,7 @@ private record class Animal(string Name, string Category); [TestInitialize] public void Init() { - var data = new Animal[] { + _allAnimals = [ _bonobo, _booby, _boomslang, @@ -37,56 +39,55 @@ public void Init() _panther, _pangolin, _parrot, - }; - _indexedSet = data.ToIndexedSet() - .WithFullTextIndex(x => x.Category) - .WithFullTextIndex(x => x.Name) - .Build(); + ]; + _indexedSet = _allAnimals.ToIndexedSet() + .WithFullTextIndex(x => x.Category) + .WithFullTextIndex(x => x.Name) + .Build(); } [TestMethod] - public void single_item_retrieval_works() + public void Single_item_retrieval_works() { _indexedSet.AssertSingleItem(x => x.Category, _boomslang); _indexedSet.AssertSingleItem(x => x.Category, _tarantula); } [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void single_item_retrieval_throws_exception_if_there_is_more_than_one_result() + public void Single_item_retrieval_throws_exception_if_there_is_more_than_one_result() { - _indexedSet.AssertSingleItem(x => x.Category, _bonobo); + _ = Assert.ThrowsException(() => _indexedSet.AssertSingleItem(x => x.Category, _bonobo)); } [TestMethod] - public void multi_item_retrieval_works() + public void Multi_item_retrieval_works() { _indexedSet.AssertMultipleItems(x => x.Category, expectedElements: [_bonobo, _borador, _tiger, _tapir, _panther, _pangolin]); _indexedSet.AssertMultipleItems(x => x.Category, expectedElements: [_booby, _penguin, _parrot]); } [TestMethod] - public void search_via_starts_with() + public void Search_via_starts_with() { CollectionAssert.AreEquivalent(new[] { _booby, _boomslang }, _indexedSet.StartsWith(x => x.Name, "Boo").ToArray()); CollectionAssert.AreEquivalent(new[] { _panther, _pangolin }, _indexedSet.StartsWith(x => x.Name, "Pan").ToArray()); } [TestMethod] - public void search_via_fuzzy_starts_with() + public void Search_via_fuzzy_starts_with() { CollectionAssert.AreEquivalent(new[] { _bonobo, _booby, _boomslang, _borador }, _indexedSet.FuzzyStartsWith(x => x.Name, "Boo", 1).ToArray()); CollectionAssert.AreEquivalent(new[] { _penguin, _parrot, _panther, _pangolin }, _indexedSet.FuzzyStartsWith(x => x.Name, "Pan", 1).ToArray()); } [TestMethod] - public void search_via_contains() + public void Search_via_contains() { CollectionAssert.AreEquivalent(new[] { _boomslang, _tarantula, _panther, _pangolin }, _indexedSet.Contains(x => x.Name, "an").ToArray()); } [TestMethod] - public void search_via_fuzzy_contains() + public void Search_via_fuzzy_contains() { Animal[] actual = _indexedSet.FuzzyContains(x => x.Name, "Pan", 1).ToArray(); CollectionAssert.AreEquivalent(new[] { _boomslang, _tarantula, _penguin, _parrot, _panther, _pangolin }, actual); @@ -138,4 +139,30 @@ public void Retrieval_via_multi_key_retrieves_correct_items() CollectionAssert.AreEquivalent(_indexedSet.StartsWith(Multikeys, "Bir").ToArray(), new[] { _booby, _penguin, _parrot }); CollectionAssert.AreEquivalent(_indexedSet.FuzzyStartsWith(Multikeys, "Lir", 1).ToArray(), new[] { _booby, _penguin, _parrot }); } + + [TestMethod] + public void Case_insensitive_matching() + { + static string CategoryCaseInsensitive(Animal a) => a.Category; + + _indexedSet = _allAnimals.ToIndexedSet() + .WithFullTextIndex(CategoryCaseInsensitive, CharEqualityComparer.OrdinalIgnoreCase) + .Build(); + + Animal[] actual = _indexedSet.StartsWith(CategoryCaseInsensitive, "MAMM").ToArray(); + CollectionAssert.AreEquivalent(_allAnimals.Where(x => x.Category == "Mammal").ToArray(), actual); + } + + [TestMethod] + public void Case_insensitive_fuzzy_matching() + { + static string NameCaseInsensitive(Animal a) => a.Name; + + _indexedSet = _allAnimals.ToIndexedSet() + .WithFullTextIndex(NameCaseInsensitive, CharEqualityComparer.OrdinalIgnoreCase) + .Build(); + + Animal[] actual = _indexedSet.FuzzyStartsWith(NameCaseInsensitive, "PAN", 1).ToArray(); + CollectionAssert.AreEquivalent(new[] { _penguin, _parrot, _panther, _pangolin }, actual); + } } diff --git a/Akade.IndexedSet.Tests/GeneralTests.cs b/Akade.IndexedSet.Tests/GeneralTests.cs index f1f9cd1..7f49541 100644 --- a/Akade.IndexedSet.Tests/GeneralTests.cs +++ b/Akade.IndexedSet.Tests/GeneralTests.cs @@ -27,7 +27,7 @@ public void Init() } [TestMethod] - public void clear_removes_all_elements() + public void Clear_removes_all_elements() { _indexedSet.Clear(); Assert.AreEqual(0, _indexedSet.Count); diff --git a/Akade.IndexedSet.Tests/MultiValueIndices.cs b/Akade.IndexedSet.Tests/MultiValueIndices.cs index 828586e..1b3a781 100644 --- a/Akade.IndexedSet.Tests/MultiValueIndices.cs +++ b/Akade.IndexedSet.Tests/MultiValueIndices.cs @@ -17,11 +17,17 @@ public void Init() { _indexedSet = new[] { _a, _b, _c, _d }.ToIndexedSet(x => x.PrimaryKey) .WithIndex(x => x.IntList) + .WithIndex(IntListWithComparer, EqualityComparer.Create((a, b) => a / 2 == b / 2, x => x / 2)) .Build(); } + private static IEnumerable IntListWithComparer(DenormalizedTestData data) + { + return data.IntList; + } + [TestMethod] - public void contains_queries_return_correct_results() + public void Contains_queries_return_correct_results() { CollectionAssert.AreEquivalent(new[] { _a, _d }, _indexedSet.Where(x => x.IntList, contains: 1).ToArray()); CollectionAssert.AreEquivalent(new[] { _a, _b, _d }, _indexedSet.Where(x => x.IntList, contains: 2).ToArray()); @@ -30,7 +36,7 @@ public void contains_queries_return_correct_results() } [TestMethod] - public void single_queries_return_correct_results() + public void Single_queries_return_correct_results() { Assert.AreEqual(_a, _indexedSet.Single(x => x.IntList, 4)); } @@ -56,6 +62,34 @@ public void TryGetSingle() Assert.IsNull(test3); } + [TestMethod] + public void Custom_comparer_where() + { + // a: [1, 2, 3, 4] => [0, 1, 1, 2] + // b: [2, 3] => [1, 1] + // c: [3] => [1] + // d: [1, 2, 3] => [0, 1, 1] + + CollectionAssert.AreEquivalent(new[] { _a, _d }, _indexedSet.Where(IntListWithComparer, contains: 0).ToArray()); // 0 + CollectionAssert.AreEquivalent(new[] { _a, _d }, _indexedSet.Where(IntListWithComparer, contains: 1).ToArray()); // 0 + CollectionAssert.AreEquivalent(new[] { _a, _b, _c, _d }, _indexedSet.Where(IntListWithComparer, contains: 2).ToArray()); // 1 + CollectionAssert.AreEquivalent(new[] { _a, _b, _c, _d }, _indexedSet.Where(IntListWithComparer, contains: 3).ToArray()); // 1 + CollectionAssert.AreEquivalent(new[] { _a }, _indexedSet.Where(IntListWithComparer, contains: 4).ToArray()); // 2 + } + + [TestMethod] + public void Custom_comparer_trygetsingle() + { + Assert.IsTrue(_indexedSet.TryGetSingle(IntListWithComparer, 4, out DenormalizedTestData? test1)); + Assert.AreEqual(_a, test1); + + Assert.IsFalse(_indexedSet.TryGetSingle(IntListWithComparer, 1, out DenormalizedTestData? test2)); + Assert.IsNull(test2); + + Assert.IsFalse(_indexedSet.TryGetSingle(IntListWithComparer, 6, out DenormalizedTestData? test3)); + Assert.IsNull(test3); + } + [TestMethod] public void Clear() { diff --git a/Akade.IndexedSet.Tests/NonUniqueIndices.cs b/Akade.IndexedSet.Tests/NonUniqueIndices.cs index 7750dd7..2ee61e2 100644 --- a/Akade.IndexedSet.Tests/NonUniqueIndices.cs +++ b/Akade.IndexedSet.Tests/NonUniqueIndices.cs @@ -22,11 +22,12 @@ public void Init() .WithIndex(x => x.IntProperty) .WithIndex(x => x.GuidProperty) .WithIndex(x => x.StringProperty) + .WithIndex(CaseInsensitiveStringProperty, StringComparer.OrdinalIgnoreCase) .Build(); } [TestMethod] - public void retrieval_via_secondary_int_key_returns_correct_items() + public void Retrieval_via_secondary_int_key_returns_correct_items() { _indexedSet.AssertMultipleItems(x => x.IntProperty, expectedElements: [_a, _b]); _indexedSet.AssertSingleItem(x => x.IntProperty, _c); @@ -34,7 +35,7 @@ public void retrieval_via_secondary_int_key_returns_correct_items() } [TestMethod] - public void retrieval_via_secondary_guid_key_returns_correct_items() + public void Retrieval_via_secondary_guid_key_returns_correct_items() { _indexedSet.AssertSingleItem(x => x.GuidProperty, _a); _indexedSet.AssertSingleItem(x => x.GuidProperty, _b); @@ -43,7 +44,7 @@ public void retrieval_via_secondary_guid_key_returns_correct_items() } [TestMethod] - public void retrieval_via_secondary_string_key_returns_correct_items() + public void Retrieval_via_secondary_string_key_returns_correct_items() { _indexedSet.AssertSingleItem(x => x.StringProperty, _a); _indexedSet.AssertSingleItem(x => x.StringProperty, _b); @@ -51,14 +52,13 @@ public void retrieval_via_secondary_string_key_returns_correct_items() } [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void range_queries_throw_exception() + public void Range_queries_throw_exception() { - _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList(); + _ = Assert.ThrowsException(() => _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList()); } [TestMethod] - public void retrieval_via_compound_key_returns_correct_items() + public void Retrieval_via_compound_key_returns_correct_items() { TestData[] data = [_a, _b, _c, _d, _e]; _indexedSet = data.ToIndexedSet(x => x.PrimaryKey) @@ -98,4 +98,19 @@ public void TryGetSingle_on_secondary_key() Assert.IsTrue(_indexedSet.TryGetSingle(x => x.IntProperty, 11, out TestData? data3)); Assert.IsNotNull(data3); } + + private static string CaseInsensitiveStringProperty(TestData data) + { + return data.StringProperty; + } + + [TestMethod] + public void Case_insensitive_string_property_matching() + { + TestData[] actual = _indexedSet.Where(CaseInsensitiveStringProperty, "aa").ToArray(); + CollectionAssert.AreEquivalent(new[] { _a }, actual); + + actual = _indexedSet.Where(CaseInsensitiveStringProperty, "cc").ToArray(); + CollectionAssert.AreEquivalent(new[] { _c, _d, _e }, actual); + } } diff --git a/Akade.IndexedSet.Tests/PrefixIndices.cs b/Akade.IndexedSet.Tests/PrefixIndices.cs index 5a6c664..fff3f10 100644 --- a/Akade.IndexedSet.Tests/PrefixIndices.cs +++ b/Akade.IndexedSet.Tests/PrefixIndices.cs @@ -1,3 +1,4 @@ +using Akade.IndexedSet.StringUtilities; using Akade.IndexedSet.Tests.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Diagnostics.CodeAnalysis; @@ -10,6 +11,7 @@ public class PrefixIndices private record class Animal(string Name, string Category); private IndexedSet _indexedSet = null!; + private Animal[] _allAnimals = null!; private readonly Animal _bonobo = new("Bonobo", "Mammal"); private readonly Animal _booby = new("Booby", "Bird"); private readonly Animal _boomslang = new("Boomslang", "Reptile"); @@ -25,7 +27,7 @@ private record class Animal(string Name, string Category); [TestInitialize] public void Init() { - var data = new Animal[] { + _allAnimals = [ _bonobo, _booby, _boomslang, @@ -37,43 +39,42 @@ public void Init() _panther, _pangolin, _parrot, - }; - _indexedSet = data.ToIndexedSet() - .WithPrefixIndex(x => x.Category) - .WithPrefixIndex(x => x.Name) - .Build(); + ]; + _indexedSet = _allAnimals.ToIndexedSet() + .WithPrefixIndex(x => x.Category) + .WithPrefixIndex(x => x.Name) + .Build(); } [TestMethod] - public void single_item_retrieval_works() + public void Single_item_retrieval_works() { _indexedSet.AssertSingleItem(x => x.Category, _boomslang); _indexedSet.AssertSingleItem(x => x.Category, _tarantula); } [TestMethod] - [ExpectedException(typeof(InvalidOperationException))] - public void single_item_retrieval_throws_exception_if_there_is_more_than_one_result() + public void Single_item_retrieval_throws_exception_if_there_is_more_than_one_result() { - _indexedSet.AssertSingleItem(x => x.Category, _bonobo); + _ = Assert.ThrowsException(() => _indexedSet.AssertSingleItem(x => x.Category, _bonobo)); } [TestMethod] - public void multi_item_retrieval_works() + public void Multi_item_retrieval_works() { _indexedSet.AssertMultipleItems(x => x.Category, expectedElements: [_bonobo, _borador, _tiger, _tapir, _panther, _pangolin]); _indexedSet.AssertMultipleItems(x => x.Category, expectedElements: [_booby, _penguin, _parrot]); } [TestMethod] - public void search_via_starts_with() + public void Search_via_starts_with() { CollectionAssert.AreEquivalent(new[] { _booby, _boomslang }, _indexedSet.StartsWith(x => x.Name, "Boo").ToArray()); CollectionAssert.AreEquivalent(new[] { _panther, _pangolin }, _indexedSet.StartsWith(x => x.Name, "Pan").ToArray()); } [TestMethod] - public void search_via_fuzzy_starts_with() + public void Search_via_fuzzy_starts_with() { CollectionAssert.AreEquivalent(new[] { _bonobo, _booby, _boomslang, _borador }, _indexedSet.FuzzyStartsWith(x => x.Name, "Boo", 1).ToArray()); CollectionAssert.AreEquivalent(new[] { _penguin, _parrot, _panther, _pangolin }, _indexedSet.FuzzyStartsWith(x => x.Name, "Pan", 1).ToArray()); @@ -95,23 +96,9 @@ public void Retrieval_via_multi_key_retrieves_correct_items() { static IEnumerable Multikeys(Animal d) => [d.Name, d.Category]; - var data = new Animal[] { - _bonobo, - _booby, - _boomslang, - _borador, - _tiger, - _tarantula, - _tapir, - _penguin, - _panther, - _pangolin, - _parrot, - }; - - _indexedSet = data.ToIndexedSet() - .WithPrefixIndex(Multikeys) - .Build(); + _indexedSet = _allAnimals.ToIndexedSet() + .WithPrefixIndex(Multikeys) + .Build(); // only reptile & spider _indexedSet.AssertSingleItem(Multikeys, _boomslang); @@ -121,4 +108,30 @@ public void Retrieval_via_multi_key_retrieves_correct_items() CollectionAssert.AreEquivalent(_indexedSet.StartsWith(Multikeys, "Bir").ToArray(), new[] { _booby, _penguin, _parrot }); CollectionAssert.AreEquivalent(_indexedSet.FuzzyStartsWith(Multikeys, "Lir", 1).ToArray(), new[] { _booby, _penguin, _parrot }); } + + [TestMethod] + public void Case_insensitive_matching() + { + static string CategoryCaseInsensitive(Animal a) => a.Category; + + _indexedSet = _allAnimals.ToIndexedSet() + .WithPrefixIndex(CategoryCaseInsensitive, CharEqualityComparer.OrdinalIgnoreCase) + .Build(); + + Animal[] actual = _indexedSet.StartsWith(CategoryCaseInsensitive, "MAMM").ToArray(); + CollectionAssert.AreEquivalent(_allAnimals.Where(x => x.Category == "Mammal").ToArray(), actual); + } + + [TestMethod] + public void Case_insensitive_fuzzy_matching() + { + static string NameCaseInsensitive(Animal a) => a.Name; + + _indexedSet = _allAnimals.ToIndexedSet() + .WithPrefixIndex(NameCaseInsensitive, CharEqualityComparer.OrdinalIgnoreCase) + .Build(); + + Animal[] actual = _indexedSet.FuzzyStartsWith(NameCaseInsensitive, "PAN", 1).ToArray(); + CollectionAssert.AreEquivalent(new[] { _penguin, _parrot, _panther, _pangolin }, actual); + } } diff --git a/Akade.IndexedSet.Tests/RangeIndices.cs b/Akade.IndexedSet.Tests/RangeIndices.cs index 8ca886b..23b5e8b 100644 --- a/Akade.IndexedSet.Tests/RangeIndices.cs +++ b/Akade.IndexedSet.Tests/RangeIndices.cs @@ -1,3 +1,4 @@ + using Akade.IndexedSet.Tests.Data; using Akade.IndexedSet.Tests.TestUtilities; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -26,7 +27,7 @@ public void Init() } [TestMethod] - public void range_query_on_ints_returns_correct_items() + public void Range_query_on_ints_returns_correct_items() { _indexedSet.AssertMultipleItemsViaRange(x => x.IntProperty, 3, 25, inclusiveStart: true, inclusiveEnd: false, expectedElements: [_a, _b, _c, _d, _e]); _indexedSet.AssertMultipleItemsViaRange(x => x.IntProperty, 8, 12, inclusiveStart: true, inclusiveEnd: false, expectedElements: [_a, _b, _c]); @@ -35,7 +36,7 @@ public void range_query_on_ints_returns_correct_items() } [TestMethod] - public void range_query_on_ints_correctly_respects_inclusive_or_exclusive_boundaries() + public void Range_query_on_ints_correctly_respects_inclusive_or_exclusive_boundaries() { _indexedSet.AssertMultipleItemsViaRange(x => x.IntProperty, 10, 13, inclusiveStart: false, inclusiveEnd: false, expectedElements: [_c]); _indexedSet.AssertMultipleItemsViaRange(x => x.IntProperty, 10, 13, inclusiveStart: true, inclusiveEnd: false, expectedElements: [_a, _b, _c]); @@ -44,7 +45,7 @@ public void range_query_on_ints_correctly_respects_inclusive_or_exclusive_bounda } [TestMethod] - public void range_query_on_strings_returns_correct_items() + public void Range_query_on_strings_returns_correct_items() { _indexedSet.AssertMultipleItemsViaRange(x => x.StringProperty, "A", "F", inclusiveStart: true, inclusiveEnd: false, expectedElements: [_a, _b, _c, _d, _e]); _indexedSet.AssertMultipleItemsViaRange(x => x.StringProperty, "A", "D", inclusiveStart: true, inclusiveEnd: false, expectedElements: [_a, _b]); @@ -53,7 +54,7 @@ public void range_query_on_strings_returns_correct_items() } [TestMethod] - public void range_query_on_guids_returns_correct_items() + public void Range_query_on_guids_returns_correct_items() { _indexedSet.AssertMultipleItemsViaRange(x => x.GuidProperty, GuidGen.Get(0), GuidGen.Get(12), inclusiveStart: true, inclusiveEnd: false, expectedElements: [_a, _b, _c, _d, _e]); _indexedSet.AssertMultipleItemsViaRange(x => x.GuidProperty, GuidGen.Get(0), GuidGen.Get(4), inclusiveStart: true, inclusiveEnd: false, expectedElements: [_a, _b, _c]); @@ -216,7 +217,6 @@ public void Retrieval_via_multi_key_retrieves_correct_items() .WithRangeIndex(Multikeys) .Build(); - CollectionAssert.AreEqual(new[] { _a, _b, _c }, _indexedSet.Range(Multikeys, _a.PrimaryKey, _c.PrimaryKey, inclusiveEnd: true).ToArray()); // matches _a @@ -226,4 +226,21 @@ public void Retrieval_via_multi_key_retrieves_correct_items() // matches _a & _b Assert.IsFalse(_indexedSet.TryGetSingle(Multikeys, _b.PrimaryKey, out _)); } + + [TestMethod] + public void Custom_comparer_allows_to_control_order() + { + IComparer byLength = Comparer.Create((a, b) => + { + int lengthComparison = a.Length.CompareTo(b.Length); + return lengthComparison == 0 ? Comparer.Default.Compare(a, b) : lengthComparison; + }); + + IndexedSet set = new[] { "aaa2", "ab", "a", "aaa1", "ba" }.ToIndexedSet() + .WithRangeIndex(x => x, byLength) + .Build(); + + CollectionAssert.AreEqual(new[] { "a", "ab", "ba", "aaa1", "aaa2" }, set.OrderBy(x => x).ToArray()); + CollectionAssert.AreEqual(new[] { "aaa2", "aaa1", "ba", "ab", "a" }, set.OrderByDescending(x => x).ToArray()); + } } diff --git a/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs b/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs index 4a5f1d1..d0e97c4 100644 --- a/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs +++ b/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs @@ -47,7 +47,7 @@ public AppointmentSample() } [TestMethod] - public void get_all_meetings_for_owner() + public void Get_all_meetings_for_owner() { // Multivalue access. Note that we use the same expression that was used during setting the set up Assert.AreEqual(6, _appointments.Where(x => x.Owner, "Lancelot").Count()); @@ -56,7 +56,7 @@ public void get_all_meetings_for_owner() } [TestMethod] - public void get_all_meetings_today_or_tomorrow() + public void Get_all_meetings_today_or_tomorrow() { // Range query with exlusive end string[] meetings = _appointments.Range(x => x.Start, _todayDateTime, _todayDateTime.AddDays(2)) @@ -68,7 +68,7 @@ public void get_all_meetings_today_or_tomorrow() } [TestMethod] - public void check_for_conflicting_meetings() + public void Check_for_conflicting_meetings() { DateTime plannedStart = _today.AddDays(2).WithDayTime(09, 00); DateTime plannedEnd = _today.AddDays(2).WithDayTime(09, 30); @@ -87,16 +87,15 @@ public void check_for_conflicting_meetings() } [TestMethod] - [ExpectedException(typeof(IndexNotFoundException))] - public void key_expression_does_matter() + public void Key_expression_does_matter() { // the index was created with x => x.Owner // Hence, even though the indexed key is the same, the name for the index is not - _ = _appointments.Single(t => t.Owner, "Test"); + _ = Assert.ThrowsException(() => _ = _appointments.Single(t => t.Owner, "Test")); } [TestMethod] - public void using_a_calculated_index_property_to_find_all_longer_appointments() + public void Using_a_calculated_index_property_to_find_all_longer_appointments() { // Note that you can use complex key extractors, make sure string[] longerAppointments = _appointments.Range(Duration, TimeSpan.FromMinutes(30), TimeSpan.MaxValue) @@ -110,7 +109,7 @@ public void using_a_calculated_index_property_to_find_all_longer_appointments() } [TestMethod] - public void text_searching_within_subjects() + public void Text_searching_within_subjects() { // querying within the full-text-search index allows to perform a contains over a trie instead of comparing it on all elements Appointment meetingWith42InSubject = _appointments.Contains(x => x.Subject, "#42").Single(); @@ -118,7 +117,7 @@ public void text_searching_within_subjects() } [TestMethod] - public void fuzzy_searching_within_subjects() + public void Fuzzy_searching_within_subjects() { // Fulltext and prefix indices support fuzzy matching to allow a certain number of errors (Levenshtein/edit distance) Appointment technicalDebtMeeting = _appointments.FuzzyContains(x => x.Subject, "Technical Det", 1).Single(); diff --git a/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs b/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs index afc974b..1184a95 100644 --- a/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs +++ b/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs @@ -9,7 +9,7 @@ namespace Akade.IndexedSet.Tests.Samples.Graph; public class GraphSample { [TestMethod] - public void efficiently_query_incoming_edges_on_nodes() + public void Efficiently_query_incoming_edges_on_nodes() { IndexedSet _graph = IndexedSetBuilder.Create(x => x.Id) .WithIndex(x => x.ConnectedTo) // for special collections such as immutable arrays: make sure the correct overload has been selected by providing generic arguments diff --git a/Akade.IndexedSet.Tests/Samples/Leaderboard/LeaderboardSample.cs b/Akade.IndexedSet.Tests/Samples/Leaderboard/LeaderboardSample.cs index 7bd4579..c280b5a 100644 --- a/Akade.IndexedSet.Tests/Samples/Leaderboard/LeaderboardSample.cs +++ b/Akade.IndexedSet.Tests/Samples/Leaderboard/LeaderboardSample.cs @@ -26,13 +26,13 @@ public LeaderboardSample() [TestMethod] - public void get_overall_highscore() + public void Get_overall_highscore() { Assert.AreEqual(240 * 240, _leaderboard.Max(x => x.Score)); } [TestMethod] - public void get_top_ten() + public void Get_top_ten() { var expected = Enumerable.Range(231, 10) // 231 to 240 (inclusive) .Select(i => i * i) @@ -49,7 +49,7 @@ public void get_top_ten() } [TestMethod] - public void get_second_page_of_leaderboard() + public void Get_second_page_of_leaderboard() { var expected = Enumerable.Range(221, 10) // 221 to 230 (inclusive) .Select(i => i * i) diff --git a/Akade.IndexedSet.Tests/Samples/TypeaheadSample/TypeaheadSample.cs b/Akade.IndexedSet.Tests/Samples/TypeaheadSample/TypeaheadSample.cs index ad9a8bb..dc0b438 100644 --- a/Akade.IndexedSet.Tests/Samples/TypeaheadSample/TypeaheadSample.cs +++ b/Akade.IndexedSet.Tests/Samples/TypeaheadSample/TypeaheadSample.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using Akade.IndexedSet.StringUtilities; +using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Akade.IndexedSet.Tests.Samples.TypeaheadSample; @@ -11,7 +12,7 @@ public TypeaheadSample() { _types = typeof(string).Assembly.GetTypes() .ToIndexedSet() - .WithPrefixIndex(x => x.Name.ToLowerInvariant()) + .WithPrefixIndex(x => x.Name, CharEqualityComparer.OrdinalIgnoreCase) .Build(); } @@ -19,7 +20,7 @@ public TypeaheadSample() public void Case_insensitve_lookahead_in_all_types_within_system_runtime() { // Travers the prefix trie to efficiently find all matches - Type[] types = _types.StartsWith(x => x.Name.ToLowerInvariant(), "int").ToArray(); + Type[] types = _types.StartsWith(x => x.Name, "int").ToArray(); Assert.IsTrue(types.Length > 0); Assert.IsTrue(types.All(t => t.Name.StartsWith("int", StringComparison.InvariantCultureIgnoreCase))); diff --git a/Akade.IndexedSet.Tests/TestUtilities/ComparerUtils.cs b/Akade.IndexedSet.Tests/TestUtilities/ComparerUtils.cs new file mode 100644 index 0000000..b426af9 --- /dev/null +++ b/Akade.IndexedSet.Tests/TestUtilities/ComparerUtils.cs @@ -0,0 +1,121 @@ +namespace Akade.IndexedSet.Tests.TestUtilities; + +/// +/// Helper utilities for converting comparers for the generic base test classes +/// +public static class ComparerUtils +{ + public static IEqualityComparer GetEqualityComparer(object comparer) + where T : notnull + { + comparer = ConvertCharToStringComparer(comparer); + if (comparer is IEqualityComparer equalityComparer) + { + return equalityComparer; + } + else if (comparer is IComparer comp) + { + return EqualityComparer.Create((a, b) => comp.Compare(a, b) == 0, a => throw new NotSupportedException("Cannot use GetHashCode() for a wrapped IComparer")); + } + else + { + throw new ArgumentException($"Invalid comparer type {comparer}", nameof(comparer)); + } + } + + public static IComparer GetComparer(object comparer) + where T : notnull + { + comparer = ConvertCharToStringComparer(comparer); + if (comparer is IComparer comp) + { + return comp; + } + else + { + throw new ArgumentException($"Invalid comparer type {comparer}", nameof(comparer)); + } + } + + /// + /// Prefix and fulltext indices use a char comparer for their internal data structures => this method creates + /// a string comparer from a char comparer (that compares char by char) if the target type is string. + /// + public static object ConvertCharToStringComparer(object comparer) + { + if (typeof(TTarget) != typeof(string)) + { + return comparer; + } + + if (comparer is IEqualityComparer charComparer) + { + return new StringEqualityComparerByChar(charComparer); + } + else if (comparer is IComparer charComp) + { + return new StringComparerByChar(charComp); + } + else + { + throw new ArgumentException($"Invalid comparer type {comparer}", nameof(comparer)); + } + } + + private class StringComparerByChar(IComparer charComparer) : IComparer + { + public int Compare(string? x, string? y) + { + if (x is null || y is null) + { + return x == y ? 0 : x is null ? -1 : 1; + } + int i, j; + for (i = 0, j = 0; i < x.Length && j < y.Length; i++, j++) + { + if (charComparer.Compare(x[i], y[j]) != 0) + { + return charComparer.Compare(x[i], y[j]); + } + } + + if (i == j) + { + return 0; + } + else + { + return i < j ? -1 : 1; + } + } + } + + private class StringEqualityComparerByChar(IEqualityComparer charComparer) : IEqualityComparer + { + public bool Equals(string? x, string? y) + { + if (x is null || y is null) + { + return x == y; + } + int i, j; + for (i = 0, j = 0; i < x.Length && j < y.Length; i++, j++) + { + if (!charComparer.Equals(x[i], y[j])) + { + return false; + } + } + return i == j; + } + public int GetHashCode(string obj) + { + int hash = 0; + foreach (char c in obj) + { + hash = HashCode.Combine(hash, charComparer.GetHashCode(c)); + } + return hash; + } + } +} diff --git a/Akade.IndexedSet.Tests/TestUtilities/IEnumerableExtensions.cs b/Akade.IndexedSet.Tests/TestUtilities/IEnumerableExtensions.cs index 9352af5..d1a7403 100644 --- a/Akade.IndexedSet.Tests/TestUtilities/IEnumerableExtensions.cs +++ b/Akade.IndexedSet.Tests/TestUtilities/IEnumerableExtensions.cs @@ -1,4 +1,6 @@ -namespace Akade.IndexedSet.Tests.TestUtilities; +using System.Collections; + +namespace Akade.IndexedSet.Tests.TestUtilities; internal static class IEnumerableExtensions { @@ -14,4 +16,76 @@ public static string StringJoin(this IEnumerable source, string? seperator { return string.Join(seperator, source); } + + /// + /// Same as but + /// uses sorting and comparison instead of hashing if an IComparer is provided. + /// + public static IEnumerable> GroupByWithSortBasedFallback(this IEnumerable source, Func keySelector, object comparer) + where TKey : notnull + { + comparer = ComparerUtils.ConvertCharToStringComparer(comparer); + if (comparer is IEqualityComparer equalityComparer) + { + return source.GroupBy(keySelector, equalityComparer); + } + else if (comparer is IComparer comp) + { + return GroupBySort(source, keySelector, comp); + } + + throw new ArgumentException($"Invalid comparer type {comparer}", nameof(comparer)); + } + + private static IEnumerable> GroupBySort(IEnumerable source, Func keySelector, IComparer comp) + where TKey : notnull + { + Grouping? currentGroup = null; + foreach (TElement elemennt in source.OrderBy(keySelector, comp)) + { + TKey key = keySelector(elemennt); + + if (currentGroup is null || comp.Compare(currentGroup.Key, key) != 0) + { + if (currentGroup is not null) + { + yield return currentGroup; + } + currentGroup = new Grouping(key, elemennt); + } + else + { + currentGroup.Add(elemennt); + } + + } + + if (currentGroup is not null) + { + yield return currentGroup; + } + } + + private class Grouping(TKey key, TElement firstElement) : IGrouping + { + private readonly List _elements = new([firstElement]); + + public void Add(TElement element) + { + _elements.Add(element); + } + + public TKey Key { get; } = key; + public IEnumerable Elements => _elements; + + public IEnumerator GetEnumerator() + { + return Elements.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } } diff --git a/Akade.IndexedSet.Tests/TestsWithoutIndices.cs b/Akade.IndexedSet.Tests/TestsWithoutIndices.cs index 18ee97a..80c120c 100644 --- a/Akade.IndexedSet.Tests/TestsWithoutIndices.cs +++ b/Akade.IndexedSet.Tests/TestsWithoutIndices.cs @@ -19,7 +19,7 @@ public void Init() } [TestMethod] - public void adding_multiple_items_alawys_returns_correct_count() + public void Adding_multiple_items_always_returns_correct_count() { Assert.AreEqual(0, _indexedSet.Count); @@ -34,7 +34,7 @@ public void adding_multiple_items_alawys_returns_correct_count() } [TestMethod] - public void retrieval_via_primary_key_returns_matching_items() + public void Retrieval_via_primary_key_returns_matching_items() { AddAll(); Assert.AreEqual(_a, _indexedSet[0]); @@ -43,7 +43,7 @@ public void retrieval_via_primary_key_returns_matching_items() } [TestMethod] - public void removing_multiple_items_alawys_returns_correct_count() + public void Removing_multiple_items_alawys_returns_correct_count() { AddAll(); Assert.AreEqual(3, _indexedSet.Count); @@ -59,7 +59,7 @@ public void removing_multiple_items_alawys_returns_correct_count() } [TestMethod] - public void removing_multiple_items_removes_the_correct_items() + public void Removing_multiple_items_removes_the_correct_items() { AddAll(); Assert.IsTrue(_indexedSet.Contains(_b)); @@ -72,34 +72,31 @@ public void removing_multiple_items_removes_the_correct_items() } [TestMethod] - [ExpectedException(typeof(KeyNotFoundException))] - public void missing_primarykey_throws() + public void Missing_primarykey_throws() { AddAll(); - _ = _indexedSet[42]; + _ = Assert.ThrowsException(() => _ = _indexedSet[42]); } [TestMethod] - [ExpectedException(typeof(IndexNotFoundException))] - public void missing_index_throws() + public void Missing_index_throws() { AddAll(); - _ = _indexedSet.Single(x => x.IntProperty, _a.IntProperty); + _ = Assert.ThrowsException(() => _ = _indexedSet.Single(x => x.IntProperty, _a.IntProperty)); } [TestMethod] - public void adding_duplicate_item_returns_false() + public void Adding_duplicate_item_returns_false() { AddAll(); Assert.IsFalse(_indexedSet.Add(_a)); } [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void adding_duplicate_primary_key_throws() + public void Adding_duplicate_primary_key_throws() { AddAll(); - _ = _indexedSet.Add(new TestData(0, 0, Guid.Empty, "")); + _ = Assert.ThrowsException(() => _ = _indexedSet.Add(new TestData(0, 0, Guid.Empty, ""))); } private void AddAll() diff --git a/Akade.IndexedSet.Tests/UniqueIndices.cs b/Akade.IndexedSet.Tests/UniqueIndices.cs index 9352428..4a9795e 100644 --- a/Akade.IndexedSet.Tests/UniqueIndices.cs +++ b/Akade.IndexedSet.Tests/UniqueIndices.cs @@ -24,7 +24,7 @@ public void Init() } [TestMethod] - public void retrieval_via_secondary_int_key_returns_correct_items() + public void Retrieval_via_secondary_int_key_returns_correct_items() { _indexedSet.AssertSingleItem(x => x.IntProperty, _a); _indexedSet.AssertSingleItem(x => x.IntProperty, _b); @@ -32,7 +32,7 @@ public void retrieval_via_secondary_int_key_returns_correct_items() } [TestMethod] - public void retrieval_via_secondary_guid_key_returns_correct_items() + public void Retrieval_via_secondary_guid_key_returns_correct_items() { _indexedSet.AssertSingleItem(x => x.GuidProperty, _a); _indexedSet.AssertSingleItem(x => x.GuidProperty, _b); @@ -40,7 +40,7 @@ public void retrieval_via_secondary_guid_key_returns_correct_items() } [TestMethod] - public void retrieval_via_secondary_string_key_returns_correct_items() + public void Retrieval_via_secondary_string_key_returns_correct_items() { _indexedSet.AssertSingleItem(x => x.StringProperty, _a); _indexedSet.AssertSingleItem(x => x.StringProperty, _b); @@ -48,7 +48,7 @@ public void retrieval_via_secondary_string_key_returns_correct_items() } [TestMethod] - public void retrieval_via_compound_key_returns_correct_items() + public void Retrieval_via_compound_key_returns_correct_items() { TestData[] data = [_a, _b, _c]; _indexedSet = data.ToIndexedSet(x => x.PrimaryKey) @@ -61,17 +61,15 @@ public void retrieval_via_compound_key_returns_correct_items() } [TestMethod] - [ExpectedException(typeof(NotSupportedException))] - public void range_queries_throw_exception() + public void Range_queries_throw_exception() { - _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList(); + _ = Assert.ThrowsException(() => _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList()); } [TestMethod] - [ExpectedException(typeof(ArgumentException))] - public void adding_duplicate_key_throws() + public void Adding_duplicate_key_throws() { - _ = _indexedSet.Add(new TestData(5, 10, Guid.NewGuid(), "ew")); + _ = Assert.ThrowsException(() => _ = _indexedSet.Add(new TestData(5, 10, Guid.NewGuid(), "ew"))); } [TestMethod] @@ -106,4 +104,18 @@ public void Retrieval_via_multi_key_retrieves_correct_items() _indexedSet.AssertSingleItem(Multikeys, _b); _indexedSet.AssertSingleItem(Multikeys, _c); } + + [TestMethod] + public void Custom_comparer_allows_modifying_comparison() + { + string[] data = ["a", "b", "c"]; + IndexedSet indexedSet = data.ToIndexedSet() + .WithUniqueIndex(x => x, StringComparer.OrdinalIgnoreCase) + .Build(); + + Assert.IsTrue(indexedSet.TryGetSingle(x => x, "A", out string? a)); + Assert.AreEqual("a", a); + + _ = Assert.ThrowsException(() => indexedSet.Add("C")); + } } diff --git a/Akade.IndexedSet.Tests/Utils/LevensteinDistanceTests.cs b/Akade.IndexedSet.Tests/Utils/LevensteinDistanceTests.cs index df246f7..36c6af1 100644 --- a/Akade.IndexedSet.Tests/Utils/LevensteinDistanceTests.cs +++ b/Akade.IndexedSet.Tests/Utils/LevensteinDistanceTests.cs @@ -1,5 +1,6 @@ using Akade.IndexedSet.Utils; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Akade.IndexedSet.StringUtilities; namespace Akade.IndexedSet.Tests.Utils; @@ -7,33 +8,64 @@ namespace Akade.IndexedSet.Tests.Utils; public class LevensteinDistanceTests { [TestMethod] - public void distance_zero_should_perform_normal_match() + public void Distance_zero_should_perform_normal_match() { - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Test", 0)); - Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "Best", 0)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Test", 0, EqualityComparer.Default)); + Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "Best", 0, EqualityComparer.Default)); } [TestMethod] - public void distance_one_should_only_match_a_single_change_or_deletion() + public void Distance_one_should_only_match_a_single_change_or_deletion() { - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Test", 0)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Test", 0, EqualityComparer.Default)); - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Best", 1)); - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "est", 1)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Best", 1, EqualityComparer.Default)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "est", 1, EqualityComparer.Default)); - Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "st", 1)); - Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "Bast", 1)); + Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "st", 1, EqualityComparer.Default)); + Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "Bast", 1, EqualityComparer.Default)); } [TestMethod] - public void distance_two_should_match_two_changes_or_deletions() + public void Distance_two_should_match_two_changes_or_deletions() { - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Test", 0)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Test", 0, EqualityComparer.Default)); - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Best", 2)); - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "est", 2)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Best", 2, EqualityComparer.Default)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "est", 2, EqualityComparer.Default)); - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "st", 2)); - Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Bast", 2)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "st", 2, EqualityComparer.Default)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "Bast", 2, EqualityComparer.Default)); + } + + [TestMethod] + public void Distance_zero_should_perform_normal_match_IgnoreCase() + { + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "tESt", 0, CharEqualityComparer.OrdinalIgnoreCase)); + Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "bESt", 0, CharEqualityComparer.OrdinalIgnoreCase)); + } + + [TestMethod] + public void Distance_one_should_only_match_a_single_change_or_deletion_IgnoreCase() + { + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "tESt", 0, CharEqualityComparer.OrdinalIgnoreCase)); + + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "bESt", 1, CharEqualityComparer.OrdinalIgnoreCase)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "eST", 1, CharEqualityComparer.OrdinalIgnoreCase)); + + Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "sT", 1, CharEqualityComparer.OrdinalIgnoreCase)); + Assert.IsFalse(LevenshteinDistance.FuzzyMatch("Test", "bAst", 1, CharEqualityComparer.OrdinalIgnoreCase)); + } + + [TestMethod] + public void Distance_two_should_match_two_changes_or_deletions_IgnoreCase() + { + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "tESt", 0, CharEqualityComparer.OrdinalIgnoreCase)); + + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "bESt", 2, CharEqualityComparer.OrdinalIgnoreCase)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "eST", 2, CharEqualityComparer.OrdinalIgnoreCase)); + + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "sT", 2, CharEqualityComparer.OrdinalIgnoreCase)); + Assert.IsTrue(LevenshteinDistance.FuzzyMatch("Test", "bAst", 2, CharEqualityComparer.OrdinalIgnoreCase)); } } diff --git a/Akade.IndexedSet/DataStructures/BinaryHeap.cs b/Akade.IndexedSet/DataStructures/BinaryHeap.cs index 27b02f0..658d205 100644 --- a/Akade.IndexedSet/DataStructures/BinaryHeap.cs +++ b/Akade.IndexedSet/DataStructures/BinaryHeap.cs @@ -9,10 +9,10 @@ namespace Akade.IndexedSet.DataStructures; /// values are inserted in reverse order: insertion at the beginning requires moving all the values /// within the list and its backing array. /// -internal class BinaryHeap : ICollection +internal class BinaryHeap(IComparer comparer) : ICollection { private readonly List _data = []; - private readonly Comparer _comparer = Comparer.Default; + private readonly IComparer _comparer = comparer; public int Count => _data.Count; diff --git a/Akade.IndexedSet/DataStructures/Lookup.cs b/Akade.IndexedSet/DataStructures/Lookup.cs index e9a577f..eac6f16 100644 --- a/Akade.IndexedSet/DataStructures/Lookup.cs +++ b/Akade.IndexedSet/DataStructures/Lookup.cs @@ -6,10 +6,10 @@ namespace Akade.IndexedSet.DataStructures; /// Modifiable lookup based on with as value collection per key. /// O(1) retreival and insertion. /// -internal class Lookup +internal class Lookup(IEqualityComparer keyComparer) where TKey : notnull { - private readonly Dictionary> _values = []; + private readonly Dictionary> _values = new(keyComparer); public bool Add(TKey key, TValue value) { diff --git a/Akade.IndexedSet/DataStructures/SortedLookup.cs b/Akade.IndexedSet/DataStructures/SortedLookup.cs index b57cb28..19370b6 100644 --- a/Akade.IndexedSet/DataStructures/SortedLookup.cs +++ b/Akade.IndexedSet/DataStructures/SortedLookup.cs @@ -6,11 +6,12 @@ /// Provides O(log(n)) operations as well as range queries based on the key. /// Suffers from the same insertion order problematic as /// -internal class SortedLookup +internal class SortedLookup(IComparer keyComparer) where TKey : notnull { private readonly List _sortedValues = []; - private readonly BinaryHeap _sortedKeys = []; + private readonly BinaryHeap _sortedKeys = new(keyComparer); + private readonly IComparer _keyComparer = keyComparer; public void Add(TKey key, TValue value) { @@ -26,7 +27,7 @@ public void AddRange(IEnumerable> elementsToAdd) _ = _sortedKeys.EnsureCapacity(_sortedKeys.Count + count); } - foreach (KeyValuePair element in elementsToAdd.OrderBy(kvp => kvp.Key)) + foreach (KeyValuePair element in elementsToAdd.OrderBy(kvp => kvp.Key, _keyComparer)) { Add(element.Key, element.Value); } diff --git a/Akade.IndexedSet/DataStructures/SuffixTrie.cs b/Akade.IndexedSet/DataStructures/SuffixTrie.cs index 1bd38b5..e4311f3 100644 --- a/Akade.IndexedSet/DataStructures/SuffixTrie.cs +++ b/Akade.IndexedSet/DataStructures/SuffixTrie.cs @@ -1,7 +1,7 @@ namespace Akade.IndexedSet.DataStructures; -internal class SuffixTrie +internal class SuffixTrie(IEqualityComparer equalityComparer) { - private readonly Trie _trie = new(); + private readonly Trie _trie = new(equalityComparer); public bool Add(ReadOnlySpan key, TElement element) { diff --git a/Akade.IndexedSet/DataStructures/Trie.cs b/Akade.IndexedSet/DataStructures/Trie.cs index d98b10e..62a8d74 100644 --- a/Akade.IndexedSet/DataStructures/Trie.cs +++ b/Akade.IndexedSet/DataStructures/Trie.cs @@ -5,9 +5,9 @@ namespace Akade.IndexedSet.DataStructures; -internal class Trie +internal class Trie(IEqualityComparer equalityComparer) { - private readonly TrieNode _root = new(); + private readonly TrieNode _root = new(equalityComparer); public bool Add(ReadOnlySpan key, TElement element) { @@ -79,7 +79,7 @@ private void FuzzySearchInternal(TrieNode currentNode, char ch, ReadOnlySpan.TrieNode currentNode, PriorityQueue results, int distance) + private static void AddRecursivlyToResult(TrieNode currentNode, PriorityQueue results, int distance) { if (currentNode._elements is not null) { @@ -135,14 +135,14 @@ private static void AddRecursivlyToResult(Trie.TrieNode currentNode, P } if (currentNode._children is not null) { - foreach ((_, Trie.TrieNode child) in currentNode._children) + foreach ((_, TrieNode child) in currentNode._children) { AddRecursivlyToResult(child, results, distance); } } } - private static void AddRecursivlyToResult(Trie.TrieNode currentNode, List results) + private static void AddRecursivlyToResult(TrieNode currentNode, List results) { if (currentNode._elements is not null) { @@ -151,7 +151,7 @@ private static void AddRecursivlyToResult(Trie.TrieNode currentNode, L if (currentNode._children is not null) { - foreach ((_, Trie.TrieNode child) in currentNode._children) + foreach ((_, TrieNode child) in currentNode._children) { AddRecursivlyToResult(child, results); } @@ -163,7 +163,7 @@ internal void Clear() _root.Clear(); } - private class TrieNode + private class TrieNode(IEqualityComparer equalityComparer) { internal Dictionary? _children; internal HashSet? _elements; @@ -177,11 +177,11 @@ internal bool Add(ReadOnlySpan key, TElement element) } else { - _children ??= []; + _children ??= new(equalityComparer); ref TrieNode? trieNode = ref CollectionsMarshal.GetValueRefOrAddDefault(_children, key[0], out _); - trieNode ??= new(); + trieNode ??= new(equalityComparer); return trieNode.Add(key[1..], element); } } diff --git a/Akade.IndexedSet/Extensions/SpanExtensions.cs b/Akade.IndexedSet/Extensions/SpanExtensions.cs new file mode 100644 index 0000000..2331fc8 --- /dev/null +++ b/Akade.IndexedSet/Extensions/SpanExtensions.cs @@ -0,0 +1,21 @@ +namespace Akade.IndexedSet.Extensions; + +internal static class SpanExtensions +{ + internal static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan contains, TComparer comparer) + where TComparer : IEqualityComparer + { + if (span.Length < contains.Length) + { + return false; + } + for (int i = 0; i < contains.Length; i++) + { + if (!comparer.Equals(span[i], contains[i])) + { + return false; + } + } + return true; + } +} diff --git a/Akade.IndexedSet/IndexedSet.cs b/Akade.IndexedSet/IndexedSet.cs index ad55130..5117477 100644 --- a/Akade.IndexedSet/IndexedSet.cs +++ b/Akade.IndexedSet/IndexedSet.cs @@ -192,7 +192,7 @@ public TElement Single( } /// - /// Searches for an element via an index within "denormalized keys". See . + /// Searches for an element via an index within "denormalized keys". See . /// /// The type of the index key /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. @@ -211,7 +211,7 @@ public TElement Single( } /// - /// Searches for multiple elements via an index. See . + /// Searches for multiple elements via an index. See . /// /// The type of the index key /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. @@ -230,7 +230,7 @@ public IEnumerable Where( } /// - /// Searches for multiple elements via an index within "denormalized keys". See . + /// Searches for multiple elements via an index within "denormalized keys". See . /// /// The type of the index key /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. @@ -880,12 +880,13 @@ public class IndexedSet : IndexedSet /// Returns the primary key for a given item. The primary key should not be changed while the element is within the set. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. /// Is passed to using . /// The name for the primary index. Usually, you should not specify this as the expression in is automatically passed by the compiler. - protected internal IndexedSet(Func primaryKeyAccessor, [CallerArgumentExpression(nameof(primaryKeyAccessor))] string primaryKeyIndexName = "") + /// A key comparer used to compare primary keys. If null is passed, the default comparer is used. + protected internal IndexedSet(Func primaryKeyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(primaryKeyAccessor))] string primaryKeyIndexName = "") { _primaryKeyAccessor = primaryKeyAccessor; _primaryKeyIndexName = primaryKeyIndexName; - AddIndex(_primaryKeyAccessor, new UniqueIndex(primaryKeyIndexName)); + AddIndex(_primaryKeyAccessor, new UniqueIndex(keyComparer ?? EqualityComparer.Default, primaryKeyIndexName)); } /// diff --git a/Akade.IndexedSet/IndexedSetBuilder.cs b/Akade.IndexedSet/IndexedSetBuilder.cs index 91d5e48..3a38286 100644 --- a/Akade.IndexedSet/IndexedSetBuilder.cs +++ b/Akade.IndexedSet/IndexedSetBuilder.cs @@ -98,14 +98,16 @@ internal IndexedSetBuilder(IndexedSet? indexedSet, IEnumerableThe type of the key within the index /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer for is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithUniqueIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithUniqueIndex(Func keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) where TIndexKey : notnull { ArgumentNullException.ThrowIfNull(indexName); + keyComparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new UniqueIndex(indexName)); + _result.AddIndex(keyAccessor, new UniqueIndex(keyComparer, indexName)); return this; } @@ -120,14 +122,16 @@ public virtual IndexedSetBuilder WithUniqueIndex(FuncThe type of the key within the index /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer for is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithUniqueIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithUniqueIndex(Func> keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) where TIndexKey : notnull { ArgumentNullException.ThrowIfNull(indexName); + keyComparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new UniqueIndex(indexName)); + _result.AddIndex(keyAccessor, new UniqueIndex(keyComparer, indexName)); return this; } @@ -142,14 +146,16 @@ public virtual IndexedSetBuilder WithUniqueIndex(FuncThe type of the key within the index /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer for is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithIndex(Func keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) where TIndexKey : notnull { ArgumentNullException.ThrowIfNull(indexName); + keyComparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new NonUniqueIndex(indexName)); + _result.AddIndex(keyAccessor, new NonUniqueIndex(keyComparer, indexName)); return this; } @@ -164,14 +170,16 @@ public virtual IndexedSetBuilder WithIndex(FuncThe type of the key within the index /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer for is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithIndex(Func> keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) where TIndexKey : notnull { ArgumentNullException.ThrowIfNull(indexName); + keyComparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new NonUniqueIndex(indexName)); + _result.AddIndex(keyAccessor, new NonUniqueIndex(keyComparer, indexName)); return this; } @@ -186,14 +194,16 @@ public virtual IndexedSetBuilder WithIndex(FuncThe type of the key within the index /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer for is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithRangeIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithRangeIndex(Func keyAccessor, IComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) where TIndexKey : notnull { ArgumentNullException.ThrowIfNull(indexName); + keyComparer ??= Comparer.Default; - _result.AddIndex(keyAccessor, new RangeIndex(indexName)); + _result.AddIndex(keyAccessor, new RangeIndex(keyComparer, indexName)); return this; } @@ -208,14 +218,16 @@ public virtual IndexedSetBuilder WithRangeIndex(FuncThe type of the key within the index /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer for is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithRangeIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithRangeIndex(Func> keyAccessor, IComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) where TIndexKey : notnull { ArgumentNullException.ThrowIfNull(indexName); + keyComparer ??= Comparer.Default; - _result.AddIndex(keyAccessor, new MultiRangeIndex(indexName)); + _result.AddIndex(keyAccessor, new MultiRangeIndex(keyComparer, indexName)); return this; } @@ -229,13 +241,15 @@ public virtual IndexedSetBuilder WithRangeIndex(Func /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithFullTextIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithFullTextIndex(Func keyAccessor, IEqualityComparer? comparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { ArgumentNullException.ThrowIfNull(indexName); + comparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new FullTextIndex(keyAccessor, indexName)); + _result.AddIndex(keyAccessor, new FullTextIndex(keyAccessor, comparer, indexName)); return this; } @@ -249,14 +263,16 @@ public virtual IndexedSetBuilder WithFullTextIndex(Func /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. [Experimental(Experiments.TextSearchImprovements, UrlFormat = Experiments.UrlTemplate)] - public virtual IndexedSetBuilder WithFullTextIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithFullTextIndex(Func> keyAccessor, IEqualityComparer? comparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { ArgumentNullException.ThrowIfNull(indexName); + comparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new FullTextIndex(keyAccessor, indexName)); + _result.AddIndex(keyAccessor, new FullTextIndex(keyAccessor, comparer, indexName)); return this; } @@ -270,13 +286,15 @@ public virtual IndexedSetBuilder WithFullTextIndex(Func /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. - public virtual IndexedSetBuilder WithPrefixIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithPrefixIndex(Func keyAccessor, IEqualityComparer? comparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { ArgumentNullException.ThrowIfNull(indexName); + comparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new PrefixIndex(indexName)); + _result.AddIndex(keyAccessor, new PrefixIndex(comparer, indexName)); return this; } @@ -290,14 +308,16 @@ public virtual IndexedSetBuilder WithPrefixIndex(Func /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. /// Hence, the convention is to always use x as an identifier in case a lambda expression is used. + /// The comparer to use for the index. If not specified, the default comparer is used. /// The name of the index. Usually, you should not specify this as the expression in is automatically passed by the compiler. /// The instance on which this method is called is returned to support the fluent syntax. [Experimental(Experiments.TextSearchImprovements, UrlFormat = Experiments.UrlTemplate)] - public virtual IndexedSetBuilder WithPrefixIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public virtual IndexedSetBuilder WithPrefixIndex(Func> keyAccessor, IEqualityComparer? comparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { ArgumentNullException.ThrowIfNull(indexName); + comparer ??= EqualityComparer.Default; - _result.AddIndex(keyAccessor, new PrefixIndex(indexName)); + _result.AddIndex(keyAccessor, new PrefixIndex(comparer, indexName)); return this; } @@ -332,64 +352,64 @@ public class IndexedSetBuilder : IndexedSetBuilder primaryKeyAccessor, IEnumerable? initialContent, string primaryKeyIndexName) : base(new IndexedSet(primaryKeyAccessor, primaryKeyIndexName), initialContent) + internal IndexedSetBuilder(Func primaryKeyAccessor, IEnumerable? initialContent, string primaryKeyIndexName, IEqualityComparer? keyComparer = null) + : base(new IndexedSet(primaryKeyAccessor, keyComparer, primaryKeyIndexName), initialContent) { } /// - public override IndexedSetBuilder WithIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithIndex(Func keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithIndex(keyAccessor, indexName); + _ = base.WithIndex(keyAccessor, keyComparer, indexName); return this; } /// - public override IndexedSetBuilder WithIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithIndex(Func> keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithIndex(keyAccessor, indexName); + _ = base.WithIndex(keyAccessor, keyComparer, indexName); return this; } /// - public override IndexedSetBuilder WithRangeIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithRangeIndex(Func keyAccessor, IComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithRangeIndex(keyAccessor, indexName); + _ = base.WithRangeIndex(keyAccessor, keyComparer, indexName); return this; } /// - public override IndexedSetBuilder WithRangeIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithRangeIndex(Func> keyAccessor, IComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithRangeIndex(keyAccessor, indexName); + _ = base.WithRangeIndex(keyAccessor, keyComparer, indexName); return this; } /// - public override IndexedSetBuilder WithUniqueIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithUniqueIndex(Func keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithUniqueIndex(keyAccessor, indexName); + _ = base.WithUniqueIndex(keyAccessor, keyComparer, indexName); return this; } /// - public override IndexedSetBuilder WithUniqueIndex(Func> keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithUniqueIndex(Func> keyAccessor, IEqualityComparer? keyComparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithUniqueIndex(keyAccessor, indexName); + _ = base.WithUniqueIndex(keyAccessor, keyComparer, indexName); return this; } - /// - public override IndexedSetBuilder WithFullTextIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithFullTextIndex(Func keyAccessor, IEqualityComparer? comparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithFullTextIndex(keyAccessor, indexName); + _ = base.WithFullTextIndex(keyAccessor, comparer, indexName); return this; } /// - public override IndexedSetBuilder WithPrefixIndex(Func keyAccessor, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) + public override IndexedSetBuilder WithPrefixIndex(Func keyAccessor, IEqualityComparer? comparer = null, [CallerArgumentExpression(nameof(keyAccessor))] string? indexName = null) { - _ = base.WithPrefixIndex(keyAccessor, indexName); + _ = base.WithPrefixIndex(keyAccessor, comparer, indexName); return this; } diff --git a/Akade.IndexedSet/Indices/FullTextIndex.cs b/Akade.IndexedSet/Indices/FullTextIndex.cs index bc5b098..ef49148 100644 --- a/Akade.IndexedSet/Indices/FullTextIndex.cs +++ b/Akade.IndexedSet/Indices/FullTextIndex.cs @@ -1,5 +1,6 @@ using Akade.IndexedSet.DataStructures; using Akade.IndexedSet.Extensions; +using Akade.IndexedSet.StringUtilities; using Akade.IndexedSet.Utils; namespace Akade.IndexedSet.Indices; @@ -8,26 +9,43 @@ internal sealed class FullTextIndex : TypedIndex { private readonly SuffixTrie _suffixTrie; + private delegate bool StartsWithCheck(ReadOnlySpan key, ReadOnlySpan startsWith); private delegate bool CandidateMatchCheck(TElement element, ReadOnlySpan startsWith); private delegate bool FuzzyCandidateMatchCheck(TElement element, ReadOnlySpan startsWith, int maxDistance); + private readonly StartsWithCheck _startsWithCheck; private readonly CandidateMatchCheck _checkCandidateMatch; private readonly FuzzyCandidateMatchCheck _checkFuzzyCandidateMatch; + private readonly IEqualityComparer _equalityComparer; - public FullTextIndex(Func keyAccessor, string name) : base(name) + public FullTextIndex(Func keyAccessor, IEqualityComparer equalityComparer, string name) : base(name) { - _checkCandidateMatch = (elem, startsWith) => MemoryExtensions.StartsWith(keyAccessor(elem), startsWith, StringComparison.Ordinal); + _startsWithCheck = GetStartsWithForComparer(equalityComparer); + _checkCandidateMatch = (elem, startsWith) => _startsWithCheck(keyAccessor(elem), startsWith); _checkFuzzyCandidateMatch = (elem, startsWith, maxDistance) => VerifyFuzzyStartsWith(keyAccessor(elem), startsWith, maxDistance); - _suffixTrie = new(); + _suffixTrie = new(equalityComparer); + _equalityComparer = equalityComparer; } - public FullTextIndex(Func> keyAccessor, string name) : base(name) + private static StartsWithCheck GetStartsWithForComparer(IEqualityComparer equalityComparer) { + return equalityComparer switch + { + IgnoreCaseCharEqualityComparer => (elem, startsWith) => elem.StartsWith(startsWith, StringComparison.OrdinalIgnoreCase), + { } x when x == EqualityComparer.Default => (elem, startsWith) => elem.StartsWith(startsWith, StringComparison.Ordinal), + _ => (elem, startsWith) => elem.StartsWith(startsWith, equalityComparer) + }; + } + + public FullTextIndex(Func> keyAccessor, IEqualityComparer equalityComparer, string name) : base(name) + { + _startsWithCheck = GetStartsWithForComparer(equalityComparer); + _checkCandidateMatch = (elem, startsWith) => { foreach (string key in keyAccessor(elem)) { - if (MemoryExtensions.StartsWith(key, startsWith, StringComparison.Ordinal)) + if (_startsWithCheck(key, startsWith)) return true; } @@ -44,7 +62,8 @@ public FullTextIndex(Func> keyAccessor, string nam return false; }; - _suffixTrie = new(); + _suffixTrie = new(equalityComparer); + _equalityComparer = equalityComparer; } internal override void Add(string key, TElement value) @@ -99,9 +118,9 @@ internal override IEnumerable StartsWith(ReadOnlySpan indexKey) return matches; } - private static bool VerifyFuzzyStartsWith(ReadOnlySpan found, ReadOnlySpan startsWith, int maxDistance) + private bool VerifyFuzzyStartsWith(ReadOnlySpan found, ReadOnlySpan startsWith, int maxDistance) { - return LevenshteinDistance.FuzzyMatch(found[..startsWith.Length], startsWith, maxDistance); + return LevenshteinDistance.FuzzyMatch(found[..startsWith.Length], startsWith, maxDistance, _equalityComparer); } internal override IEnumerable FuzzyStartsWith(ReadOnlySpan indexKey, int maxDistance) diff --git a/Akade.IndexedSet/Indices/MultiRangeIndex.cs b/Akade.IndexedSet/Indices/MultiRangeIndex.cs index 25a7342..f31ad9b 100644 --- a/Akade.IndexedSet/Indices/MultiRangeIndex.cs +++ b/Akade.IndexedSet/Indices/MultiRangeIndex.cs @@ -7,10 +7,10 @@ namespace Akade.IndexedSet.Indices; /// O(log(n)) range queries based on . Filters for distinct values as it is used /// for indices where elements can have multiple keys. /// -internal class MultiRangeIndex(string name) : TypedIndex(name) +internal class MultiRangeIndex(IComparer keyComparer, string name) : TypedIndex(name) where TIndexKey : notnull { - private readonly SortedLookup _lookup = new(); + private readonly SortedLookup _lookup = new(keyComparer); internal override void Add(TIndexKey key, TElement value) { diff --git a/Akade.IndexedSet/Indices/NonUniqueIndex.cs b/Akade.IndexedSet/Indices/NonUniqueIndex.cs index f871d13..1a9df8a 100644 --- a/Akade.IndexedSet/Indices/NonUniqueIndex.cs +++ b/Akade.IndexedSet/Indices/NonUniqueIndex.cs @@ -3,10 +3,10 @@ /// /// Nonunique index implementation based on /// -internal sealed class NonUniqueIndex(string name) : TypedIndex(name) +internal sealed class NonUniqueIndex(IEqualityComparer equalityComparer, string name) : TypedIndex(name) where TIndexKey : notnull { - private readonly DataStructures.Lookup _data = new(); + private readonly DataStructures.Lookup _data = new(equalityComparer); internal override void Add(TIndexKey key, TElement value) { diff --git a/Akade.IndexedSet/Indices/PrefixIndex.cs b/Akade.IndexedSet/Indices/PrefixIndex.cs index f715e6b..37729bc 100644 --- a/Akade.IndexedSet/Indices/PrefixIndex.cs +++ b/Akade.IndexedSet/Indices/PrefixIndex.cs @@ -2,9 +2,9 @@ using Akade.IndexedSet.Extensions; namespace Akade.IndexedSet.Indices; -internal class PrefixIndex(string name) : TypedIndex(name) +internal class PrefixIndex(IEqualityComparer equalityComparer, string name) : TypedIndex(name) { - private readonly Trie _trie = new(); + private readonly Trie _trie = new(equalityComparer); internal override void Add(string key, TElement value) { diff --git a/Akade.IndexedSet/Indices/RangeIndex.cs b/Akade.IndexedSet/Indices/RangeIndex.cs index a51a89e..5850bff 100644 --- a/Akade.IndexedSet/Indices/RangeIndex.cs +++ b/Akade.IndexedSet/Indices/RangeIndex.cs @@ -5,10 +5,10 @@ namespace Akade.IndexedSet.Indices; /// /// O(log(n)) range queries based on . /// -internal class RangeIndex(string name) : TypedIndex(name) +internal class RangeIndex(IComparer keyComparer, string name) : TypedIndex(name) where TIndexKey : notnull { - private readonly SortedLookup _lookup = new(); + private readonly SortedLookup _lookup = new(keyComparer); internal override void Add(TIndexKey key, TElement value) { diff --git a/Akade.IndexedSet/Indices/UniqueIndex.cs b/Akade.IndexedSet/Indices/UniqueIndex.cs index 9d84c14..c5f451b 100644 --- a/Akade.IndexedSet/Indices/UniqueIndex.cs +++ b/Akade.IndexedSet/Indices/UniqueIndex.cs @@ -3,10 +3,10 @@ /// /// Unique index providing O(1) retrieval and insertion as well as enforcing unqueness /// -internal class UniqueIndex(string name) : TypedIndex(name) +internal class UniqueIndex(IEqualityComparer equalityComparer, string name) : TypedIndex(name) where TIndexKey : notnull { - private readonly Dictionary _data = []; + private readonly Dictionary _data = new(equalityComparer); internal override void Add(TIndexKey key, TElement value) { diff --git a/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt b/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt index 3e16ab3..d2c2358 100644 --- a/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt +++ b/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt @@ -111,7 +111,6 @@ Akade.IndexedSet.IndexedSet.Where(System.Func.Where(System.Func! indexAccessor, TIndexKey indexKey, string? indexName = null) -> System.Collections.Generic.IEnumerable! Akade.IndexedSet.IndexedSet Akade.IndexedSet.IndexedSet.Contains(TPrimaryKey key) -> bool -Akade.IndexedSet.IndexedSet.IndexedSet(System.Func! primaryKeyAccessor, string! primaryKeyIndexName = "") -> void Akade.IndexedSet.IndexedSet.Remove(TPrimaryKey key) -> bool Akade.IndexedSet.IndexedSet.Single(TPrimaryKey key) -> TElement Akade.IndexedSet.IndexedSet.this[TPrimaryKey key].get -> TElement @@ -122,14 +121,6 @@ Akade.IndexedSet.IndexedSetBuilder Akade.IndexedSet.IndexNotFoundException override Akade.IndexedSet.IndexedSetBuilder.Build() -> Akade.IndexedSet.IndexedSet! override Akade.IndexedSet.IndexedSetBuilder.BuildConcurrent() -> Akade.IndexedSet.Concurrency.ConcurrentIndexedSet! -override Akade.IndexedSet.IndexedSetBuilder.WithFullTextIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -override Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -override Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -override Akade.IndexedSet.IndexedSetBuilder.WithPrefixIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -override Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -override Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -override Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -override Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! static Akade.IndexedSet.IndexedSetBuilder.Create(System.Collections.Generic.IEnumerable! initialContent) -> Akade.IndexedSet.IndexedSetBuilder! static Akade.IndexedSet.IndexedSetBuilder.Create(System.Collections.Generic.IEnumerable! initialContent, System.Func! primaryKeyAccessor, string! primaryKeyIndexName = "") -> Akade.IndexedSet.IndexedSetBuilder! static Akade.IndexedSet.IndexedSetBuilder.ToIndexedSet(this System.Collections.Generic.IEnumerable! initialContent) -> Akade.IndexedSet.IndexedSetBuilder! @@ -138,13 +129,24 @@ static Akade.IndexedSet.IndexedSetBuilder.Create() -> Akade.IndexedSet static Akade.IndexedSet.IndexedSetBuilder.Create(System.Func! primaryKeyAccessor, string! primaryKeyIndexName = "") -> Akade.IndexedSet.IndexedSetBuilder! virtual Akade.IndexedSet.IndexedSetBuilder.Build() -> Akade.IndexedSet.IndexedSet! virtual Akade.IndexedSet.IndexedSetBuilder.BuildConcurrent() -> Akade.IndexedSet.Concurrency.ConcurrentIndexedSet! -virtual Akade.IndexedSet.IndexedSetBuilder.WithFullTextIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithFullTextIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithPrefixIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithPrefixIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func!>! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func! keyAccessor, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! + + +Akade.IndexedSet.IndexedSet.IndexedSet(System.Func! primaryKeyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string! primaryKeyIndexName = "") -> void +override Akade.IndexedSet.IndexedSetBuilder.WithFullTextIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? comparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +override Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func!>! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +override Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +override Akade.IndexedSet.IndexedSetBuilder.WithPrefixIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? comparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +override Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func!>! keyAccessor, System.Collections.Generic.IComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +override Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func! keyAccessor, System.Collections.Generic.IComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +override Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func!>! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +override Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithFullTextIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? comparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithFullTextIndex(System.Func!>! keyAccessor, System.Collections.Generic.IEqualityComparer? comparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func!>! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithPrefixIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? comparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithPrefixIndex(System.Func!>! keyAccessor, System.Collections.Generic.IEqualityComparer? comparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func!>! keyAccessor, System.Collections.Generic.IComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func! keyAccessor, System.Collections.Generic.IComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func!>! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! +virtual Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! \ No newline at end of file diff --git a/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt b/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt index ab058de..a6f6301 100644 --- a/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt +++ b/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,4 @@ #nullable enable +Akade.IndexedSet.StringUtilities.CharEqualityComparer +static Akade.IndexedSet.StringUtilities.CharEqualityComparer.IgnoreCase(System.Globalization.CultureInfo! cultureInfo) -> System.Collections.Generic.IEqualityComparer! +static Akade.IndexedSet.StringUtilities.CharEqualityComparer.OrdinalIgnoreCase.get -> System.Collections.Generic.IEqualityComparer! diff --git a/Akade.IndexedSet/StringUtilities/CharComparer.cs b/Akade.IndexedSet/StringUtilities/CharComparer.cs new file mode 100644 index 0000000..ff04db7 --- /dev/null +++ b/Akade.IndexedSet/StringUtilities/CharComparer.cs @@ -0,0 +1,51 @@ +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +namespace Akade.IndexedSet.StringUtilities; + + +/// +/// Provides a set of standard comparer for values. +/// +public static class CharEqualityComparer +{ + /// + /// Gets a that performs an ordinal equality comparison of values using . + /// + public static IEqualityComparer OrdinalIgnoreCase { get; } = new OrdinalIgnoreCaseCharEqualityComparer(); + + /// + /// Gets a that performs an ordinal equality comparison of values using . + /// + /// The culture used for the case transformation + public static IEqualityComparer IgnoreCase(CultureInfo cultureInfo) + { + return new IgnoreCaseCharEqualityComparer(cultureInfo); + } +} + +internal class IgnoreCaseCharEqualityComparer(CultureInfo cultureInfo) : IEqualityComparer +{ + public bool Equals(char x, char y) + { + return char.ToUpper(x, cultureInfo) == char.ToUpper(y, cultureInfo); + } + + public int GetHashCode([DisallowNull] char obj) + { + return char.ToUpper(obj, cultureInfo).GetHashCode(); + } +} + +internal class OrdinalIgnoreCaseCharEqualityComparer : IEqualityComparer +{ + public bool Equals(char x, char y) + { + return char.ToUpperInvariant(x) == char.ToUpperInvariant(y); + } + + public int GetHashCode([DisallowNull] char obj) + { + return char.ToUpperInvariant(obj).GetHashCode(); + } +} diff --git a/Akade.IndexedSet/Utils/LevenshteinDistance.cs b/Akade.IndexedSet/Utils/LevenshteinDistance.cs index e1f1cda..92d1eb4 100644 --- a/Akade.IndexedSet/Utils/LevenshteinDistance.cs +++ b/Akade.IndexedSet/Utils/LevenshteinDistance.cs @@ -8,7 +8,7 @@ internal static class LevenshteinDistance /// Returns true if the strings have a levenshein distance smaller than . /// Does not calculate the entire distance if the minimum distance is already bigger. /// - public static bool FuzzyMatch(ReadOnlySpan a, ReadOnlySpan b, int maxDistance) + public static bool FuzzyMatch(ReadOnlySpan a, ReadOnlySpan b, int maxDistance, IEqualityComparer equalityComparer) { int rowLength = a.Length + 1; @@ -42,7 +42,7 @@ public static bool FuzzyMatch(ReadOnlySpan a, ReadOnlySpan b, int ma for (int i = 1; i < currentRow.Length; i++) { int insertOrDeletion = Math.Min(currentRow[i - 1] + 1, lastRow[i] + 1); - int replacement = a[i - 1] == b[j] ? lastRow[i - 1] : lastRow[i - 1] + 1; + int replacement = equalityComparer.Equals(a[i - 1], b[j]) ? lastRow[i - 1] : lastRow[i - 1] + 1; currentRow[i] = Math.Min(insertOrDeletion, replacement); minDistance = Math.Min(minDistance, currentRow[i]); } diff --git a/README.md b/README.md index 825479f..9d7c213 100644 --- a/README.md +++ b/README.md @@ -332,13 +332,14 @@ concurrentSet.Update(set => ``` ### How do I do case-insensitve (fuzzy) string matching (Prefix, FullTextIndex)? -Remember that you can index whatever you want, including computed properties. This also applies for fuzzy matching: +While you can use whatever index expression that you want (i.e. `.ToLowerInvariant()`), +using a comparer is recommended: ```csharp IndexedSet set = IndexedSetBuilder.Create(x => x.PrimaryKey) - .WithFullTextIndex(x => x.Text.ToLowerInvariant()) + .WithFullTextIndex(x => x.Text, CharEqualityComparer.OrdinalIgnoreCase) .Build(); -IEnumerable matches = set.FuzzyContains(x => x.Text.ToLowerInvariant(), "Search", maxDistance: 2); +IEnumerable matches = set.FuzzyContains(x => x.Text, "Search", maxDistance: 2); ``` ## Roadmap From c1d778c1c1b2a06d4c6136b8ac962f07a44e9623 Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sun, 20 Apr 2025 18:05:46 +0200 Subject: [PATCH 26/36] Fixes #217 by splitting the analyzers and the corresponding fixes into separate assemblies. (#220) --- Akade.IndexedSet.sln | 7 +++++ Akade.IndexedSet/Akade.IndexedSet.csproj | 2 ++ .../Akade.IndexedSet.Analyzers.Fixes.csproj | 26 +++++++++++++++++++ ...AkadeIndexedSetAnalyzersCodeFixProvider.cs | 5 ++-- .../Akade.IndexedSet.Analyzers.Test.csproj | 3 ++- .../AnalyzerAndCodeFixTests.cs | 2 +- .../SampleCodeGenerator.cs | 2 +- .../SimpleAnalyzerTests.cs | 2 +- .../SimpleCodeFixTests.cs | 2 +- .../Akade.IndexedSet.Analyzers.Vsix.csproj | 1 + .../Akade.IndexedSet.Analyzers.csproj | 1 - ....IndexedSet.InternalSourceGenerator.csproj | 1 - 12 files changed, 44 insertions(+), 10 deletions(-) create mode 100644 Analyzers/Akade.IndexedSet.Analyzers.Fixes/Akade.IndexedSet.Analyzers.Fixes.csproj rename Analyzers/{Akade.IndexedSet.Analyzers => Akade.IndexedSet.Analyzers.Fixes}/AkadeIndexedSetAnalyzersCodeFixProvider.cs (99%) diff --git a/Akade.IndexedSet.sln b/Akade.IndexedSet.sln index 9605e67..62ab4cf 100644 --- a/Akade.IndexedSet.sln +++ b/Akade.IndexedSet.sln @@ -44,6 +44,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{A85AE3BF-0 docs\ExperimentalFeatures.md = docs\ExperimentalFeatures.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Akade.IndexedSet.Analyzers.Fixes", "Analyzers\Akade.IndexedSet.Analyzers.Fixes\Akade.IndexedSet.Analyzers.Fixes.csproj", "{951D9B15-BA77-F3E4-348D-FB0600408FC6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -82,6 +84,10 @@ Global {34BEB19B-028A-4CD1-90EB-1E1A62C04671}.Debug|Any CPU.Build.0 = Debug|Any CPU {34BEB19B-028A-4CD1-90EB-1E1A62C04671}.Release|Any CPU.ActiveCfg = Release|Any CPU {34BEB19B-028A-4CD1-90EB-1E1A62C04671}.Release|Any CPU.Build.0 = Release|Any CPU + {951D9B15-BA77-F3E4-348D-FB0600408FC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {951D9B15-BA77-F3E4-348D-FB0600408FC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {951D9B15-BA77-F3E4-348D-FB0600408FC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {951D9B15-BA77-F3E4-348D-FB0600408FC6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -93,6 +99,7 @@ Global {CAAB99B7-218B-417A-B81A-84F30BAC1B4E} = {601B55BB-32DD-491A-816E-127FC48BCCEA} {34BEB19B-028A-4CD1-90EB-1E1A62C04671} = {601B55BB-32DD-491A-816E-127FC48BCCEA} {A85AE3BF-0E73-4032-9DE5-0EF9200AA43F} = {1D4D9594-0CD1-4103-AA2E-8589FBBD6EAC} + {951D9B15-BA77-F3E4-348D-FB0600408FC6} = {7F8BE28E-51E4-4818-B0C9-BDD357CCDAFA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C65ABA45-764F-4D07-A2A5-800115204611} diff --git a/Akade.IndexedSet/Akade.IndexedSet.csproj b/Akade.IndexedSet/Akade.IndexedSet.csproj index bc6cb1b..c5132ce 100644 --- a/Akade.IndexedSet/Akade.IndexedSet.csproj +++ b/Akade.IndexedSet/Akade.IndexedSet.csproj @@ -38,11 +38,13 @@ + + diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Fixes/Akade.IndexedSet.Analyzers.Fixes.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Fixes/Akade.IndexedSet.Analyzers.Fixes.csproj new file mode 100644 index 0000000..a9b7dca --- /dev/null +++ b/Analyzers/Akade.IndexedSet.Analyzers.Fixes/Akade.IndexedSet.Analyzers.Fixes.csproj @@ -0,0 +1,26 @@ + + + + netstandard2.0 + 13.0 + enable + false + true + false + true + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/Analyzers/Akade.IndexedSet.Analyzers/AkadeIndexedSetAnalyzersCodeFixProvider.cs b/Analyzers/Akade.IndexedSet.Analyzers.Fixes/AkadeIndexedSetAnalyzersCodeFixProvider.cs similarity index 99% rename from Analyzers/Akade.IndexedSet.Analyzers/AkadeIndexedSetAnalyzersCodeFixProvider.cs rename to Analyzers/Akade.IndexedSet.Analyzers.Fixes/AkadeIndexedSetAnalyzersCodeFixProvider.cs index 2845a12..de9fc27 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers/AkadeIndexedSetAnalyzersCodeFixProvider.cs +++ b/Analyzers/Akade.IndexedSet.Analyzers.Fixes/AkadeIndexedSetAnalyzersCodeFixProvider.cs @@ -1,5 +1,4 @@ - -using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -14,7 +13,7 @@ using System.Threading; using System.Threading.Tasks; -namespace Akade.IndexedSet.Analyzers; +namespace Akade.IndexedSet.Analyzers.Fixes; [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AkadeIndexedSetAnalyzersCodeFixProvider)), Shared] public class AkadeIndexedSetAnalyzersCodeFixProvider : CodeFixProvider diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index a4a120a..72b3543 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -23,7 +23,8 @@ - + + diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/AnalyzerAndCodeFixTests.cs b/Analyzers/Akade.IndexedSet.Analyzers.Test/AnalyzerAndCodeFixTests.cs index 3441329..08f7fcd 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/AnalyzerAndCodeFixTests.cs +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/AnalyzerAndCodeFixTests.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using VerifyCS = Akade.IndexedSet.Analyzers.Test.CSharpCodeFixVerifier< Akade.IndexedSet.Analyzers.IndexNamingRulesAnalyzer, - Akade.IndexedSet.Analyzers.AkadeIndexedSetAnalyzersCodeFixProvider>; + Akade.IndexedSet.Analyzers.Fixes.AkadeIndexedSetAnalyzersCodeFixProvider>; [assembly: TestDataSourceDiscovery(TestDataSourceDiscoveryOption.DuringExecution)] namespace Akade.IndexedSet.Analyzers.Test; diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/SampleCodeGenerator.cs b/Analyzers/Akade.IndexedSet.Analyzers.Test/SampleCodeGenerator.cs index ce6dab4..d234545 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/SampleCodeGenerator.cs +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/SampleCodeGenerator.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using VerifyCS = Akade.IndexedSet.Analyzers.Test.CSharpCodeFixVerifier< Akade.IndexedSet.Analyzers.IndexNamingRulesAnalyzer, - Akade.IndexedSet.Analyzers.AkadeIndexedSetAnalyzersCodeFixProvider>; + Akade.IndexedSet.Analyzers.Fixes.AkadeIndexedSetAnalyzersCodeFixProvider>; namespace Akade.IndexedSet.Analyzers.Test; internal static class SampleCodeGenerator diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleAnalyzerTests.cs b/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleAnalyzerTests.cs index 1ad4ebd..057b7d8 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleAnalyzerTests.cs +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleAnalyzerTests.cs @@ -3,7 +3,7 @@ using System.Threading.Tasks; using VerifyCS = Akade.IndexedSet.Analyzers.Test.CSharpCodeFixVerifier< Akade.IndexedSet.Analyzers.IndexNamingRulesAnalyzer, - Akade.IndexedSet.Analyzers.AkadeIndexedSetAnalyzersCodeFixProvider>; + Akade.IndexedSet.Analyzers.Fixes.AkadeIndexedSetAnalyzersCodeFixProvider>; namespace Akade.IndexedSet.Analyzers.Test; diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleCodeFixTests.cs b/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleCodeFixTests.cs index 592a8fb..aca21c6 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleCodeFixTests.cs +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/SimpleCodeFixTests.cs @@ -2,7 +2,7 @@ using System.Threading.Tasks; using VerifyCS = Akade.IndexedSet.Analyzers.Test.CSharpCodeFixVerifier< Akade.IndexedSet.Analyzers.IndexNamingRulesAnalyzer, - Akade.IndexedSet.Analyzers.AkadeIndexedSetAnalyzersCodeFixProvider>; + Akade.IndexedSet.Analyzers.Fixes.AkadeIndexedSetAnalyzersCodeFixProvider>; namespace Akade.IndexedSet.Analyzers.Test; diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj index 8d8ebca..14d104a 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Vsix/Akade.IndexedSet.Analyzers.Vsix.csproj @@ -33,6 +33,7 @@ + diff --git a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj index d85ba10..125f61b 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers/Akade.IndexedSet.Analyzers.csproj @@ -11,7 +11,6 @@ - all diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj index 88dd122..18701a4 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator/Akade.IndexedSet.InternalSourceGenerator.csproj @@ -11,7 +11,6 @@ - all From 32a9dab6182ca0383bff9fad64855bc18cb1120d Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:39:39 +0200 Subject: [PATCH 27/36] Update scorecard.yml (#221) --- .github/workflows/scorecard.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index a27919a..d376795 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -32,12 +32,12 @@ jobs: steps: - name: "Checkout code" - uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2 + uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 with: results_file: results.sarif results_format: sarif @@ -59,7 +59,7 @@ jobs: # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF # format to the repository Actions tab. - name: "Upload artifact" - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0 + uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # v4.6.1 with: name: SARIF file path: results.sarif @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4 + uses: github/codeql-action/upload-sarif@v3 with: sarif_file: results.sarif From 39f24716c21f2fb200c27900c59d588252e0dc19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Apr 2025 21:02:38 +0200 Subject: [PATCH 28/36] Bump dotnet-reportgenerator-globaltool from 5.4.4 to 5.4.5 (#216) Bumps [dotnet-reportgenerator-globaltool](https://github.com/danielpalme/ReportGenerator) from 5.4.4 to 5.4.5. - [Release notes](https://github.com/danielpalme/ReportGenerator/releases) - [Commits](https://github.com/danielpalme/ReportGenerator/compare/v5.4.4...v5.4.5) --- updated-dependencies: - dependency-name: dotnet-reportgenerator-globaltool dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index e921b3c..14e6964 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-reportgenerator-globaltool": { - "version": "5.4.4", + "version": "5.4.5", "commands": [ "reportgenerator" ] From 17ca20e549c4e1b4f9deb4d2ed16e550b2821f9e Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sun, 20 Apr 2025 21:12:48 +0200 Subject: [PATCH 29/36] Switching to LockRecursionPolicy.SupportsRecursion as it actually seems to perform slightly better - shaving some very small amount of time that can add up in hot pathes (#222) --- Akade.IndexedSet/Concurrency/ReaderWriterLockEx.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Akade.IndexedSet/Concurrency/ReaderWriterLockEx.cs b/Akade.IndexedSet/Concurrency/ReaderWriterLockEx.cs index 48694f0..860df96 100644 --- a/Akade.IndexedSet/Concurrency/ReaderWriterLockEx.cs +++ b/Akade.IndexedSet/Concurrency/ReaderWriterLockEx.cs @@ -2,7 +2,7 @@ internal sealed class ReaderWriterLockEx : IDisposable { - private readonly ReaderWriterLockSlim _lock = new(); + private readonly ReaderWriterLockSlim _lock = new(LockRecursionPolicy.SupportsRecursion); private readonly ReaderDisposable _readerDisposable; private readonly WriterDisposable _writerDisposable; From 24f2a0421b82cf38cc984a28eb34d5cd4486ee69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 20 Apr 2025 21:21:44 +0200 Subject: [PATCH 30/36] Bump MSTest.TestFramework from 3.7.3 to 3.8.3 (#213) * Bump MSTest.TestFramework from 3.7.3 to 3.8.3 Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) from 3.7.3 to 3.8.3. - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.7.3...v3.8.3) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * Updated other testing libraries --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: akade <16369295+akade@users.noreply.github.com> --- Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj | 6 +++--- .../Akade.IndexedSet.Analyzers.Test.csproj | 6 +++--- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index 17af911..fd12b01 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -9,9 +9,9 @@ - - - + + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj index 72b3543..9fb9b56 100644 --- a/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj +++ b/Analyzers/Akade.IndexedSet.Analyzers.Test/Akade.IndexedSet.Analyzers.Test.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -9,8 +9,8 @@ - - + + diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index a71f5d6..dcbe762 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -9,14 +9,14 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + From b2f10dfe0b84c8c976ec69f94d21300f1a998460 Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sun, 20 Apr 2025 21:29:02 +0200 Subject: [PATCH 31/36] v1.4.0-beta (#223) --- Akade.IndexedSet/Akade.IndexedSet.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Akade.IndexedSet/Akade.IndexedSet.csproj b/Akade.IndexedSet/Akade.IndexedSet.csproj index c5132ce..28e6d0f 100644 --- a/Akade.IndexedSet/Akade.IndexedSet.csproj +++ b/Akade.IndexedSet/Akade.IndexedSet.csproj @@ -7,7 +7,7 @@ - 1.3.0 + 1.4.0-beta Provides an In-Memory data structure, the IndexedSet, that allows to easily add indices to allow efficient querying. Currently supports unique and non-unique indices, range indices as well as fuzzy string matching for single attributes, compound or computed keys. Copyright © Akade 2024 Akade From 75c3b8a37b2e3e4abae57c5fb25839d10f1e0bb0 Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sun, 20 Apr 2025 21:40:46 +0200 Subject: [PATCH 32/36] Public API file: Moved unshipped to shipped (#224) --- Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt | 6 +++++- Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt b/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt index d2c2358..c2d0cff 100644 --- a/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt +++ b/Akade.IndexedSet/PublicAPI/PublicAPI.Shipped.txt @@ -149,4 +149,8 @@ virtual Akade.IndexedSet.IndexedSetBuilder.WithPrefixIndex(System.Func virtual Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func!>! keyAccessor, System.Collections.Generic.IComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! virtual Akade.IndexedSet.IndexedSetBuilder.WithRangeIndex(System.Func! keyAccessor, System.Collections.Generic.IComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! virtual Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func!>! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! -virtual Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! \ No newline at end of file +virtual Akade.IndexedSet.IndexedSetBuilder.WithUniqueIndex(System.Func! keyAccessor, System.Collections.Generic.IEqualityComparer? keyComparer = null, string? indexName = null) -> Akade.IndexedSet.IndexedSetBuilder! + +Akade.IndexedSet.StringUtilities.CharEqualityComparer +static Akade.IndexedSet.StringUtilities.CharEqualityComparer.IgnoreCase(System.Globalization.CultureInfo! cultureInfo) -> System.Collections.Generic.IEqualityComparer! +static Akade.IndexedSet.StringUtilities.CharEqualityComparer.OrdinalIgnoreCase.get -> System.Collections.Generic.IEqualityComparer! diff --git a/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt b/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt index a6f6301..ab058de 100644 --- a/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt +++ b/Akade.IndexedSet/PublicAPI/PublicAPI.Unshipped.txt @@ -1,4 +1 @@ #nullable enable -Akade.IndexedSet.StringUtilities.CharEqualityComparer -static Akade.IndexedSet.StringUtilities.CharEqualityComparer.IgnoreCase(System.Globalization.CultureInfo! cultureInfo) -> System.Collections.Generic.IEqualityComparer! -static Akade.IndexedSet.StringUtilities.CharEqualityComparer.OrdinalIgnoreCase.get -> System.Collections.Generic.IEqualityComparer! From 6ee3082ae68e16409c4ea94ce83b057f0a786666 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 22:52:03 +0200 Subject: [PATCH 33/36] Bump MSTest.TestFramework and Verify.MSTest (#227) Bumps [MSTest.TestFramework](https://github.com/microsoft/testfx) and [Verify.MSTest](https://github.com/VerifyTests/Verify). These dependencies needed to be updated together. Updates `MSTest.TestFramework` from 3.8.3 to 3.8.3 - [Release notes](https://github.com/microsoft/testfx/releases) - [Changelog](https://github.com/microsoft/testfx/blob/main/docs/Changelog.md) - [Commits](https://github.com/microsoft/testfx/compare/v3.8.3...v3.8.3) Updates `Verify.MSTest` from 29.2.0 to 30.0.0 - [Release notes](https://github.com/VerifyTests/Verify/releases) - [Commits](https://github.com/VerifyTests/Verify/compare/29.2.0...30.0.0) --- updated-dependencies: - dependency-name: MSTest.TestFramework dependency-version: 3.8.3 dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: Verify.MSTest dependency-version: 30.0.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../Akade.IndexedSet.InternalSourceGenerator.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj index dcbe762..d7bc013 100644 --- a/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj +++ b/SourceGenerator/Akade.IndexedSet.InternalSourceGenerator.Tests/Akade.IndexedSet.InternalSourceGenerator.Tests.csproj @@ -16,7 +16,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + From 6443b41c23779e6e0ee850936e173ae439b66a10 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 22:52:37 +0200 Subject: [PATCH 34/36] Bump dotnet-reportgenerator-globaltool from 5.4.5 to 5.4.6 (#226) Bumps [dotnet-reportgenerator-globaltool](https://github.com/danielpalme/ReportGenerator) from 5.4.5 to 5.4.6. - [Release notes](https://github.com/danielpalme/ReportGenerator/releases) - [Commits](https://github.com/danielpalme/ReportGenerator/compare/v5.4.5...v5.4.6) --- updated-dependencies: - dependency-name: dotnet-reportgenerator-globaltool dependency-version: 5.4.6 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 14e6964..f47c9c6 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "dotnet-reportgenerator-globaltool": { - "version": "5.4.5", + "version": "5.4.6", "commands": [ "reportgenerator" ] From 4f4b77e367b6cfa2fb3673c8e14d96c99abc6121 Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Sun, 1 Jun 2025 21:00:33 +0200 Subject: [PATCH 35/36] Fixed all "messages" diagnostics (#228) --- .editorconfig | 2 +- .../RehydrationBenchmark.cs | 8 ++--- Akade.IndexedSet.Tests/.editorconfig | 4 +++ .../Akade.IndexedSet.Tests.csproj | 10 ++++++- .../BaseIndexTest.Contains.cs | 4 +-- .../BaseIndexTest.RangeQueries.cs | 30 +++++++++---------- .../CommonIndexTests/BaseIndexTest.Single.cs | 6 ++-- .../BaseIndexTest.StartsWith.cs | 4 +-- .../CommonIndexTests/BaseIndexTest.cs | 2 +- .../DataStructures/BinaryHeapTests.cs | 1 - .../DataStructures/SuffixTrieTests.cs | 1 - .../DataStructures/TrieTests.cs | 1 - Akade.IndexedSet.Tests/FullTextIndices.cs | 2 +- Akade.IndexedSet.Tests/GeneralTests.cs | 4 +-- Akade.IndexedSet.Tests/NonUniqueIndices.cs | 2 +- Akade.IndexedSet.Tests/PrefixIndices.cs | 2 +- Akade.IndexedSet.Tests/RangeIndices.cs | 4 +-- .../Samples/Appointments/AppointmentSample.cs | 3 +- .../Samples/Graph/GraphSample.cs | 1 - Akade.IndexedSet.Tests/TestsWithoutIndices.cs | 6 ++-- Akade.IndexedSet.Tests/UniqueIndices.cs | 6 ++-- 21 files changed, 54 insertions(+), 49 deletions(-) create mode 100644 Akade.IndexedSet.Tests/.editorconfig diff --git a/.editorconfig b/.editorconfig index 6c50859..20ffa5e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -127,4 +127,4 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion dotnet_style_prefer_compound_assignment = true:suggestion dotnet_style_prefer_simplified_interpolation = true:suggestion dotnet_style_namespace_match_folder = true:suggestion -dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion \ No newline at end of file +dotnet_style_prefer_collection_expression = when_types_loosely_match:silent \ No newline at end of file diff --git a/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/RehydrationBenchmark.cs b/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/RehydrationBenchmark.cs index bb33ab8..5be44e9 100644 --- a/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/RehydrationBenchmark.cs +++ b/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/RehydrationBenchmark.cs @@ -1,9 +1,7 @@ -using Akade.IndexedSet.Benchmarks.RealWorld.EventSourcedAggregateCache; -using Akade.IndexedSet.Concurrency; +using Akade.IndexedSet.Concurrency; using BenchmarkDotNet.Attributes; -using System.Collections.Immutable; -namespace Akade.IndexedSet.Benchmarks.RealWorld; +namespace Akade.IndexedSet.Benchmarks.RealWorld.EventSourcedAggregateCache; [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net80)] [SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net90)] @@ -22,5 +20,5 @@ public void Rehydration() _set.Clear(); EventHandlers.HandleEvents(_set, DataGenerator.GenerateEvents(30000)); } - + } diff --git a/Akade.IndexedSet.Tests/.editorconfig b/Akade.IndexedSet.Tests/.editorconfig new file mode 100644 index 0000000..af09177 --- /dev/null +++ b/Akade.IndexedSet.Tests/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CA1861: Avoid constant arrays as arguments +dotnet_diagnostic.CA1861.severity = silent \ No newline at end of file diff --git a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj index fd12b01..2123fe8 100644 --- a/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj +++ b/Akade.IndexedSet.Tests/Akade.IndexedSet.Tests.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0 @@ -22,4 +22,12 @@ + + + + + + + + diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs index 5d3fb05..2c148e9 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Contains.cs @@ -9,7 +9,7 @@ public void Contains_based_methods_should_throw_if_not_supported() if (!SupportsContainsQueries) { TIndex index = CreateIndex(); - _ = Assert.ThrowsException(() => index.Contains(GetNotExistingKey().ToString())); + _ = Assert.ThrowsExactly(() => index.Contains(GetNotExistingKey().ToString())); } } @@ -67,7 +67,7 @@ public void FuzzyContains_based_methods_should_throw_if_not_supported() if (!SupportsContainsQueries) { TIndex index = CreateIndex(); - _ = Assert.ThrowsException(() => index.FuzzyContains(GetNotExistingKey().ToString(), 1)); + _ = Assert.ThrowsExactly(() => index.FuzzyContains(GetNotExistingKey().ToString(), 1)); } } diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs index b2276c6..d9bba97 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.RangeQueries.cs @@ -10,17 +10,17 @@ public void Range_based_methods_should_throw_if_not_supported() if (!SupportsRangeBasedQueries) { TIndex index = CreateIndex(); - _ = Assert.ThrowsException(() => index.Range(GetNotExistingKey(), GetNotExistingKey(), false, false)); - _ = Assert.ThrowsException(() => index.LessThan(GetNotExistingKey())); - _ = Assert.ThrowsException(() => index.LessThanOrEqual(GetNotExistingKey())); - _ = Assert.ThrowsException(() => index.GreaterThan(GetNotExistingKey())); - _ = Assert.ThrowsException(() => index.GreaterThanOrEqual(GetNotExistingKey())); - _ = Assert.ThrowsException(() => index.Min()); - _ = Assert.ThrowsException(() => index.MinBy()); - _ = Assert.ThrowsException(() => index.Max()); - _ = Assert.ThrowsException(() => index.MaxBy()); - _ = Assert.ThrowsException(() => index.OrderBy(0)); - _ = Assert.ThrowsException(() => index.OrderByDescending(0)); + _ = Assert.ThrowsExactly(() => index.Range(GetNotExistingKey(), GetNotExistingKey(), false, false)); + _ = Assert.ThrowsExactly(() => index.LessThan(GetNotExistingKey())); + _ = Assert.ThrowsExactly(() => index.LessThanOrEqual(GetNotExistingKey())); + _ = Assert.ThrowsExactly(() => index.GreaterThan(GetNotExistingKey())); + _ = Assert.ThrowsExactly(() => index.GreaterThanOrEqual(GetNotExistingKey())); + _ = Assert.ThrowsExactly(() => index.Min()); + _ = Assert.ThrowsExactly(() => index.MinBy()); + _ = Assert.ThrowsExactly(() => index.Max()); + _ = Assert.ThrowsExactly(() => index.MaxBy()); + _ = Assert.ThrowsExactly(() => index.OrderBy(0)); + _ = Assert.ThrowsExactly(() => index.OrderByDescending(0)); } } @@ -91,8 +91,8 @@ public void MaxMin_throw_if_the_set_is_empty() if (SupportsRangeBasedQueries) { TIndex index = CreateIndex(); - _ = Assert.ThrowsException(() => index.Min()); - _ = Assert.ThrowsException(() => index.Max()); + _ = Assert.ThrowsExactly(() => index.Min()); + _ = Assert.ThrowsExactly(() => index.Max()); } } @@ -144,7 +144,7 @@ public void OrderBy_throws_if_skip_value_is_too_large() TIndex index = CreateIndex(); TElement[] data = GetUniqueData(); AddElements(data, index); - _ = Assert.ThrowsException(() => index.OrderBy(data.Length + 1).Any()); + _ = Assert.ThrowsExactly(() => _ = index.OrderBy(data.Length + 1).Any()); } } @@ -183,7 +183,7 @@ public void OrderByDescending_throws_if_skip_value_is_too_large() TIndex index = CreateIndex(); TElement[] data = GetUniqueData(); AddElements(data, index); - _ = Assert.ThrowsException(() => index.OrderByDescending(data.Length + 1).Any()); + _ = Assert.ThrowsExactly(() => _ = index.OrderByDescending(data.Length + 1).Any()); } } diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs index a4473a2..65e53a6 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.Single.cs @@ -17,14 +17,14 @@ public void Single_should_return_matching_element() public void Single_should_throw_if_empty() { TIndex index = CreateIndex(); - _ = Assert.ThrowsException(() => index.Single(GetNotExistingKey())); + _ = Assert.ThrowsExactly(() => index.Single(GetNotExistingKey())); } [BaseTestMethod] public void Single_should_throw_if_not_found() { TIndex index = CreateIndexWithData(GetUniqueData()); - _ = Assert.ThrowsException(() => index.Single(GetNotExistingKey())); + _ = Assert.ThrowsExactly(() => index.Single(GetNotExistingKey())); } [BaseTestMethod] @@ -35,7 +35,7 @@ public void Single_should_throw_if_multiple_entries_are_found() TElement[] data = GetNonUniqueData(); TIndex index = CreateIndexWithData(data); TIndexKey nonUniqueKey = data.GroupByWithSortBasedFallback(_keyAccessor, _comparer).Where(x => x.Count() > 1).First().Key; - _ = Assert.ThrowsException(() => index.Single(nonUniqueKey)); + _ = Assert.ThrowsExactly(() => index.Single(nonUniqueKey)); } } diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs index cdab460..d69a323 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.StartsWith.cs @@ -9,7 +9,7 @@ public void StartsWith_based_methods_should_throw_if_not_supported() if (!SupportsStartsWithQueries) { TIndex index = CreateIndex(); - _ = Assert.ThrowsException(() => index.StartsWith(GetNotExistingKey().ToString())); + _ = Assert.ThrowsExactly(() => index.StartsWith(GetNotExistingKey().ToString())); } } @@ -68,7 +68,7 @@ public void FuzzyStartsWith_based_methods_should_throw_if_not_supported() if (!SupportsStartsWithQueries) { TIndex index = CreateIndex(); - _ = Assert.ThrowsException(() => index.FuzzyStartsWith(GetNotExistingKey().ToString(), 1)); + _ = Assert.ThrowsExactly(() => index.FuzzyStartsWith(GetNotExistingKey().ToString(), 1)); } } diff --git a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs index 3caf6b4..602b9c5 100644 --- a/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs +++ b/Akade.IndexedSet.Tests/CommonIndexTests/BaseIndexTest.cs @@ -45,7 +45,7 @@ public void Adding_unique_data_should_throw_if_nonunique_keys_are_not_supported( { if (!SupportsNonUniqueKeys) { - _ = Assert.ThrowsException(() => CreateIndexWithData(GetNonUniqueData())); + _ = Assert.ThrowsExactly(() => CreateIndexWithData(GetNonUniqueData())); } } } diff --git a/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs b/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs index ccb1495..42d8dae 100644 --- a/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs +++ b/Akade.IndexedSet.Tests/DataStructures/BinaryHeapTests.cs @@ -5,7 +5,6 @@ namespace Akade.IndexedSet.Tests.DataStructures; [TestClass] -[SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "In unit tests: readability > performance")] public class BinaryHeapTests { private BinaryHeap _heap = null!; diff --git a/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs b/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs index 59b2dba..b2e6a44 100644 --- a/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs +++ b/Akade.IndexedSet.Tests/DataStructures/SuffixTrieTests.cs @@ -5,7 +5,6 @@ namespace Akade.IndexedSet.Tests.DataStructures; [TestClass] -[SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "In unit tests: readability > performance")] public class SuffixTrieTests { private SuffixTrie _trie = default!; diff --git a/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs b/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs index 632f6d7..b0a8fe5 100644 --- a/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs +++ b/Akade.IndexedSet.Tests/DataStructures/TrieTests.cs @@ -6,7 +6,6 @@ namespace Akade.IndexedSet.Tests.DataStructures; [TestClass] -[SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "In unit tests: readability > performance")] public class TrieTests { private readonly Trie _trie = GetAnimalTrie(); diff --git a/Akade.IndexedSet.Tests/FullTextIndices.cs b/Akade.IndexedSet.Tests/FullTextIndices.cs index 3a5f175..95770b4 100644 --- a/Akade.IndexedSet.Tests/FullTextIndices.cs +++ b/Akade.IndexedSet.Tests/FullTextIndices.cs @@ -56,7 +56,7 @@ public void Single_item_retrieval_works() [TestMethod] public void Single_item_retrieval_throws_exception_if_there_is_more_than_one_result() { - _ = Assert.ThrowsException(() => _indexedSet.AssertSingleItem(x => x.Category, _bonobo)); + _ = Assert.ThrowsExactly(() => _indexedSet.AssertSingleItem(x => x.Category, _bonobo)); } [TestMethod] diff --git a/Akade.IndexedSet.Tests/GeneralTests.cs b/Akade.IndexedSet.Tests/GeneralTests.cs index 7f49541..b8477ea 100644 --- a/Akade.IndexedSet.Tests/GeneralTests.cs +++ b/Akade.IndexedSet.Tests/GeneralTests.cs @@ -71,7 +71,7 @@ public void Adding_a_conflicting_item_should_keep_the_set_in_a_consistent_state( .WithUniqueIndex(x => x.IntProperty) .Build(); - _ = Assert.ThrowsException(() => set.Add(_b)); + _ = Assert.ThrowsExactly(() => set.Add(_b)); Assert.IsFalse(set.TryGetSingle(x => x.GuidProperty, _b.GuidProperty, out _)); Assert.AreEqual(3, set.Count); Assert.IsFalse(set.Contains(_b)); @@ -86,7 +86,7 @@ public void Adding_multiple_items_with_a_conflicting_item_should_keep_the_set_in .Build(); TestData[] dataToAdd = [_d, _b, _e]; - _ = Assert.ThrowsException(() => set.AddRange(dataToAdd)); + _ = Assert.ThrowsExactly(() => set.AddRange(dataToAdd)); Assert.AreEqual(2, set.Count); foreach (TestData item in dataToAdd) diff --git a/Akade.IndexedSet.Tests/NonUniqueIndices.cs b/Akade.IndexedSet.Tests/NonUniqueIndices.cs index 2ee61e2..13a8cfc 100644 --- a/Akade.IndexedSet.Tests/NonUniqueIndices.cs +++ b/Akade.IndexedSet.Tests/NonUniqueIndices.cs @@ -54,7 +54,7 @@ public void Retrieval_via_secondary_string_key_returns_correct_items() [TestMethod] public void Range_queries_throw_exception() { - _ = Assert.ThrowsException(() => _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList()); + _ = Assert.ThrowsExactly(() => _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList()); } [TestMethod] diff --git a/Akade.IndexedSet.Tests/PrefixIndices.cs b/Akade.IndexedSet.Tests/PrefixIndices.cs index fff3f10..0d4d0ec 100644 --- a/Akade.IndexedSet.Tests/PrefixIndices.cs +++ b/Akade.IndexedSet.Tests/PrefixIndices.cs @@ -56,7 +56,7 @@ public void Single_item_retrieval_works() [TestMethod] public void Single_item_retrieval_throws_exception_if_there_is_more_than_one_result() { - _ = Assert.ThrowsException(() => _indexedSet.AssertSingleItem(x => x.Category, _bonobo)); + _ = Assert.ThrowsExactly(() => _indexedSet.AssertSingleItem(x => x.Category, _bonobo)); } [TestMethod] diff --git a/Akade.IndexedSet.Tests/RangeIndices.cs b/Akade.IndexedSet.Tests/RangeIndices.cs index 23b5e8b..67d69c8 100644 --- a/Akade.IndexedSet.Tests/RangeIndices.cs +++ b/Akade.IndexedSet.Tests/RangeIndices.cs @@ -181,8 +181,8 @@ public void Min_and_max_throws_for_empty_set() .WithRangeIndex(x => x.IntProperty) .Build(); - _ = Assert.ThrowsException(() => _indexedSet.Min(x => x.IntProperty)); - _ = Assert.ThrowsException(() => _indexedSet.Max(x => x.IntProperty)); + _ = Assert.ThrowsExactly(() => _indexedSet.Min(x => x.IntProperty)); + _ = Assert.ThrowsExactly(() => _indexedSet.Max(x => x.IntProperty)); } [TestMethod] diff --git a/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs b/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs index d0e97c4..ccb7cba 100644 --- a/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs +++ b/Akade.IndexedSet.Tests/Samples/Appointments/AppointmentSample.cs @@ -5,7 +5,6 @@ namespace Akade.IndexedSet.Tests.Samples.Appointments; [TestClass] -[SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "In unit tests: readability > performance")] public class AppointmentSample { private readonly DateTime _todayDateTime; @@ -91,7 +90,7 @@ public void Key_expression_does_matter() { // the index was created with x => x.Owner // Hence, even though the indexed key is the same, the name for the index is not - _ = Assert.ThrowsException(() => _ = _appointments.Single(t => t.Owner, "Test")); + _ = Assert.ThrowsExactly(() => _ = _appointments.Single(t => t.Owner, "Test")); } [TestMethod] diff --git a/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs b/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs index 1184a95..0b2a9c5 100644 --- a/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs +++ b/Akade.IndexedSet.Tests/Samples/Graph/GraphSample.cs @@ -5,7 +5,6 @@ namespace Akade.IndexedSet.Tests.Samples.Graph; [TestClass] -[SuppressMessage("Performance", "CA1861:Avoid constant arrays as arguments", Justification = "In unit tests: readability > performance")] public class GraphSample { [TestMethod] diff --git a/Akade.IndexedSet.Tests/TestsWithoutIndices.cs b/Akade.IndexedSet.Tests/TestsWithoutIndices.cs index 80c120c..191cb3b 100644 --- a/Akade.IndexedSet.Tests/TestsWithoutIndices.cs +++ b/Akade.IndexedSet.Tests/TestsWithoutIndices.cs @@ -75,14 +75,14 @@ public void Removing_multiple_items_removes_the_correct_items() public void Missing_primarykey_throws() { AddAll(); - _ = Assert.ThrowsException(() => _ = _indexedSet[42]); + _ = Assert.ThrowsExactly(() => _ = _indexedSet[42]); } [TestMethod] public void Missing_index_throws() { AddAll(); - _ = Assert.ThrowsException(() => _ = _indexedSet.Single(x => x.IntProperty, _a.IntProperty)); + _ = Assert.ThrowsExactly(() => _ = _indexedSet.Single(x => x.IntProperty, _a.IntProperty)); } [TestMethod] @@ -96,7 +96,7 @@ public void Adding_duplicate_item_returns_false() public void Adding_duplicate_primary_key_throws() { AddAll(); - _ = Assert.ThrowsException(() => _ = _indexedSet.Add(new TestData(0, 0, Guid.Empty, ""))); + _ = Assert.ThrowsExactly(() => _ = _indexedSet.Add(new TestData(0, 0, Guid.Empty, ""))); } private void AddAll() diff --git a/Akade.IndexedSet.Tests/UniqueIndices.cs b/Akade.IndexedSet.Tests/UniqueIndices.cs index 4a9795e..9409f85 100644 --- a/Akade.IndexedSet.Tests/UniqueIndices.cs +++ b/Akade.IndexedSet.Tests/UniqueIndices.cs @@ -63,13 +63,13 @@ public void Retrieval_via_compound_key_returns_correct_items() [TestMethod] public void Range_queries_throw_exception() { - _ = Assert.ThrowsException(() => _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList()); + _ = Assert.ThrowsExactly(() => _ = _indexedSet.Range(x => x.IntProperty, 5, 10).ToList()); } [TestMethod] public void Adding_duplicate_key_throws() { - _ = Assert.ThrowsException(() => _ = _indexedSet.Add(new TestData(5, 10, Guid.NewGuid(), "ew"))); + _ = Assert.ThrowsExactly(() => _ = _indexedSet.Add(new TestData(5, 10, Guid.NewGuid(), "ew"))); } [TestMethod] @@ -116,6 +116,6 @@ public void Custom_comparer_allows_modifying_comparison() Assert.IsTrue(indexedSet.TryGetSingle(x => x, "A", out string? a)); Assert.AreEqual("a", a); - _ = Assert.ThrowsException(() => indexedSet.Add("C")); + _ = Assert.ThrowsExactly(() => indexedSet.Add("C")); } } From 5b719d4ec39e07a342f6e7a51778dac8ba7dd59c Mon Sep 17 00:00:00 2001 From: Alex <16369295+akade@users.noreply.github.com> Date: Mon, 2 Jun 2025 17:53:05 +0200 Subject: [PATCH 36/36] v1.4 (#229) * v1.4 - Release * Newest benchmark numbers --- .../EventHandlers.cs | 11 +- Akade.IndexedSet/Akade.IndexedSet.csproj | 2 +- Akade.IndexedSet/IndexedSet.cs | 12 +- README.md | 6 +- docs/Benchmarks.md | 147 +++++++++--------- 5 files changed, 88 insertions(+), 90 deletions(-) diff --git a/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/EventHandlers.cs b/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/EventHandlers.cs index d45ac58..594e41d 100644 --- a/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/EventHandlers.cs +++ b/Akade.IndexedSet.Benchmarks/RealWorld/EventSourcedAggregateCache/EventHandlers.cs @@ -1,22 +1,23 @@ using Akade.IndexedSet.Concurrency; -using System.Collections.Immutable; +using System.Collections.Frozen; namespace Akade.IndexedSet.Benchmarks.RealWorld.EventSourcedAggregateCache; internal static class EventHandlers { - private static readonly Dictionary, AggregateEvent>> _eventHandler = new() + private static readonly FrozenDictionary, AggregateEvent>> _eventHandler = + new Dictionary, AggregateEvent>>() { { typeof(AggregateAdded), (state, @event) => HandleEvent(state, (AggregateAdded)@event) }, { typeof(AggregateShared), (state, @event) => HandleEvent(state, (AggregateShared)@event) } - }; + }.ToFrozenDictionary(); private static void HandleEvent(ConcurrentIndexedSet state, AggregateShared @event) { if (state.TryGetSingle(@event.Id, out Aggregate? existingAggregate)) { - _ = state.Update(existingAggregate, aggregate => aggregate with + _ = state.Update(existingAggregate, @event, static (aggregate, @event) => aggregate with { - SharedWith = existingAggregate.SharedWith.Add(@event.SharedWith) + SharedWith = aggregate.SharedWith.Add(@event.SharedWith) }); } } diff --git a/Akade.IndexedSet/Akade.IndexedSet.csproj b/Akade.IndexedSet/Akade.IndexedSet.csproj index 28e6d0f..5aa4716 100644 --- a/Akade.IndexedSet/Akade.IndexedSet.csproj +++ b/Akade.IndexedSet/Akade.IndexedSet.csproj @@ -7,7 +7,7 @@ - 1.4.0-beta + 1.4.0 Provides an In-Memory data structure, the IndexedSet, that allows to easily add indices to allow efficient querying. Currently supports unique and non-unique indices, range indices as well as fuzzy string matching for single attributes, compound or computed keys. Copyright © Akade 2024 Akade diff --git a/Akade.IndexedSet/IndexedSet.cs b/Akade.IndexedSet/IndexedSet.cs index 5117477..88522ef 100644 --- a/Akade.IndexedSet/IndexedSet.cs +++ b/Akade.IndexedSet/IndexedSet.cs @@ -365,7 +365,7 @@ public IEnumerable LessThanOrEqual(Func - /// Searches for elements that have keys that are greator than the supplied value. + /// Searches for elements that have keys that are greater than the supplied value. /// /// The type of the index key /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. @@ -381,7 +381,7 @@ public IEnumerable GreaterThan(Func in } /// - /// Searches for elements that have keys that are greator than the supplied value. + /// Searches for elements that have keys that are greater than the supplied value. /// /// The type of the index key /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. @@ -397,7 +397,7 @@ public IEnumerable GreaterThan(Func - /// Searches for elements that have keys that are greator or equal than the supplied value. + /// Searches for elements that have keys that are greater or equal than the supplied value. /// /// The type of the index key /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. @@ -413,7 +413,7 @@ public IEnumerable GreaterThanOrEqual(Func - /// Searches for elements that have keys that are greator or equal than the supplied value. + /// Searches for elements that have keys that are greater or equal than the supplied value. /// /// The type of the index key /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. @@ -698,7 +698,7 @@ public IEnumerable Contains(Func> indexA } /// - /// Returns all elements that contain the given char sequence or a simalar one. + /// Returns all elements that contain the given char sequence or a similar one. /// /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. /// Is passed to using . @@ -713,7 +713,7 @@ public IEnumerable FuzzyContains(Func indexAccessor, } /// - /// Returns all elements that contain the given char sequence or a simalar one. + /// Returns all elements that contain the given char sequence or a similar one. /// /// Accessor for the indexed property. The expression as a string is used as an identifier for the index. Hence, the convention is to always use x as an identifier. /// Is passed to using . diff --git a/README.md b/README.md index 9d7c213..272bc12 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ # Akade.IndexedSet ![.Net Version](https://img.shields.io/badge/dynamic/xml?color=%23512bd4&label=version&query=%2F%2FTargetFrameworks%5B1%5D&url=https://raw.githubusercontent.com/akade/Akade.IndexedSet/main/Akade.IndexedSet/Akade.IndexedSet.csproj&logo=.net) -[![CI Build](https://github.com/akade/Akade.IndexedSet/actions/workflows/ci-build.yml/badge.svg?branch=master)](https://github.com/akade/Akade.IndexedSet/actions/workflows/ci-build.yml) +[![CI Build](https://github.com/akade/Akade.IndexedSet/actions/workflows/ci-build.yml/badge.svg?branch=main)](https://github.com/akade/Akade.IndexedSet/actions/workflows/ci-build.yml) [![NuGet version (Akade.IndexedSet)](https://img.shields.io/nuget/v/Akade.IndexedSet.svg)](https://www.nuget.org/packages/Akade.IndexedSet/) [![MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/akade/Akade.IndexedSet#readme) -[![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-43bc00?logo=readme&logoColor=white)](https://www.robiniadocs.com/d/akadeinde/api/Akade.IndexedSet.IndexedSet-1.html) - A convenient data structure supporting efficient in-memory indexing and querying, including range queries and fuzzy string matching. In a nutshell, it allows you to write LINQ-like queries *without* enumerating through the entire list. If you are currently completely enumerating @@ -29,7 +27,7 @@ through your data, expect huge [speedups](docs/Benchmarks.md) and much better sc - [FAQs](#faqs) - [How do I use multiple index types for the same property?](#how-do-i-use-multiple-index-types-for-the-same-property) - [How do I update key values if the elements are already in the set?](#how-do-i-update-key-values-if-the-elements-are-already-in-the-set) - - [How do I do case-insensitive (fuzzy) string matching (Prefix, FullTextIndex)?](#how-do-i-do-case-insensitve-fuzzy-string-matching-prefix-fulltextindex) + - [How do I do case-insensitve (fuzzy) string matching (Prefix, FullTextIndex)?](#how-do-i-do-case-insensitve-fuzzy-string-matching-prefix-fulltextindex) - [Roadmap](#roadmap) diff --git a/docs/Benchmarks.md b/docs/Benchmarks.md index b0b8b5d..07bc450 100644 --- a/docs/Benchmarks.md +++ b/docs/Benchmarks.md @@ -3,31 +3,30 @@ Benchmarks measured on 27.07.2022: ## Environment ``` ini -BenchmarkDotNet v0.13.12, Windows 11 (10.0.22631.3447/23H2/2023Update/SunValley3) +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.4061) AMD Ryzen 9 5900X, 1 CPU, 24 logical and 12 physical cores -.NET SDK 8.0.300-preview.24203.14 - [Host] : .NET 8.0.4 (8.0.424.16909), X64 RyuJIT AVX2 - .NET 6.0 : .NET 6.0.29 (6.0.2924.17105), X64 RyuJIT AVX2 - .NET 8.0 : .NET 8.0.4 (8.0.424.16909), X64 RyuJIT AVX2 +.NET SDK 9.0.300 + [Host] : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2 + .NET 8.0 : .NET 8.0.16 (8.0.1625.21506), X64 RyuJIT AVX2 + .NET 9.0 : .NET 9.0.5 (9.0.525.21509), X64 RyuJIT AVX2 ``` All benchmarks are currenlty using *1k elements*. Benchmarks showing the scaling is on the roadmap - expect IndexedSet to scale much -much better than the naive LINQ Queries. As .NET 8.0 brings in [a bunch of performance improvements](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-8/), -the benchmarks are run against both .NET 6 and 8. -.NET 7 is out of support and hence, no longer an explicit target (but the library can be consumed from .NET 7 without any issue). +much better than the naive LINQ Queries. As .NET 9.0 brings in [a bunch of performance improvements](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-9/), +the benchmarks are run against both .NET 8 and 9. ## Unique-Index -| Method | Job | Runtime | Mean | Error | StdDev | Ratio | Code Size | Gen0 | Allocated | Alloc Ratio | -|----------------------------- |--------- |--------- |------------:|------------:|----------:|------:|----------:|-------:|----------:|------------:| -| Unqiue_Linq | .NET 8.0 | .NET 8.0 | 92,618.7 ns | 1,077.03 ns | 954.76 ns | 1.000 | 1,040 B | 0.7324 | 12800 B | 1.00 | -| Unique_Dictionary | .NET 8.0 | .NET 8.0 | 373.2 ns | 0.78 ns | 0.65 ns | 0.004 | 505 B | - | - | 0.00 | -| Unique_IndexedSet_PrimaryKey | .NET 8.0 | .NET 8.0 | 788.6 ns | 0.76 ns | 0.67 ns | 0.009 | 1,631 B | - | - | 0.00 | -| Unique_IndexedSet_Single | .NET 8.0 | .NET 8.0 | 803.5 ns | 1.31 ns | 1.02 ns | 0.009 | 1,387 B | - | - | 0.00 | -| | | | | | | | | | | | -| Unqiue_Linq | .NET 9.0 | .NET 9.0 | 10,730.4 ns | 25.36 ns | 22.48 ns | 1.00 | 260 B | 0.5188 | 8800 B | 1.00 | -| Unique_Dictionary | .NET 9.0 | .NET 9.0 | 356.9 ns | 0.30 ns | 0.27 ns | 0.03 | 485 B | - | - | 0.00 | -| Unique_IndexedSet_PrimaryKey | .NET 9.0 | .NET 9.0 | 810.5 ns | 1.28 ns | 1.14 ns | 0.08 | 754 B | - | - | 0.00 | -| Unique_IndexedSet_Single | .NET 9.0 | .NET 9.0 | 746.7 ns | 1.23 ns | 1.02 ns | 0.07 | 843 B | - | - | 0.00 | +| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Code Size | Gen0 | Allocated | Alloc Ratio | +|----------------------------- |--------- |--------- |------------:|------------:|------------:|------:|--------:|----------:|-------:|----------:|------------:| +| Unqiue_Linq | .NET 8.0 | .NET 8.0 | 95,150.6 ns | 1,856.80 ns | 3,441.70 ns | 1.001 | 0.05 | 1,040 B | 0.7324 | 12800 B | 1.00 | +| Unique_Dictionary | .NET 8.0 | .NET 8.0 | 360.3 ns | 0.49 ns | 0.43 ns | 0.004 | 0.00 | 514 B | - | - | 0.00 | +| Unique_IndexedSet_PrimaryKey | .NET 8.0 | .NET 8.0 | 789.7 ns | 1.76 ns | 1.64 ns | 0.008 | 0.00 | 1,622 B | - | - | 0.00 | +| Unique_IndexedSet_Single | .NET 8.0 | .NET 8.0 | 798.9 ns | 1.95 ns | 1.83 ns | 0.008 | 0.00 | 1,387 B | - | - | 0.00 | +| | | | | | | | | | | | | +| Unqiue_Linq | .NET 9.0 | .NET 9.0 | 9,734.1 ns | 27.71 ns | 25.92 ns | 1.00 | 0.00 | 260 B | 0.5188 | 8800 B | 1.00 | +| Unique_Dictionary | .NET 9.0 | .NET 9.0 | 358.5 ns | 0.62 ns | 0.58 ns | 0.04 | 0.00 | 485 B | - | - | 0.00 | +| Unique_IndexedSet_PrimaryKey | .NET 9.0 | .NET 9.0 | 850.3 ns | 1.93 ns | 1.80 ns | 0.09 | 0.00 | 754 B | - | - | 0.00 | +| Unique_IndexedSet_Single | .NET 9.0 | .NET 9.0 | 784.0 ns | 1.93 ns | 1.71 ns | 0.08 | 0.00 | 843 B | - | - | 0.00 | > ℹ️ Note that manually maintaining a dictionary is *currently* faster but only if the executing class has direct access > to the dictionary. Ideas to bring it on par are being explored for scenarios with very tight loops around dictionary lookup. @@ -35,76 +34,76 @@ the benchmarks are run against both .NET 6 and 8. ## MultiValue-Index | Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Code Size | Allocated | Alloc Ratio | |---------------------- |--------- |--------- |----------:|----------:|----------:|------:|--------:|-------:|----------:|----------:|------------:| -| MultiValue_Linq | .NET 8.0 | .NET 8.0 | 34.851 μs | 0.6304 μs | 1.1206 μs | 1.00 | 0.04 | 0.0610 | 3,832 B | 1680 B | 1.00 | -| Multivalue_Lookup | .NET 8.0 | .NET 8.0 | 8.323 μs | 0.0343 μs | 0.0286 μs | 0.24 | 0.01 | 0.0153 | 2,879 B | 360 B | 0.21 | -| Multivalue_IndexedSet | .NET 8.0 | .NET 8.0 | 8.814 μs | 0.1749 μs | 0.2148 μs | 0.25 | 0.01 | 0.0153 | 3,623 B | 360 B | 0.21 | +| MultiValue_Linq | .NET 8.0 | .NET 8.0 | 39.322 μs | 0.7690 μs | 1.3058 μs | 1.00 | 0.05 | 0.0610 | 3,844 B | 1680 B | 1.00 | +| Multivalue_Lookup | .NET 8.0 | .NET 8.0 | 8.726 μs | 0.0972 μs | 0.0909 μs | 0.22 | 0.01 | 0.0153 | 2,864 B | 360 B | 0.21 | +| Multivalue_IndexedSet | .NET 8.0 | .NET 8.0 | 8.742 μs | 0.1702 μs | 0.1960 μs | 0.22 | 0.01 | 0.0153 | 3,624 B | 360 B | 0.21 | | | | | | | | | | | | | | -| MultiValue_Linq | .NET 9.0 | .NET 9.0 | 37.035 μs | 0.6287 μs | 0.5881 μs | 1.00 | 0.02 | 0.0610 | 3,135 B | 1680 B | 1.00 | -| Multivalue_Lookup | .NET 9.0 | .NET 9.0 | 7.816 μs | 0.0377 μs | 0.0352 μs | 0.21 | 0.00 | 0.0153 | 2,412 B | 288 B | 0.17 | -| Multivalue_IndexedSet | .NET 9.0 | .NET 9.0 | 8.639 μs | 0.0633 μs | 0.0592 μs | 0.23 | 0.00 | 0.0153 | 2,772 B | 360 B | 0.21 | +| MultiValue_Linq | .NET 9.0 | .NET 9.0 | 37.828 μs | 0.7422 μs | 0.8835 μs | 1.00 | 0.03 | 0.0610 | 3,135 B | 1680 B | 1.00 | +| Multivalue_Lookup | .NET 9.0 | .NET 9.0 | 7.385 μs | 0.0256 μs | 0.0200 μs | 0.20 | 0.00 | 0.0153 | 2,414 B | 288 B | 0.17 | +| Multivalue_IndexedSet | .NET 9.0 | .NET 9.0 | 8.571 μs | 0.1204 μs | 0.1126 μs | 0.23 | 0.01 | 0.0153 | 2,772 B | 360 B | 0.21 | > ℹ️ Solution is on par with manually using LINQ's lookup (i.e. `.ToLookup()`) ## Range-Index | Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio | |-------------------- |--------- |--------- |--------------:|------------:|------------:|------:|--------:|-------:|-------:|----------:|------------:| -| LessThan_Linq | .NET 8.0 | .NET 8.0 | 12,172.573 ns | 65.0425 ns | 60.8408 ns | 1.00 | 0.01 | - | - | 160 B | 1.00 | -| LessThan_IndexedSet | .NET 8.0 | .NET 8.0 | 3,439.486 ns | 5.9086 ns | 5.5269 ns | 0.28 | 0.00 | 0.0038 | - | 72 B | 0.45 | +| LessThan_Linq | .NET 8.0 | .NET 8.0 | 12,202.818 ns | 38.0986 ns | 31.8141 ns | 1.00 | 0.00 | - | - | 160 B | 1.00 | +| LessThan_IndexedSet | .NET 8.0 | .NET 8.0 | 3,510.905 ns | 33.8899 ns | 31.7006 ns | 0.29 | 0.00 | 0.0038 | - | 72 B | 0.45 | | | | | | | | | | | | | | -| LessThan_Linq | .NET 9.0 | .NET 9.0 | 4,379.574 ns | 26.8995 ns | 23.8457 ns | 1.00 | 0.01 | 0.0076 | - | 160 B | 1.00 | -| LessThan_IndexedSet | .NET 9.0 | .NET 9.0 | 3,449.437 ns | 8.2712 ns | 6.9068 ns | 0.79 | 0.00 | 0.0038 | - | 72 B | 0.45 | +| LessThan_Linq | .NET 9.0 | .NET 9.0 | 4,391.714 ns | 31.0503 ns | 25.9284 ns | 1.00 | 0.01 | 0.0076 | - | 160 B | 1.00 | +| LessThan_IndexedSet | .NET 9.0 | .NET 9.0 | 3,480.802 ns | 11.6068 ns | 10.8570 ns | 0.79 | 0.01 | 0.0038 | - | 72 B | 0.45 | | | | | | | | | | | | | | -| Min_Linq | .NET 8.0 | .NET 8.0 | 35,974.568 ns | 715.3502 ns | 765.4167 ns | 1.000 | 0.03 | - | - | 40 B | 1.00 | -| Min_IndexedSet | .NET 8.0 | .NET 8.0 | 5.179 ns | 0.0153 ns | 0.0143 ns | 0.000 | 0.00 | - | - | - | 0.00 | +| Min_Linq | .NET 8.0 | .NET 8.0 | 38,406.650 ns | 764.9037 ns | 967.3583 ns | 1.001 | 0.04 | - | - | 40 B | 1.00 | +| Min_IndexedSet | .NET 8.0 | .NET 8.0 | 5.287 ns | 0.0341 ns | 0.0319 ns | 0.000 | 0.00 | - | - | - | 0.00 | | | | | | | | | | | | | | -| Min_Linq | .NET 9.0 | .NET 9.0 | 30,744.936 ns | 607.9471 ns | 746.6137 ns | 1.001 | 0.03 | - | - | 40 B | 1.00 | -| Min_IndexedSet | .NET 9.0 | .NET 9.0 | 4.948 ns | 0.0066 ns | 0.0062 ns | 0.000 | 0.00 | - | - | - | 0.00 | +| Min_Linq | .NET 9.0 | .NET 9.0 | 31,559.012 ns | 609.4903 ns | 748.5089 ns | 1.001 | 0.03 | - | - | 40 B | 1.00 | +| Min_IndexedSet | .NET 9.0 | .NET 9.0 | 5.087 ns | 0.0272 ns | 0.0242 ns | 0.000 | 0.00 | - | - | - | 0.00 | | | | | | | | | | | | | | -| Paging_Linq | .NET 8.0 | .NET 8.0 | 42,730.709 ns | 165.4306 ns | 154.7439 ns | 1.000 | 0.00 | 9.5215 | 2.3804 | 160352 B | 1.000 | -| Paging_IndexedSet | .NET 8.0 | .NET 8.0 | 146.687 ns | 2.1497 ns | 2.0108 ns | 0.003 | 0.00 | 0.0277 | - | 464 B | 0.003 | +| Paging_Linq | .NET 8.0 | .NET 8.0 | 49,019.614 ns | 538.2468 ns | 503.4764 ns | 1.000 | 0.01 | 9.5215 | 2.3804 | 160352 B | 1.000 | +| Paging_IndexedSet | .NET 8.0 | .NET 8.0 | 153.676 ns | 1.6417 ns | 1.5357 ns | 0.003 | 0.00 | 0.0277 | - | 464 B | 0.003 | | | | | | | | | | | | | | -| Paging_Linq | .NET 9.0 | .NET 9.0 | 41,435.160 ns | 455.6568 ns | 426.2217 ns | 1.000 | 0.01 | 9.5215 | 1.1597 | 160464 B | 1.000 | -| Paging_IndexedSet | .NET 9.0 | .NET 9.0 | 121.028 ns | 0.6311 ns | 0.5903 ns | 0.003 | 0.00 | 0.0138 | - | 232 B | 0.001 | +| Paging_Linq | .NET 9.0 | .NET 9.0 | 40,252.494 ns | 709.1066 ns | 663.2987 ns | 1.000 | 0.02 | 9.5215 | 1.1597 | 160464 B | 1.000 | +| Paging_IndexedSet | .NET 9.0 | .NET 9.0 | 123.875 ns | 2.2537 ns | 2.1081 ns | 0.003 | 0.00 | 0.0138 | - | 232 B | 0.001 | | | | | | | | | | | | | | -| Range_Linq | .NET 8.0 | .NET 8.0 | 13,410.602 ns | 65.6024 ns | 61.3645 ns | 1.00 | 0.01 | - | - | 160 B | 1.00 | -| Range_IndexedSet | .NET 8.0 | .NET 8.0 | 2,919.190 ns | 2.2875 ns | 1.7859 ns | 0.22 | 0.00 | 0.0038 | - | 72 B | 0.45 | +| Range_Linq | .NET 8.0 | .NET 8.0 | 14,455.695 ns | 284.9172 ns | 468.1271 ns | 1.00 | 0.05 | - | - | 160 B | 1.00 | +| Range_IndexedSet | .NET 8.0 | .NET 8.0 | 2,704.781 ns | 53.9501 ns | 50.4649 ns | 0.19 | 0.01 | 0.0038 | - | 72 B | 0.45 | | | | | | | | | | | | | | -| Range_Linq | .NET 9.0 | .NET 9.0 | 4,440.144 ns | 20.0350 ns | 18.7407 ns | 1.00 | 0.01 | 0.0076 | - | 160 B | 1.00 | -| Range_IndexedSet | .NET 9.0 | .NET 9.0 | 2,713.934 ns | 9.9596 ns | 8.8289 ns | 0.61 | 0.00 | 0.0038 | - | 72 B | 0.45 | +| Range_Linq | .NET 9.0 | .NET 9.0 | 4,376.953 ns | 42.5539 ns | 39.8050 ns | 1.00 | 0.01 | 0.0076 | - | 160 B | 1.00 | +| Range_IndexedSet | .NET 9.0 | .NET 9.0 | 2,946.508 ns | 19.8687 ns | 18.5852 ns | 0.67 | 0.01 | 0.0038 | - | 72 B | 0.45 | > ℹ️ There is no built-in range data structure, hence no comparison. Paging covers sorting scenarios, while min-queries cover MinBy and MaxBy-Queries as well. ## Prefix-Index -| Method | Job | Runtime | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | -|--------------------------- |--------- |--------- |------------:|----------:|----------:|------:|-------:|----------:|------------:| -| FuzzyStartsWith_Linq | .NET 8.0 | .NET 8.0 | 58,900.4 ns | 187.75 ns | 175.62 ns | 1.00 | 5.7373 | 96088 B | 1.000 | -| FuzzyStartsWith_IndexedSet | .NET 8.0 | .NET 8.0 | 15,862.9 ns | 51.72 ns | 43.19 ns | 0.27 | - | 240 B | 0.002 | -| | | | | | | | | | | -| FuzzyStartsWith_Linq | .NET 9.0 | .NET 9.0 | 61,080.0 ns | 230.47 ns | 215.58 ns | 1.00 | 5.7373 | 96088 B | 1.000 | -| FuzzyStartsWith_IndexedSet | .NET 9.0 | .NET 9.0 | 13,758.7 ns | 21.79 ns | 17.01 ns | 0.23 | - | 240 B | 0.002 | -| | | | | | | | | | | -| StartsWith_Linq | .NET 8.0 | .NET 8.0 | 2,137.0 ns | 1.58 ns | 1.40 ns | 1.00 | 0.0076 | 128 B | 1.00 | -| StartsWith_IndexedSet | .NET 8.0 | .NET 8.0 | 592.4 ns | 1.11 ns | 1.04 ns | 0.28 | 0.0086 | 144 B | 1.12 | -| | | | | | | | | | | -| StartsWith_Linq | .NET 9.0 | .NET 9.0 | 1,347.1 ns | 0.99 ns | 0.83 ns | 1.00 | 0.0076 | 128 B | 1.00 | -| StartsWith_IndexedSet | .NET 9.0 | .NET 9.0 | 381.8 ns | 0.54 ns | 0.48 ns | 0.28 | 0.0086 | 144 B | 1.12 | +| Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Allocated | Alloc Ratio | +|--------------------------- |--------- |--------- |------------:|----------:|----------:|------:|--------:|-------:|----------:|------------:| +| FuzzyStartsWith_Linq | .NET 8.0 | .NET 8.0 | 58,445.7 ns | 157.84 ns | 147.64 ns | 1.00 | 0.00 | 5.7373 | 96088 B | 1.000 | +| FuzzyStartsWith_IndexedSet | .NET 8.0 | .NET 8.0 | 21,850.5 ns | 49.15 ns | 43.57 ns | 0.37 | 0.00 | - | 240 B | 0.002 | +| | | | | | | | | | | | +| FuzzyStartsWith_Linq | .NET 9.0 | .NET 9.0 | 58,022.0 ns | 680.04 ns | 636.11 ns | 1.00 | 0.02 | 5.7373 | 96088 B | 1.000 | +| FuzzyStartsWith_IndexedSet | .NET 9.0 | .NET 9.0 | 21,369.8 ns | 90.70 ns | 84.85 ns | 0.37 | 0.00 | - | 240 B | 0.002 | +| | | | | | | | | | | | +| StartsWith_Linq | .NET 8.0 | .NET 8.0 | 2,025.9 ns | 4.27 ns | 3.78 ns | 1.00 | 0.00 | 0.0076 | 128 B | 1.00 | +| StartsWith_IndexedSet | .NET 8.0 | .NET 8.0 | 602.7 ns | 1.41 ns | 1.25 ns | 0.30 | 0.00 | 0.0086 | 144 B | 1.12 | +| | | | | | | | | | | | +| StartsWith_Linq | .NET 9.0 | .NET 9.0 | 1,276.4 ns | 3.59 ns | 3.36 ns | 1.00 | 0.00 | 0.0076 | 128 B | 1.00 | +| StartsWith_IndexedSet | .NET 9.0 | .NET 9.0 | 396.8 ns | 1.12 ns | 1.05 ns | 0.31 | 0.00 | 0.0086 | 144 B | 1.12 | > ℹ️ Fuzzy-Matching of string pairs within the Linq-Benchmark is done with [Fastenshtein](https://github.com/DanHarltey/Fastenshtein) ## FullText-Index | Method | Job | Runtime | Mean | Error | StdDev | Ratio | Gen0 | Allocated | Alloc Ratio | |------------------------- |--------- |--------- |---------------:|-------------:|-------------:|------:|---------:|----------:|------------:| -| Contains_Linq | .NET 8.0 | .NET 8.0 | 5,683.6 ns | 5.98 ns | 5.60 ns | 1.00 | 0.0229 | 408 B | 1.00 | -| Contains_IndexedSet | .NET 8.0 | .NET 8.0 | 424.4 ns | 0.52 ns | 0.46 ns | 0.07 | 0.0238 | 400 B | 0.98 | +| Contains_Linq | .NET 8.0 | .NET 8.0 | 5,915.0 ns | 16.05 ns | 15.02 ns | 1.00 | 0.0229 | 408 B | 1.00 | +| Contains_IndexedSet | .NET 8.0 | .NET 8.0 | 429.2 ns | 1.63 ns | 1.36 ns | 0.07 | 0.0238 | 400 B | 0.98 | | | | | | | | | | | | -| Contains_Linq | .NET 9.0 | .NET 9.0 | 5,003.8 ns | 5.28 ns | 4.68 ns | 1.00 | 0.0076 | 176 B | 1.00 | -| Contains_IndexedSet | .NET 9.0 | .NET 9.0 | 337.7 ns | 0.80 ns | 0.75 ns | 0.07 | 0.0238 | 400 B | 2.27 | +| Contains_Linq | .NET 9.0 | .NET 9.0 | 5,029.3 ns | 7.84 ns | 6.95 ns | 1.00 | 0.0076 | 176 B | 1.00 | +| Contains_IndexedSet | .NET 9.0 | .NET 9.0 | 351.9 ns | 1.46 ns | 1.37 ns | 0.07 | 0.0238 | 400 B | 2.27 | | | | | | | | | | | | -| FuzzyContains_Linq | .NET 8.0 | .NET 8.0 | 6,252,849.7 ns | 11,934.09 ns | 11,163.15 ns | 1.00 | 281.2500 | 4831742 B | 1.000 | -| FuzzyContains_IndexedSet | .NET 8.0 | .NET 8.0 | 140,481.3 ns | 325.54 ns | 304.51 ns | 0.02 | - | 608 B | 0.000 | +| FuzzyContains_Linq | .NET 8.0 | .NET 8.0 | 6,356,542.1 ns | 65,872.79 ns | 61,617.45 ns | 1.00 | 281.2500 | 4831744 B | 1.000 | +| FuzzyContains_IndexedSet | .NET 8.0 | .NET 8.0 | 198,529.9 ns | 642.52 ns | 601.01 ns | 0.03 | - | 608 B | 0.000 | | | | | | | | | | | | -| FuzzyContains_Linq | .NET 9.0 | .NET 9.0 | 5,974,308.9 ns | 17,245.07 ns | 16,131.04 ns | 1.00 | 281.2500 | 4831736 B | 1.000 | -| FuzzyContains_IndexedSet | .NET 9.0 | .NET 9.0 | 124,523.5 ns | 789.63 ns | 738.62 ns | 0.02 | - | 608 B | 0.000 | +| FuzzyContains_Linq | .NET 9.0 | .NET 9.0 | 6,084,268.4 ns | 58,886.56 ns | 55,082.53 ns | 1.00 | 281.2500 | 4831737 B | 1.000 | +| FuzzyContains_IndexedSet | .NET 9.0 | .NET 9.0 | 196,501.6 ns | 1,008.63 ns | 943.48 ns | 0.03 | - | 608 B | 0.000 | > ℹ️ Fuzzy-Matching of string pairs within the Linq-Benchmark is done with [Fastenshtein](https://github.com/DanHarltey/Fastenshtein) and enumerating possible infixes. @@ -112,23 +111,23 @@ the benchmarks are run against both .NET 6 and 8. | Method | Job | Runtime | Mean | Error | StdDev | Ratio | RatioSD | Allocated | Alloc Ratio | |------------------------- |--------- |--------- |--------------:|-----------:|-----------:|------:|--------:|----------:|------------:| -| FullTextLookup | .NET 8.0 | .NET 8.0 | 17,781.201 ns | 30.5715 ns | 28.5966 ns | 1.00 | 0.00 | 424 B | 1.00 | -| ConcurrentFullTextLookup | .NET 8.0 | .NET 8.0 | 17,562.165 ns | 39.1982 ns | 34.7482 ns | 0.99 | 0.00 | 464 B | 1.09 | +| FullTextLookup | .NET 8.0 | .NET 8.0 | 24,578.302 ns | 42.3126 ns | 37.5090 ns | 1.00 | 0.00 | 424 B | 1.00 | +| ConcurrentFullTextLookup | .NET 8.0 | .NET 8.0 | 25,049.091 ns | 81.2626 ns | 72.0372 ns | 1.02 | 0.00 | 464 B | 1.09 | | | | | | | | | | | | -| FullTextLookup | .NET 9.0 | .NET 9.0 | 16,162.325 ns | 13.2668 ns | 10.3578 ns | 1.00 | 0.00 | 424 B | 1.00 | -| ConcurrentFullTextLookup | .NET 9.0 | .NET 9.0 | 16,220.753 ns | 25.7419 ns | 22.8195 ns | 1.00 | 0.00 | 464 B | 1.09 | +| FullTextLookup | .NET 9.0 | .NET 9.0 | 23,501.549 ns | 37.4894 ns | 33.2334 ns | 1.00 | 0.00 | 424 B | 1.00 | +| ConcurrentFullTextLookup | .NET 9.0 | .NET 9.0 | 23,613.616 ns | 33.7388 ns | 31.5593 ns | 1.00 | 0.00 | 464 B | 1.09 | | | | | | | | | | | | -| LessThanLookup | .NET 8.0 | .NET 8.0 | 10.967 ns | 0.0387 ns | 0.0343 ns | 1.00 | 0.00 | - | NA | -| ConcurrentLessThanLookup | .NET 8.0 | .NET 8.0 | 56.053 ns | 0.8625 ns | 0.7646 ns | 5.11 | 0.07 | - | NA | +| LessThanLookup | .NET 8.0 | .NET 8.0 | 10.952 ns | 0.0518 ns | 0.0484 ns | 1.00 | 0.01 | - | NA | +| ConcurrentLessThanLookup | .NET 8.0 | .NET 8.0 | 55.013 ns | 0.8904 ns | 0.7893 ns | 5.02 | 0.07 | - | NA | | | | | | | | | | | | -| LessThanLookup | .NET 9.0 | .NET 9.0 | 12.063 ns | 0.0193 ns | 0.0180 ns | 1.00 | 0.00 | - | NA | -| ConcurrentLessThanLookup | .NET 9.0 | .NET 9.0 | 41.691 ns | 0.8385 ns | 0.9656 ns | 3.46 | 0.08 | - | NA | +| LessThanLookup | .NET 9.0 | .NET 9.0 | 10.724 ns | 0.0096 ns | 0.0090 ns | 1.00 | 0.00 | - | NA | +| ConcurrentLessThanLookup | .NET 9.0 | .NET 9.0 | 44.744 ns | 0.8647 ns | 0.8492 ns | 4.17 | 0.08 | - | NA | | | | | | | | | | | | -| UniqueLookup | .NET 8.0 | .NET 8.0 | 9.380 ns | 0.0273 ns | 0.0242 ns | 1.00 | 0.00 | - | NA | -| ConcurrentUniqueLookup | .NET 8.0 | .NET 8.0 | 33.221 ns | 0.6806 ns | 1.7447 ns | 3.54 | 0.19 | - | NA | +| UniqueLookup | .NET 8.0 | .NET 8.0 | 9.759 ns | 0.0245 ns | 0.0229 ns | 1.00 | 0.00 | - | NA | +| ConcurrentUniqueLookup | .NET 8.0 | .NET 8.0 | 38.876 ns | 0.7559 ns | 0.8401 ns | 3.98 | 0.08 | - | NA | | | | | | | | | | | | -| UniqueLookup | .NET 9.0 | .NET 9.0 | 8.900 ns | 0.0231 ns | 0.0205 ns | 1.00 | 0.00 | - | NA | -| ConcurrentUniqueLookup | .NET 9.0 | .NET 9.0 | 24.075 ns | 0.4980 ns | 1.0396 ns | 2.71 | 0.12 | - | NA | +| UniqueLookup | .NET 9.0 | .NET 9.0 | 9.196 ns | 0.0690 ns | 0.0645 ns | 1.00 | 0.01 | - | NA | +| ConcurrentUniqueLookup | .NET 9.0 | .NET 9.0 | 25.773 ns | 0.5374 ns | 0.7533 ns | 2.80 | 0.08 | - | NA | > ℹ️ For more complex scenarios, the synchronization cost is negligible. For simple queries, it is not. The difference in allocated memory is due to materialization of results in the concurrent case.