From ddabb6ea8cbdd4af594854e40c1c0ad1f29239e2 Mon Sep 17 00:00:00 2001 From: Sandro Bollhalder Date: Fri, 27 Jan 2023 21:24:05 +0100 Subject: [PATCH] Add MemoryExtensions.Count (#80662) * Add MemoryExtensions.Count * add test cases * fix implementation * fix review comments part I * more tests * return 0 if value is Empty * Address PR feedback --------- Co-authored-by: Stephen Toub --- src/libraries/System.Memory/System.Memory.sln | 89 ++--- src/libraries/System.Memory/ref/System.Memory.cs | 4 + .../System.Memory/tests/ReadOnlySpan/Count.T.cs | 383 +++++++++++++++++++++ .../System.Memory/tests/ReadOnlySpan/Count.byte.cs | 271 +++++++++++++++ .../System.Memory/tests/System.Memory.Tests.csproj | 2 + src/libraries/System.Memory/tests/TestHelpers.cs | 20 +- .../src/System/MemoryExtensions.cs | 91 +++++ .../src/System/SpanHelpers.T.cs | 84 +++++ 8 files changed, 902 insertions(+), 42 deletions(-) create mode 100644 src/libraries/System.Memory/tests/ReadOnlySpan/Count.T.cs create mode 100644 src/libraries/System.Memory/tests/ReadOnlySpan/Count.byte.cs diff --git a/src/libraries/System.Memory/System.Memory.sln b/src/libraries/System.Memory/System.Memory.sln index d66566f..1dd8168 100644 --- a/src/libraries/System.Memory/System.Memory.sln +++ b/src/libraries/System.Memory/System.Memory.sln @@ -1,4 +1,8 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.6.33316.92 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Private.CoreLib", "..\..\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj", "{7746BFD6-E6D6-4703-AA2D-43380B5DEA22}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestUtilities", "..\Common\tests\TestUtilities\TestUtilities.csproj", "{6A54FACA-933E-4C1D-92AB-1A5506CFC212}" @@ -29,17 +33,23 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "gen", "gen", "{2BEB1A89-DD2 EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Checked|Any CPU = Checked|Any CPU + Checked|x64 = Checked|x64 + Checked|x86 = Checked|x86 Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 - Checked|Any CPU = Checked|Any CPU - Checked|x64 = Checked|x64 - Checked|x86 = Checked|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|Any CPU.ActiveCfg = Checked|x64 + {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|Any CPU.Build.0 = Checked|x64 + {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x64.ActiveCfg = Checked|x64 + {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x64.Build.0 = Checked|x64 + {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x86.ActiveCfg = Checked|x86 + {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x86.Build.0 = Checked|x86 {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Debug|Any CPU.ActiveCfg = Debug|x64 {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Debug|Any CPU.Build.0 = Debug|x64 {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Debug|x64.ActiveCfg = Debug|x64 @@ -52,12 +62,9 @@ Global {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Release|x64.Build.0 = Release|x64 {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Release|x86.ActiveCfg = Release|x86 {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Release|x86.Build.0 = Release|x86 - {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|Any CPU.ActiveCfg = Checked|x64 - {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|Any CPU.Build.0 = Checked|x64 - {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x64.ActiveCfg = Checked|x64 - {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x64.Build.0 = Checked|x64 - {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x86.ActiveCfg = Checked|x86 - {7746BFD6-E6D6-4703-AA2D-43380B5DEA22}.Checked|x86.Build.0 = Checked|x86 + {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Checked|x64.ActiveCfg = Debug|Any CPU + {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Checked|x86.ActiveCfg = Debug|Any CPU {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Debug|Any CPU.Build.0 = Debug|Any CPU {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -70,9 +77,9 @@ Global {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Release|x64.Build.0 = Release|Any CPU {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Release|x86.ActiveCfg = Release|Any CPU {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Release|x86.Build.0 = Release|Any CPU - {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Checked|x64.ActiveCfg = Debug|Any CPU - {6A54FACA-933E-4C1D-92AB-1A5506CFC212}.Checked|x86.ActiveCfg = Debug|Any CPU + {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Checked|x64.ActiveCfg = Debug|Any CPU + {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Checked|x86.ActiveCfg = Debug|Any CPU {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Debug|Any CPU.Build.0 = Debug|Any CPU {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -85,9 +92,9 @@ Global {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Release|x64.Build.0 = Release|Any CPU {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Release|x86.ActiveCfg = Release|Any CPU {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Release|x86.Build.0 = Release|Any CPU - {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Checked|x64.ActiveCfg = Debug|Any CPU - {9112BAE3-344D-4DD0-ADC9-478D82B84584}.Checked|x86.ActiveCfg = Debug|Any CPU + {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Checked|x64.ActiveCfg = Debug|Any CPU + {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Checked|x86.ActiveCfg = Debug|Any CPU {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Debug|Any CPU.Build.0 = Debug|Any CPU {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -100,9 +107,9 @@ Global {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Release|x64.Build.0 = Release|Any CPU {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Release|x86.ActiveCfg = Release|Any CPU {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Release|x86.Build.0 = Release|Any CPU - {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Checked|x64.ActiveCfg = Debug|Any CPU - {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92}.Checked|x86.ActiveCfg = Debug|Any CPU + {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Checked|x64.ActiveCfg = Debug|Any CPU + {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Checked|x86.ActiveCfg = Debug|Any CPU {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Debug|Any CPU.Build.0 = Debug|Any CPU {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -115,9 +122,9 @@ Global {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Release|x64.Build.0 = Release|Any CPU {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Release|x86.ActiveCfg = Release|Any CPU {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Release|x86.Build.0 = Release|Any CPU - {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Checked|x64.ActiveCfg = Debug|Any CPU - {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297}.Checked|x86.ActiveCfg = Debug|Any CPU + {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Checked|x64.ActiveCfg = Debug|Any CPU + {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Checked|x86.ActiveCfg = Debug|Any CPU {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Debug|Any CPU.Build.0 = Debug|Any CPU {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -130,9 +137,9 @@ Global {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Release|x64.Build.0 = Release|Any CPU {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Release|x86.ActiveCfg = Release|Any CPU {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Release|x86.Build.0 = Release|Any CPU - {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Checked|x64.ActiveCfg = Debug|Any CPU - {EFF00253-633C-4D2F-86EE-F40C721F6A68}.Checked|x86.ActiveCfg = Debug|Any CPU + {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Checked|x64.ActiveCfg = Debug|Any CPU + {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Checked|x86.ActiveCfg = Debug|Any CPU {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Debug|Any CPU.Build.0 = Debug|Any CPU {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -145,9 +152,9 @@ Global {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Release|x64.Build.0 = Release|Any CPU {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Release|x86.ActiveCfg = Release|Any CPU {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Release|x86.Build.0 = Release|Any CPU - {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Checked|x64.ActiveCfg = Debug|Any CPU - {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE}.Checked|x86.ActiveCfg = Debug|Any CPU + {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Checked|x64.ActiveCfg = Debug|Any CPU + {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Checked|x86.ActiveCfg = Debug|Any CPU {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Debug|Any CPU.Build.0 = Debug|Any CPU {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -160,9 +167,9 @@ Global {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Release|x64.Build.0 = Release|Any CPU {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Release|x86.ActiveCfg = Release|Any CPU {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Release|x86.Build.0 = Release|Any CPU - {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Checked|x64.ActiveCfg = Debug|Any CPU - {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7}.Checked|x86.ActiveCfg = Debug|Any CPU + {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Checked|x64.ActiveCfg = Debug|Any CPU + {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Checked|x86.ActiveCfg = Debug|Any CPU {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -175,9 +182,9 @@ Global {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Release|x64.Build.0 = Release|Any CPU {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Release|x86.ActiveCfg = Release|Any CPU {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Release|x86.Build.0 = Release|Any CPU - {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Checked|x64.ActiveCfg = Debug|Any CPU - {DA7CEED7-1A86-4221-B4AD-4307AB83A31F}.Checked|x86.ActiveCfg = Debug|Any CPU + {B773D664-C00B-4DB3-823B-947576EE7A46}.Checked|Any CPU.ActiveCfg = Debug|Any CPU + {B773D664-C00B-4DB3-823B-947576EE7A46}.Checked|x64.ActiveCfg = Debug|Any CPU + {B773D664-C00B-4DB3-823B-947576EE7A46}.Checked|x86.ActiveCfg = Debug|Any CPU {B773D664-C00B-4DB3-823B-947576EE7A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B773D664-C00B-4DB3-823B-947576EE7A46}.Debug|Any CPU.Build.0 = Debug|Any CPU {B773D664-C00B-4DB3-823B-947576EE7A46}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -190,26 +197,26 @@ Global {B773D664-C00B-4DB3-823B-947576EE7A46}.Release|x64.Build.0 = Release|Any CPU {B773D664-C00B-4DB3-823B-947576EE7A46}.Release|x86.ActiveCfg = Release|Any CPU {B773D664-C00B-4DB3-823B-947576EE7A46}.Release|x86.Build.0 = Release|Any CPU - {B773D664-C00B-4DB3-823B-947576EE7A46}.Checked|Any CPU.ActiveCfg = Debug|Any CPU - {B773D664-C00B-4DB3-823B-947576EE7A46}.Checked|x64.ActiveCfg = Debug|Any CPU - {B773D664-C00B-4DB3-823B-947576EE7A46}.Checked|x86.ActiveCfg = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {7746BFD6-E6D6-4703-AA2D-43380B5DEA22} = {C352AC7D-959D-431F-AF83-2CA506B70D59} - {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92} = {C352AC7D-959D-431F-AF83-2CA506B70D59} {6A54FACA-933E-4C1D-92AB-1A5506CFC212} = {FA259C32-B79B-4DE2-9677-055D5D25FA33} - {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297} = {FA259C32-B79B-4DE2-9677-055D5D25FA33} {9112BAE3-344D-4DD0-ADC9-478D82B84584} = {7212FBCF-E89D-4065-9DCE-D5F7E5D3EF1D} - {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE} = {7212FBCF-E89D-4065-9DCE-D5F7E5D3EF1D} - {B773D664-C00B-4DB3-823B-947576EE7A46} = {7212FBCF-E89D-4065-9DCE-D5F7E5D3EF1D} + {C9417154-D8DB-4FF9-9DD8-6B2ED351FC92} = {C352AC7D-959D-431F-AF83-2CA506B70D59} + {C2BC6AE7-7E8B-4AA2-8E9F-5D4B9127B297} = {FA259C32-B79B-4DE2-9677-055D5D25FA33} {EFF00253-633C-4D2F-86EE-F40C721F6A68} = {2BEB1A89-DD2D-42BD-95DD-89860A0C9663} + {D0EEF7E0-BD51-4C39-AF4F-DD583D01AEBE} = {7212FBCF-E89D-4065-9DCE-D5F7E5D3EF1D} {84AD7BF6-D76C-4BEE-9879-5A23150DD3F7} = {2BEB1A89-DD2D-42BD-95DD-89860A0C9663} {DA7CEED7-1A86-4221-B4AD-4307AB83A31F} = {2BEB1A89-DD2D-42BD-95DD-89860A0C9663} + {B773D664-C00B-4DB3-823B-947576EE7A46} = {7212FBCF-E89D-4065-9DCE-D5F7E5D3EF1D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {65DB6A6B-0AAC-4BC2-A61A-641679771DC7} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ..\System.Private.CoreLib\src\System.Private.CoreLib.Shared.projitems*{7746bfd6-e6d6-4703-aa2d-43380b5dea22}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 6669b94..9f998a0 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -234,6 +234,10 @@ namespace System public static bool Contains(this System.Span span, T value) where T : System.IEquatable? { throw null; } public static void CopyTo(this T[]? source, System.Memory destination) { } public static void CopyTo(this T[]? source, System.Span destination) { } + public static int Count(this System.Span span, T value) where T : System.IEquatable? { throw null; } + public static int Count(this System.ReadOnlySpan span, T value) where T : System.IEquatable? { throw null; } + public static int Count(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable? { throw null; } + public static int Count(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable? { throw null; } public static bool EndsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value, System.StringComparison comparisonType) { throw null; } public static bool EndsWith(this System.ReadOnlySpan span, System.ReadOnlySpan value) where T : System.IEquatable? { throw null; } public static bool EndsWith(this System.Span span, System.ReadOnlySpan value) where T : System.IEquatable? { throw null; } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Count.T.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Count.T.cs new file mode 100644 index 0000000..6cc1ff7 --- /dev/null +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Count.T.cs @@ -0,0 +1,383 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthCount_Int() + { + Assert.Equal(0, ReadOnlySpan.Empty.Count(0)); + } + + [Fact] + public static void ZeroLengthCount_RosInt() + { + for (int i = 0; i <= 2; i++) + { + Assert.Equal(0, ReadOnlySpan.Empty.Count(new int[i])); + } + } + + [Fact] + public static void ZeroLengthNeedleCount_RosInt() + { + ReadOnlySpan span = new ReadOnlySpan(new int[] { 5, 5, 5, 5, 5 }); + + Assert.Equal(0, span.Count(ReadOnlySpan.Empty)); + } + + [Fact] + public static void TestCount_Int() + { + for (int length = 0; length < 32; length++) + { + int[] a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = 10 * (i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + Assert.Equal(1, span.Count(a[targetIndex])); + } + } + } + + [Fact] + public static void TestCount_RosInt() + { + for (int length = 0; length < 32; length++) + { + int[] a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = 10 * (i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + Assert.Equal(1, span.Count(new int[] { a[targetIndex], a[targetIndex + 1] })); + } + } + } + + [Fact] + public static void TestMultipleCount_Int() + { + for (int length = 2; length < 32; length++) + { + int[] a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = 10 * (i + 1); + } + + a[^1] = a[^2] = 5555; + + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(2, span.Count(5555)); + } + } + + [Fact] + public static void TestMultipleCount_RosInt() + { + for (int length = 4; length < 32; length++) + { + int[] a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = 10 * (i + 1); + } + a[0] = a[1] = a[^1] = a[^2] = 5555; + + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(2, span.Count(new int[] { 5555, 5555 })); + } + } + + [Fact] + public static void OnNoMatchForCountMakeSureEveryElementIsCompared_TInt() + { + for (int length = 0; length < 100; length++) + { + TIntLog log = new TIntLog(); + + TInt[] a = new TInt[length]; + for (int i = 0; i < length; i++) + { + a[i] = new TInt(10 * (i + 1), log); + } + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(0, span.Count(new TInt(9999, log))); + + // Since we asked for a non-existent value, make sure each element of the array was compared once. + // (Strictly speaking, it would not be illegal for Count to compare an element more than once but + // that would be a non-optimal implementation and a red flag. So we'll stick with the stricter test.) + Assert.Equal(a.Length, log.Count); + foreach (TInt elem in a) + { + int numCompares = log.CountCompares(elem.Value, 9999); + Assert.True(numCompares == 1, $"Expected {numCompares} == 1 for element {elem.Value}."); + } + } + } + + [Fact] + public static void OnNoMatchForCountMakeSureEveryElementIsCompared_RosTInt() + { + for (int length = 0; length < 100; length++) + { + TIntLog log = new TIntLog(); + + TInt[] a = new TInt[length]; + for (int i = 0; i < length; i++) + { + a[i] = new TInt(10 * (i + 1), log); + } + + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(0, span.Count(new TInt[] { new TInt(9999, log), new TInt(10000, log) })); + + // Since we asked for a non-existent value, make sure each element of the array was compared once. + // (Strictly speaking, it would not be illegal for Count to compare an element more than once but + // that would be a non-optimal implementation and a red flag. So we'll stick with the stricter test.) + if (length > 0) + { + Assert.Equal(a.Length - 1, log.Count); + } + for (int i = 0; i < length - 1; i++) + { + int numCompares = log.CountCompares(a[i].Value, 9999); + Assert.True(numCompares == 1, $"Expected {numCompares} == 1 for element {a[i].Value}."); + + numCompares = log.CountCompares(a[i].Value, 10000); + Assert.True(numCompares == 0, $"Expected {numCompares} == 0 for element {a[i].Value}."); + } + } + } + + [Fact] + public static void MakeSureNoChecksForCountGoOutOfRange_TInt() + { + const int GuardValue = 77777; + const int GuardLength = 50; + + void CheckForOutOfRangeAccess(int x, int y) => + Assert.True(x != GuardValue && y != GuardValue, $"{x} or {y} == {GuardValue}"); + + for (int length = 0; length < 100; length++) + { + TInt[] a = new TInt[GuardLength + length + GuardLength]; + for (int i = 0; i < a.Length; i++) + { + a[i] = new TInt(GuardValue, CheckForOutOfRangeAccess); + } + + for (int i = 0; i < length; i++) + { + a[GuardLength + i] = new TInt(10 * (i + 1), CheckForOutOfRangeAccess); + } + + ReadOnlySpan span = new ReadOnlySpan(a, GuardLength, length); + Assert.Equal(0, span.Count(new TInt(9999, CheckForOutOfRangeAccess))); + } + } + + [Fact] + public static void MakeSureNoChecksForCountGoOutOfRange_RosTInt() + { + const int GuardValue = 77777; + const int GuardLength = 50; + + void CheckForOutOfRangeAccess(int x, int y) => + Assert.True(x != GuardValue && y != GuardValue, $"{x} or {y} == {GuardValue}"); + + for (int length = 0; length < 100; length++) + { + TInt[] a = new TInt[GuardLength + length + GuardLength]; + for (int i = 0; i < a.Length; i++) + { + a[i] = new TInt(GuardValue, CheckForOutOfRangeAccess); + } + + for (int i = 0; i < length; i++) + { + a[GuardLength + i] = new TInt(10 * (i + 1), CheckForOutOfRangeAccess); + } + + ReadOnlySpan span = new ReadOnlySpan(a, GuardLength, length); + Assert.Equal(0, span.Count(new TInt[] { new TInt(9999, CheckForOutOfRangeAccess), new TInt(9999, CheckForOutOfRangeAccess) })); + } + } + + [Fact] + public static void ZeroLengthCount_String() + { + Assert.Equal(0, ReadOnlySpan.Empty.Count("a")); + } + + [Fact] + public static void ZeroLengthCount_RosString() + { + Assert.Equal(0, ReadOnlySpan.Empty.Count(new[] { "a", "b" })); + } + + [Fact] + public static void TestMatchCount_String() + { + for (int length = 0; length < 32; length++) + { + string[] a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (10 * (i + 1)).ToString(); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + Assert.Equal(1, span.Count(a[targetIndex])); + } + } + } + + [Fact] + public static void TestMatchCount_RosString() + { + for (int length = 0; length < 32; length++) + { + string[] a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (10 * (i + 1)).ToString(); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + Assert.Equal(1, span.Count(new string[] { a[targetIndex], a[targetIndex + 1] })); + } + } + } + + [Fact] + public static void TestNoMatchCount_String() + { + var rnd = new Random(42); + for (int length = 0; length <= byte.MaxValue; length++) + { + string[] a = new string[length]; + string target = rnd.Next(0, 256).ToString(); + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == target ? (target + 1) : val; + } + + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(0, span.Count(target)); + } + } + + [Fact] + public static void TestNoMatchCount_RosString() + { + var rnd = new Random(42); + for (int length = 0; length <= byte.MaxValue; length++) + { + string[] a = new string[length]; + ReadOnlySpan target = new string[] { rnd.Next(0, 256).ToString(), "0" }; + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == target[0] ? (target[0] + 1) : val; + } + + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(0, span.Count(target)); + } + } + + [Fact] + public static void TestMultipleMatchCount_String() + { + for (int length = 2; length < 32; length++) + { + string[] a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (10 * (i + 1)).ToString(); + } + + a[^1] = a[^2] = "5555"; + + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(2, span.Count("5555")); + } + } + + [Fact] + public static void TestMultipleMatchCount_RosString() + { + for (int length = 4; length < 32; length++) + { + string[] a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (10 * (i + 1)).ToString(); + } + + a[0] = a[1] = a[^1] = a[^2] = "5555"; + + ReadOnlySpan span = new ReadOnlySpan(a); + Assert.Equal(2, span.Count(new string[] { "5555", "5555" })); + } + } + + [Fact] + public static void TestOrdinalStringCount_String() + { + ReadOnlySpan span = new string[] { "ii", "II", "ìì", "ii" }; + Assert.Equal(2, span.Count("ii")); + } + + [Fact] + public static void TestOrdinalStringCount_RosString() + { + ReadOnlySpan span = new string[] { "ii", "II", "ìì", "ii", "ìì" }; + Assert.Equal(1, span.Count(new string[] { "ii", "II" })); + } + + [Fact] + public static void TestOverlapDoNotCount_RosChar() + { + ReadOnlySpan span = new string('a', 10); + Assert.Equal(5, span.Count("aa")); + } + + [Theory] + [MemberData(nameof(TestHelpers.CountNullData), MemberType = typeof(TestHelpers))] + public static void CountNull_String(string[] spanInput, int expected) + { + ReadOnlySpan theStrings = spanInput; + Assert.Equal(expected, theStrings.Count((string)null)); + } + + [Theory] + [MemberData(nameof(TestHelpers.CountNullRosData), MemberType = typeof(TestHelpers))] + public static void CountNull_RosString(string[] spanInput, int expected) + { + ReadOnlySpan theStrings = spanInput; + ReadOnlySpan target = new string[] { null, "9" }; + Assert.Equal(expected, theStrings.Count(target)); + } + } +} diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Count.byte.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Count.byte.cs new file mode 100644 index 0000000..642a24c --- /dev/null +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Count.byte.cs @@ -0,0 +1,271 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Numerics; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthCount_Byte() + { + Assert.Equal(0, ReadOnlySpan.Empty.Count(0)); + } + + [Fact] + public static void ZeroLengthCount_RosByte() + { + for (int i = 0; i <= 2; i++) + { + Assert.Equal(0, ReadOnlySpan.Empty.Count(new byte[i])); + } + } + + [Fact] + public static void ZeroLengthNeedleCount_RosByte() + { + var span = new ReadOnlySpan(new byte[] { 5, 5, 5, 5, 5 }); + + Assert.Equal(0, span.Count(ReadOnlySpan.Empty)); + } + + [Fact] + public static void DefaultFilledCount_Byte() + { + foreach (int length in new int[] { 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 255, 256 }) + { + var span = new ReadOnlySpan(new byte[length]); + Assert.Equal(length, span.Count((byte)0)); + } + } + + [Fact] + public static void DefaultFilledCount_RosByte() + { + foreach (int length in new int[] { 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 255, 256 }) + { + var span = new ReadOnlySpan(new byte[length]); + Assert.Equal(length / 2, span.Count(new byte[2])); + } + } + + [Fact] + public static void TestCount_Byte() + { + foreach (int length in new int[] { 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 255, 256 }) + { + var span = new ReadOnlySpan(Enumerable.Range(1, length).Select(i => (byte)i).ToArray()); + + foreach (byte target in span) + { + Assert.Equal(1, span.Count(target)); + } + } + } + + [Fact] + public static void TestCount_RosByte() + { + foreach (int length in new int[] { 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 255, 256 }) + { + var span = new ReadOnlySpan(Enumerable.Range(1, length).Select(i => (byte)i).ToArray()); + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + Assert.Equal(1, span.Count(new byte[] { span[targetIndex], span[targetIndex + 1] })); + } + } + } + + [Fact] + public static void TestSingleValueCount_Byte() + { + foreach (int length in new int[] { 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 255, 256 }) + { + var span = new ReadOnlySpan(Enumerable.Range(1, length).Select(i => (byte)i).ToArray()); + + foreach (byte value in span) + { + Assert.Equal(1, span.Count(new byte[] { value })); + } + } + } + + [Fact] + public static void TestNotCount_Byte() + { + var rnd = new Random(42); + int[] lengths = new int[] { 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 255, 256 }; + foreach (int length in lengths) + { + byte[] a = new byte[length]; + byte target = (byte)rnd.Next(0, 256); + for (int i = 0; i < length; i++) + { + byte val = (byte)(i + 1); + a[i] = val == target ? (byte)(target + 1) : val; + } + + var span = new ReadOnlySpan(a); + Assert.Equal(0, span.Count(target)); + } + } + + [Fact] + public static void TestNotCount_RosByte() + { + var rnd = new Random(42); + int[] lengths = new int[] { 0, 1, 7, 8, 9, 15, 16, 17, 31, 32, 33, 255, 256 }; + foreach (int length in lengths) + { + byte[] a = new byte[length]; + byte targetVal = (byte)rnd.Next(0, 256); + for (int i = 0; i < length; i++) + { + byte val = (byte)(i + 1); + a[i] = val == targetVal ? (byte)(targetVal + 1) : val; + } + + var span = new ReadOnlySpan(a); + Assert.Equal(0, span.Count(new byte[] { targetVal, 0 })); + } + } + + [Fact] + public static void TestAlignmentNotCount_Byte() + { + byte[] array = new byte[4 * Vector.Count]; + for (var i = 0; i < Vector.Count; i++) + { + var span = new ReadOnlySpan(array, i, 3 * Vector.Count); + Assert.Equal(0, span.Count((byte)'1')); + + span = new ReadOnlySpan(array, i, 3 * Vector.Count - 3); + Assert.Equal(0, span.Count((byte)'1')); + } + } + + [Fact] + public static void TestAlignmentNotCount_RosByte() + { + byte[] array = new byte[4 * Vector.Count]; + for (var i = 0; i < Vector.Count; i++) + { + var span = new ReadOnlySpan(array, i, 3 * Vector.Count); + ReadOnlySpan target = new byte[] { 1, 0 }; + Assert.Equal(0, span.Count(target)); + + span = new ReadOnlySpan(array, i, 3 * Vector.Count - 3); + Assert.Equal(0, span.Count(target)); + } + } + + [Fact] + public static void TestAlignmentCount_Byte() + { + byte[] array = new byte[4 * Vector.Count]; + Array.Fill(array, (byte)5); + for (var i = 0; i < Vector.Count; i++) + { + var span = new ReadOnlySpan(array, i, 3 * Vector.Count); + Assert.Equal(span.Length, span.Count(5)); + + span = new ReadOnlySpan(array, i, 3 * Vector.Count - 3); + Assert.Equal(span.Length, span.Count(5)); + } + } + + [Fact] + public static void TestAlignmentCount_RosByte() + { + byte[] array = new byte[4 * Vector.Count]; + Array.Fill(array, (byte)5); + for (var i = 0; i < Vector.Count; i++) + { + var span = new ReadOnlySpan(array, i, 3 * Vector.Count); + ReadOnlySpan target = new byte[] { 5, 5 }; + Assert.Equal(span.Length / 2, span.Count(target)); + + span = new ReadOnlySpan(array, i, 3 * Vector.Count - 3); + Assert.Equal(span.Length / 2, span.Count(target)); + } + } + + [Fact] + public static void TestMultipleCount_Byte() + { + for (int length = 2; length <= byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + byte val = (byte)(i + 1); + a[i] = val == 200 ? (byte)201 : val; + } + + a[^1] = a[^2] = 200; + + var span = new ReadOnlySpan(a); + Assert.Equal(2, span.Count(200)); + } + } + + [Fact] + public static void TestMultipleCount_RosByte() + { + for (int length = 4; length <= byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + byte val = (byte)(i + 1); + a[i] = val == 200 ? (byte)201 : val; + } + a[0] = a[1] = a[^1] = a[^2] = 200; + + var span = new ReadOnlySpan(a); + Assert.Equal(2, span.Count(new byte[] { 200, 200 })); + } + } + + [Fact] + public static void MakeSureNoCountChecksGoOutOfRange_Byte() + { + for (int length = 0; length <= byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = a[^1] = 99; + + var span = new ReadOnlySpan(a, 1, length); + Assert.Equal(0, span.Count(99)); + } + } + + [Fact] + public static void MakeSureNoCountChecksGoOutOfRange_RosByte() + { + for (int length = 0; length <= byte.MaxValue; length++) + { + byte[] a = new byte[length + 4]; + a[0] = a[1] = a[^1] = a[^2] = 99; + + var span = new ReadOnlySpan(a, 2, length); + Assert.Equal(0, span.Count(new byte[] { 99, 99 })); + } + } + + [Fact] + public static void TestOverlapDoNotCount_RosByte() + { + byte[] a = new byte[10]; + Array.Fill(a, 6); + + + var span = new ReadOnlySpan(a); + Assert.Equal(5, span.Count(new byte[] { 6, 6 })); + } + } +} diff --git a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj index 5179cd1..e35c06d 100644 --- a/src/libraries/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/libraries/System.Memory/tests/System.Memory.Tests.csproj @@ -141,6 +141,8 @@ + + diff --git a/src/libraries/System.Memory/tests/TestHelpers.cs b/src/libraries/System.Memory/tests/TestHelpers.cs index f209d92..95dbbe9 100644 --- a/src/libraries/System.Memory/tests/TestHelpers.cs +++ b/src/libraries/System.Memory/tests/TestHelpers.cs @@ -403,7 +403,7 @@ namespace System /// Creates a with the specified values in its backing field. public static ReadOnlyMemory DangerousCreateReadOnlyMemory(object obj, int offset, int length) => DangerousCreateMemory(obj, offset, length); - + public static TheoryData ContainsNullData => new TheoryData() { { new string[] { "1", null, "2" }, true}, @@ -412,6 +412,24 @@ namespace System { new string[] { "1", null, null }, true}, { new string[] { null, null, null }, true}, }; + + public static TheoryData CountNullData => new TheoryData() + { + { new string[] { "1", null, "2" }, 1}, + { new string[] { "1", "3", "2" }, 0}, + { null, 0}, + { new string[] { "1", null, null }, 2}, + { new string[] { null, null, null }, 3}, + }; + + public static TheoryData CountNullRosData => new TheoryData() + { + { new string[] { "1", null, "9", "2" }, 1}, + { new string[] { "1", "3", "9", "2" }, 0}, + { null, 0}, + { new string[] { "1", null, "9", null, "9"}, 2}, + { new string[] { null, null, "9", null, "9", "9", null, "9"}, 3}, + }; public static TheoryData SequenceEqualsNullData => new TheoryData() { diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 1c148d5..4baf212 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -3564,6 +3564,97 @@ namespace System return (startInclusive, endExclusive); } + /// Counts the number of times the specified occurs in the . + /// The element type of the span. + /// The span to search. + /// The value for which to search. + /// The number of times was found in the . + public static int Count(this Span span, T value) where T : IEquatable? => + Count((ReadOnlySpan)span, value); + + /// Counts the number of times the specified occurs in the . + /// The element type of the span. + /// The span to search. + /// The value for which to search. + /// The number of times was found in the . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int Count(this ReadOnlySpan span, T value) where T : IEquatable? + { + if (RuntimeHelpers.IsBitwiseEquatable()) + { + if (Unsafe.SizeOf() == sizeof(byte)) + { + return SpanHelpers.CountValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(short)) + { + return SpanHelpers.CountValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(int)) + { + return SpanHelpers.CountValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + else if (Unsafe.SizeOf() == sizeof(long)) + { + return SpanHelpers.CountValueType( + ref Unsafe.As(ref MemoryMarshal.GetReference(span)), + Unsafe.As(ref value), + span.Length); + } + } + + return SpanHelpers.Count( + ref MemoryMarshal.GetReference(span), + value, + span.Length); + } + + /// Counts the number of times the specified occurs in the . + /// The element type of the span. + /// The span to search. + /// The value for which to search. + /// The number of times was found in the . + public static int Count(this Span span, ReadOnlySpan value) where T : IEquatable? => + Count((ReadOnlySpan)span, value); + + /// Counts the number of times the specified occurs in the . + /// The element type of the span. + /// The span to search. + /// The value for which to search. + /// The number of times was found in the . + public static int Count(this ReadOnlySpan span, ReadOnlySpan value) where T : IEquatable? + { + switch (value.Length) + { + case 0: + return 0; + + case 1: + return Count(span, value[0]); + + default: + int count = 0; + + int pos; + while ((pos = span.IndexOf(value)) >= 0) + { + span = span.Slice(pos + value.Length); + count++; + } + + return count; + } + } + /// Writes the specified interpolated string to the character span. /// The span to which the interpolated string should be formatted. /// The interpolated string. diff --git a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs index e45c796..11c79ef 100644 --- a/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs +++ b/src/libraries/System.Private.CoreLib/src/System/SpanHelpers.T.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Runtime.Intrinsics; #pragma warning disable IDE0060 // https://github.com/dotnet/roslyn-analyzers/issues/6228 @@ -3296,5 +3297,88 @@ namespace System return -1; } + + public static int Count(ref T current, T value, int length) where T : IEquatable? + { + int count = 0; + + ref T end = ref Unsafe.Add(ref current, length); + if (value is not null) + { + while (Unsafe.IsAddressLessThan(ref current, ref end)) + { + if (value.Equals(current)) + { + count++; + } + + current = ref Unsafe.Add(ref current, 1); + } + } + else + { + while (Unsafe.IsAddressLessThan(ref current, ref end)) + { + if (current is null) + { + count++; + } + + current = ref Unsafe.Add(ref current, 1); + } + } + + return count; + } + + public static int CountValueType(ref T current, T value, int length) where T : struct, IEquatable? + { + int count = 0; + + ref T end = ref Unsafe.Add(ref current, length); + if (Vector128.IsHardwareAccelerated && length >= Vector128.Count) + { + if (Vector256.IsHardwareAccelerated && length >= Vector256.Count) + { + Vector256 targetVector = Vector256.Create(value); + ref T oneVectorAwayFromEndMinus1 = ref Unsafe.Subtract(ref end, Vector256.Count - 1); + do + { + count += BitOperations.PopCount(Vector256.Equals(Vector256.LoadUnsafe(ref current), targetVector).ExtractMostSignificantBits()); + current = ref Unsafe.Add(ref current, Vector256.Count); + } + while (Unsafe.IsAddressLessThan(ref current, ref oneVectorAwayFromEndMinus1)); + + if (Unsafe.IsAddressLessThan(ref current, ref Unsafe.Subtract(ref end, Vector128.Count - 1))) + { + count += BitOperations.PopCount(Vector128.Equals(Vector128.LoadUnsafe(ref current), Vector128.Create(value)).ExtractMostSignificantBits()); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + } + else + { + Vector128 targetVector = Vector128.Create(value); + ref T oneVectorAwayFromEndMinus1 = ref Unsafe.Subtract(ref end, Vector128.Count - 1); + do + { + count += BitOperations.PopCount(Vector128.Equals(Vector128.LoadUnsafe(ref current), targetVector).ExtractMostSignificantBits()); + current = ref Unsafe.Add(ref current, Vector128.Count); + } + while (Unsafe.IsAddressLessThan(ref current, ref oneVectorAwayFromEndMinus1)); + } + } + + while (Unsafe.IsAddressLessThan(ref current, ref end)) + { + if (current.Equals(value)) + { + count++; + } + + current = ref Unsafe.Add(ref current, 1); + } + + return count; + } } } -- 2.7.4