Add IndexOfAnyValues.Contains (#78996)
authorMiha Zupan <mihazupan.zupan1@gmail.com>
Wed, 30 Nov 2022 04:37:09 +0000 (04:37 +0000)
committerGitHub <noreply@github.com>
Wed, 30 Nov 2022 04:37:09 +0000 (20:37 -0800)
* Add IndexOfAnyValues.Contains

* Unsafe => unsafe

21 files changed:
src/coreclr/System.Private.CoreLib/src/System/TypeNameParser.cs
src/libraries/System.Memory/tests/Span/IndexOfAny.byte.cs
src/libraries/System.Memory/tests/Span/IndexOfAny.char.cs
src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs
src/libraries/System.Memory/tests/Span/IndexOfAnyValues.cs [new file with mode: 0644]
src/libraries/System.Memory/tests/Span/LastIndexOfAny.T.cs
src/libraries/System.Memory/tests/System.Memory.Tests.csproj
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny1Value.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny2Values.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny3Values.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny4Values.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAny5Values.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiByteValues.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyAsciiCharValues.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyByteValues.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyCharValuesProbabilistic.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyLatin1CharValues.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValues.T.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfAnyValuesInRange.cs
src/libraries/System.Private.CoreLib/src/System/IndexOfAnyValues/IndexOfEmptyValues.cs
src/libraries/System.Runtime/ref/System.Runtime.cs

index 2df15e8..06f3704 100644 (file)
@@ -89,8 +89,7 @@ namespace System
 
         #region Private Data Members
         private readonly SafeTypeNameParserHandle m_NativeParser;
-        private const string SpecialChars = ",[]&*+\\"; // see typeparse.h
-        private static readonly IndexOfAnyValues<char> s_specialChars = IndexOfAnyValues.Create(SpecialChars);
+        private static readonly IndexOfAnyValues<char> s_specialChars = IndexOfAnyValues.Create(",[]&*+\\"); // see typeparse.h
         #endregion
 
         #region Constructor and Disposer
@@ -289,7 +288,7 @@ namespace System
 
             foreach (char c in name.AsSpan(specialCharIndex))
             {
-                if (SpecialChars.Contains(c))
+                if (s_specialChars.Contains(c))
                     sb.Append('\\');
 
                 sb.Append(c);
index 5791f29..cc2e92c 100644 (file)
@@ -530,73 +530,6 @@ namespace System.SpanTests
             }
         }
 
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestIndexOfAny_RandomInputs_Byte()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: IndexOfAnyReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values));
-
-            static int IndexOfAnyReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
-            {
-                for (int i = 0; i < searchSpace.Length; i++)
-                {
-                    if (values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
-
-        [Theory]
-        [InlineData(true)]
-        [InlineData(false)]
-        public static void AsciiNeedle_ProperlyHandlesEdgeCases_Byte(bool needleContainsZero)
-        {
-            // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results
-            ReadOnlySpan<byte> needleValues = needleContainsZero ? "AEIOU\0"u8 : "AEIOU!"u8;
-            IndexOfAnyValues<byte> needle = IndexOfAnyValues.Create(needleValues);
-
-            ReadOnlySpan<byte> repeatingHaystack = "AaAaAaAaAaAa"u8;
-            Assert.Equal(0, repeatingHaystack.IndexOfAny(needle));
-            Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle));
-            Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle));
-            Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle));
-
-            ReadOnlySpan<byte> haystackWithZeroes = "Aa\0Aa\0Aa\0"u8;
-            Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle));
-            Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle));
-            Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle));
-            Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle));
-
-            Span<byte> haystackWithOffsetNeedle = new byte[100];
-            for (int i = 0; i < haystackWithOffsetNeedle.Length; i++)
-            {
-                haystackWithOffsetNeedle[i] = (byte)(128 + needleValues[i % needleValues.Length]);
-            }
-
-            Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle));
-            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
-            Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
-
-            // Mix matching characters back in
-            for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3)
-            {
-                haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length];
-            }
-
-            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle));
-            Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
-        }
-
         private static int IndexOf(Span<byte> span, byte value)
         {
             int index = span.IndexOf(value);
index c616ec2..47ec7be 100644 (file)
@@ -4,9 +4,6 @@
 using System.Buffers;
 using System.Linq;
 using System.Numerics;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
-using System.Text;
 using Xunit;
 
 namespace System.SpanTests
@@ -776,95 +773,6 @@ namespace System.SpanTests
             }
         }
 
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestIndexOfAny_RandomInputs_Char()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: IndexOfAnyReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values));
-
-            static int IndexOfAnyReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
-            {
-                for (int i = 0; i < searchSpace.Length; i++)
-                {
-                    if (values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
-
-        [Theory]
-        [InlineData(true)]
-        [InlineData(false)]
-        public static void AsciiNeedle_ProperlyHandlesEdgeCases_Char(bool needleContainsZero)
-        {
-            // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results
-            ReadOnlySpan<char> needleValues = needleContainsZero ? "AEIOU\0" : "AEIOU!";
-            IndexOfAnyValues<char> needle = IndexOfAnyValues.Create(needleValues);
-
-            ReadOnlySpan<char> repeatingHaystack = "AaAaAaAaAaAa";
-            Assert.Equal(0, repeatingHaystack.IndexOfAny(needle));
-            Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle));
-            Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle));
-            Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle));
-
-            ReadOnlySpan<char> haystackWithZeroes = "Aa\0Aa\0Aa\0";
-            Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle));
-            Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle));
-            Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle));
-            Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle));
-
-            Span<char> haystackWithOffsetNeedle = new char[100];
-            for (int i = 0; i < haystackWithOffsetNeedle.Length; i++)
-            {
-                haystackWithOffsetNeedle[i] = (char)(128 + needleValues[i % needleValues.Length]);
-            }
-
-            Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle));
-            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
-            Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
-
-            // Mix matching characters back in
-            for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3)
-            {
-                haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length];
-            }
-
-            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle));
-            Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
-
-            // With chars, the lower byte could be matching, but we have to check that the higher byte is also 0
-            for (int i = 0; i < haystackWithOffsetNeedle.Length; i++)
-            {
-                haystackWithOffsetNeedle[i] = (char)(((i + 1) * 256) + needleValues[i % needleValues.Length]);
-            }
-
-            Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle));
-            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
-            Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
-
-            // Mix matching characters back in
-            for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3)
-            {
-                haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length];
-            }
-
-            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle));
-            Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
-            Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
-        }
-
         private static int IndexOf(Span<char> span, char value)
         {
             int index = span.IndexOf(value);
@@ -893,115 +801,4 @@ namespace System.SpanTests
             return index;
         }
     }
-
-    public static class IndexOfAnyCharTestHelper
-    {
-        private const int MaxNeedleLength = 10;
-        private const int MaxHaystackLength = 40;
-
-        private static readonly char[] s_randomAsciiChars;
-        private static readonly char[] s_randomLatin1Chars;
-        private static readonly char[] s_randomChars;
-        private static readonly byte[] s_randomAsciiBytes;
-        private static readonly byte[] s_randomBytes;
-
-        static IndexOfAnyCharTestHelper()
-        {
-            s_randomAsciiChars = new char[100 * 1024];
-            s_randomLatin1Chars = new char[100 * 1024];
-            s_randomChars = new char[1024 * 1024];
-            s_randomBytes = new byte[100 * 1024];
-
-            var rng = new Random(42);
-
-            for (int i = 0; i < s_randomAsciiChars.Length; i++)
-            {
-                s_randomAsciiChars[i] = (char)rng.Next(0, 128);
-            }
-
-            for (int i = 0; i < s_randomLatin1Chars.Length; i++)
-            {
-                s_randomLatin1Chars[i] = (char)rng.Next(0, 256);
-            }
-
-            rng.NextBytes(MemoryMarshal.Cast<char, byte>(s_randomChars));
-
-            s_randomAsciiBytes = Encoding.ASCII.GetBytes(s_randomAsciiChars);
-
-            rng.NextBytes(s_randomBytes);
-        }
-
-        public delegate int IndexOfAnySearchDelegate<T>(ReadOnlySpan<T> searchSpace, ReadOnlySpan<T> values) where T : IEquatable<T>?;
-
-        public delegate int IndexOfAnyValuesSearchDelegate<T>(ReadOnlySpan<T> searchSpace, IndexOfAnyValues<T> values) where T : IEquatable<T>?;
-
-        public static void TestRandomInputs(IndexOfAnySearchDelegate<byte> expected, IndexOfAnySearchDelegate<byte> indexOfAny, IndexOfAnyValuesSearchDelegate<byte> indexOfAnyValues)
-        {
-            var rng = new Random(42);
-
-            for (int iterations = 0; iterations < 1_000_000; iterations++)
-            {
-                // There are more interesting corner cases with ASCII needles, test those more.
-                Test(rng, s_randomBytes, s_randomAsciiBytes, expected, indexOfAny, indexOfAnyValues);
-
-                Test(rng, s_randomBytes, s_randomBytes, expected, indexOfAny, indexOfAnyValues);
-            }
-        }
-
-        public static void TestRandomInputs(IndexOfAnySearchDelegate<char> expected, IndexOfAnySearchDelegate<char> indexOfAny, IndexOfAnyValuesSearchDelegate<char> indexOfAnyValues)
-        {
-            var rng = new Random(42);
-
-            for (int iterations = 0; iterations < 1_000_000; iterations++)
-            {
-                // There are more interesting corner cases with ASCII needles, test those more.
-                Test(rng, s_randomChars, s_randomAsciiChars, expected, indexOfAny, indexOfAnyValues);
-
-                Test(rng, s_randomChars, s_randomLatin1Chars, expected, indexOfAny, indexOfAnyValues);
-
-                Test(rng, s_randomChars, s_randomChars, expected, indexOfAny, indexOfAnyValues);
-            }
-        }
-
-        private static void Test<T>(Random rng, ReadOnlySpan<T> haystackRandom, ReadOnlySpan<T> needleRandom,
-            IndexOfAnySearchDelegate<T> expected, IndexOfAnySearchDelegate<T> indexOfAny, IndexOfAnyValuesSearchDelegate<T> indexOfAnyValues)
-            where T : INumber<T>
-        {
-            ReadOnlySpan<T> haystack = GetRandomSlice(rng, haystackRandom, MaxHaystackLength);
-            ReadOnlySpan<T> needle = GetRandomSlice(rng, needleRandom, MaxNeedleLength);
-
-            IndexOfAnyValues<T> indexOfAnyValuesInstance = (IndexOfAnyValues<T>)(object)(typeof(T) == typeof(byte)
-                ? IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(needle)), needle.Length))
-                : IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(needle)), needle.Length)));
-
-            int expectedIndex = expected(haystack, needle);
-            int indexOfAnyIndex = indexOfAny(haystack, needle);
-            int indexOfAnyValuesIndex = indexOfAnyValues(haystack, indexOfAnyValuesInstance);
-
-            if (expectedIndex != indexOfAnyIndex)
-            {
-                AssertionFailed(haystack, needle, expectedIndex, indexOfAnyIndex, nameof(indexOfAny));
-            }
-
-            if (expectedIndex != indexOfAnyValuesIndex)
-            {
-                AssertionFailed(haystack, needle, expectedIndex, indexOfAnyValuesIndex, nameof(indexOfAnyValues));
-            }
-        }
-
-        private static ReadOnlySpan<T> GetRandomSlice<T>(Random rng, ReadOnlySpan<T> span, int maxLength)
-        {
-            ReadOnlySpan<T> slice = span.Slice(rng.Next(span.Length + 1));
-            return slice.Slice(0, Math.Min(slice.Length, rng.Next(maxLength + 1)));
-        }
-
-        private static void AssertionFailed<T>(ReadOnlySpan<T> haystack, ReadOnlySpan<T> needle, int expected, int actual, string approach)
-            where T : INumber<T>
-        {
-            string readableHaystack = string.Join(", ", haystack.ToString().Select(c => int.CreateChecked(c)));
-            string readableNeedle = string.Join(", ", needle.ToString().Select(c => int.CreateChecked(c)));
-
-            Assert.True(false, $"Expected {expected}, got {approach}={actual} for needle='{readableNeedle}', haystack='{readableHaystack}'");
-        }
-    }
 }
index 6a5b2df..6d9d6fb 100644 (file)
@@ -55,98 +55,6 @@ namespace System.SpanTests
                     break;
             }
         }
-
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestIndexOfAnyExcept_RandomInputs_Byte()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: IndexOfAnyExceptReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values));
-
-            static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
-            {
-                for (int i = 0; i < searchSpace.Length; i++)
-                {
-                    if (!values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
-
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestLastIndexOfAnyExcept_RandomInputs_Byte()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: LastIndexOfAnyExceptReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values));
-
-            static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
-            {
-                for (int i = searchSpace.Length - 1; i >= 0; i--)
-                {
-                    if (!values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
-
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestIndexOfAnyExcept_RandomInputs_Char()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: IndexOfAnyExceptReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values));
-
-            static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
-            {
-                for (int i = 0; i < searchSpace.Length; i++)
-                {
-                    if (!values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
-
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestLastIndexOfAnyExcept_RandomInputs_Char()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: LastIndexOfAnyExceptReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values));
-
-            static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
-            {
-                for (int i = searchSpace.Length - 1; i >= 0; i--)
-                {
-                    if (!values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
     }
 
     public record SimpleRecord(int Value);
diff --git a/src/libraries/System.Memory/tests/Span/IndexOfAnyValues.cs b/src/libraries/System.Memory/tests/Span/IndexOfAnyValues.cs
new file mode 100644 (file)
index 0000000..8a4266d
--- /dev/null
@@ -0,0 +1,471 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Linq;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Xunit;
+
+namespace System.SpanTests
+{
+    public static partial class SpanTests
+    {
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public static void AsciiNeedle_ProperlyHandlesEdgeCases_Char(bool needleContainsZero)
+        {
+            // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results
+            ReadOnlySpan<char> needleValues = needleContainsZero ? "AEIOU\0" : "AEIOU!";
+            IndexOfAnyValues<char> needle = IndexOfAnyValues.Create(needleValues);
+
+            ReadOnlySpan<char> repeatingHaystack = "AaAaAaAaAaAa";
+            Assert.Equal(0, repeatingHaystack.IndexOfAny(needle));
+            Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle));
+            Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle));
+            Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle));
+
+            ReadOnlySpan<char> haystackWithZeroes = "Aa\0Aa\0Aa\0";
+            Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle));
+            Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle));
+            Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle));
+            Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle));
+
+            Span<char> haystackWithOffsetNeedle = new char[100];
+            for (int i = 0; i < haystackWithOffsetNeedle.Length; i++)
+            {
+                haystackWithOffsetNeedle[i] = (char)(128 + needleValues[i % needleValues.Length]);
+            }
+
+            Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle));
+            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
+            Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
+
+            // Mix matching characters back in
+            for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3)
+            {
+                haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length];
+            }
+
+            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle));
+            Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
+
+            // With chars, the lower byte could be matching, but we have to check that the higher byte is also 0
+            for (int i = 0; i < haystackWithOffsetNeedle.Length; i++)
+            {
+                haystackWithOffsetNeedle[i] = (char)(((i + 1) * 256) + needleValues[i % needleValues.Length]);
+            }
+
+            Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle));
+            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
+            Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
+
+            // Mix matching characters back in
+            for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3)
+            {
+                haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length];
+            }
+
+            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle));
+            Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public static void AsciiNeedle_ProperlyHandlesEdgeCases_Byte(bool needleContainsZero)
+        {
+            // There is some special handling we have to do for ASCII needles to properly filter out non-ASCII results
+            ReadOnlySpan<byte> needleValues = needleContainsZero ? "AEIOU\0"u8 : "AEIOU!"u8;
+            IndexOfAnyValues<byte> needle = IndexOfAnyValues.Create(needleValues);
+
+            ReadOnlySpan<byte> repeatingHaystack = "AaAaAaAaAaAa"u8;
+            Assert.Equal(0, repeatingHaystack.IndexOfAny(needle));
+            Assert.Equal(1, repeatingHaystack.IndexOfAnyExcept(needle));
+            Assert.Equal(10, repeatingHaystack.LastIndexOfAny(needle));
+            Assert.Equal(11, repeatingHaystack.LastIndexOfAnyExcept(needle));
+
+            ReadOnlySpan<byte> haystackWithZeroes = "Aa\0Aa\0Aa\0"u8;
+            Assert.Equal(0, haystackWithZeroes.IndexOfAny(needle));
+            Assert.Equal(1, haystackWithZeroes.IndexOfAnyExcept(needle));
+            Assert.Equal(needleContainsZero ? 8 : 6, haystackWithZeroes.LastIndexOfAny(needle));
+            Assert.Equal(needleContainsZero ? 7 : 8, haystackWithZeroes.LastIndexOfAnyExcept(needle));
+
+            Span<byte> haystackWithOffsetNeedle = new byte[100];
+            for (int i = 0; i < haystackWithOffsetNeedle.Length; i++)
+            {
+                haystackWithOffsetNeedle[i] = (byte)(128 + needleValues[i % needleValues.Length]);
+            }
+
+            Assert.Equal(-1, haystackWithOffsetNeedle.IndexOfAny(needle));
+            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
+            Assert.Equal(-1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
+
+            // Mix matching characters back in
+            for (int i = 0; i < haystackWithOffsetNeedle.Length; i += 3)
+            {
+                haystackWithOffsetNeedle[i] = needleValues[i % needleValues.Length];
+            }
+
+            Assert.Equal(0, haystackWithOffsetNeedle.IndexOfAny(needle));
+            Assert.Equal(1, haystackWithOffsetNeedle.IndexOfAnyExcept(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 1, haystackWithOffsetNeedle.LastIndexOfAny(needle));
+            Assert.Equal(haystackWithOffsetNeedle.Length - 2, haystackWithOffsetNeedle.LastIndexOfAnyExcept(needle));
+        }
+
+        [Theory]
+        [InlineData("\0")]
+        [InlineData("a")]
+        [InlineData("ab")]
+        [InlineData("ac")]
+        [InlineData("abc")]
+        [InlineData("aei")]
+        [InlineData("abcd")]
+        [InlineData("aeio")]
+        [InlineData("aeiou")]
+        [InlineData("abceiou")]
+        [InlineData("123456789")]
+        [InlineData("123456789123")]
+        [InlineData("abcdefgh")]
+        [InlineData("abcdefghIJK")]
+        [InlineData("aa")]
+        [InlineData("aaa")]
+        [InlineData("aaaa")]
+        [InlineData("aaaaa")]
+        [InlineData("\u0000\u0001\u0002\u0003\u0004\u0005")]
+        [InlineData("\u0001\u0002\u0003\u0004\u0005\u0006")]
+        [InlineData("\u0001\u0002\u0003\u0004\u0005\u0007")]
+        [InlineData("\u007E\u007F\u0080\u0081\u0082\u0083")]
+        [InlineData("\u007E\u007F\u0080\u0081\u0082\u0084")]
+        [InlineData("\u007E\u007F\u0080\u0081\u0082")]
+        [InlineData("\u007E\u007F\u0080\u0081\u0083")]
+        [InlineData("\u00FE\u00FF\u0100\u0101\u0102\u0103")]
+        [InlineData("\u00FE\u00FF\u0100\u0101\u0102\u0104")]
+        [InlineData("\u00FE\u00FF\u0100\u0101\u0102")]
+        [InlineData("\u00FE\u00FF\u0100\u0101\u0103")]
+        [InlineData("\uFFFF\uFFFE\uFFFD\uFFFC\uFFFB\uFFFA")]
+        [InlineData("\uFFFF\uFFFE\uFFFD\uFFFC\uFFFB\uFFFB")]
+        [InlineData("\uFFFF\uFFFE\uFFFD\uFFFC\uFFFB\uFFF9")]
+        public static void IndexOfAnyValues_Contains(string needle)
+        {
+            Test(needle, IndexOfAnyValues.Create(needle));
+
+            byte[] needleBytes = Encoding.Latin1.GetBytes(needle);
+            Test(needleBytes, IndexOfAnyValues.Create(needleBytes));
+
+            static void Test<T>(ReadOnlySpan<T> needle, IndexOfAnyValues<T> values) where T : struct, INumber<T>, IMinMaxValue<T>
+            {
+                for (int i = int.CreateChecked(T.MaxValue); i >= 0; i--)
+                {
+                    T t = T.CreateChecked(i);
+                    Assert.Equal(needle.Contains(t), values.Contains(t));
+                }
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestIndexOfAny_RandomInputs_Byte()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: IndexOfAnyReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values));
+
+            static int IndexOfAnyReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
+            {
+                for (int i = 0; i < searchSpace.Length; i++)
+                {
+                    if (values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestIndexOfAny_RandomInputs_Char()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: IndexOfAnyReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAny(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAny(values));
+
+            static int IndexOfAnyReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
+            {
+                for (int i = 0; i < searchSpace.Length; i++)
+                {
+                    if (values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestLastIndexOfAny_RandomInputs_Byte()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: LastIndexOfAnyReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values));
+
+            static int LastIndexOfAnyReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
+            {
+                for (int i = searchSpace.Length - 1; i >= 0; i--)
+                {
+                    if (values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestLastIndexOfAny_RandomInputs_Char()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: LastIndexOfAnyReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values));
+
+            static int LastIndexOfAnyReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
+            {
+                for (int i = searchSpace.Length - 1; i >= 0; i--)
+                {
+                    if (values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestIndexOfAnyExcept_RandomInputs_Byte()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: IndexOfAnyExceptReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values));
+
+            static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
+            {
+                for (int i = 0; i < searchSpace.Length; i++)
+                {
+                    if (!values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestIndexOfAnyExcept_RandomInputs_Char()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: IndexOfAnyExceptReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.IndexOfAnyExcept(values));
+
+            static int IndexOfAnyExceptReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
+            {
+                for (int i = 0; i < searchSpace.Length; i++)
+                {
+                    if (!values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestLastIndexOfAnyExcept_RandomInputs_Byte()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: LastIndexOfAnyExceptReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values));
+
+            static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
+            {
+                for (int i = searchSpace.Length - 1; i >= 0; i--)
+                {
+                    if (!values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        [Fact]
+        [OuterLoop("Takes about a second to execute")]
+        public static void TestLastIndexOfAnyExcept_RandomInputs_Char()
+        {
+            IndexOfAnyValuesTestHelper.TestRandomInputs(
+                expected: LastIndexOfAnyExceptReferenceImpl,
+                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values),
+                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAnyExcept(values));
+
+            static int LastIndexOfAnyExceptReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
+            {
+                for (int i = searchSpace.Length - 1; i >= 0; i--)
+                {
+                    if (!values.Contains(searchSpace[i]))
+                    {
+                        return i;
+                    }
+                }
+
+                return -1;
+            }
+        }
+
+        private static class IndexOfAnyValuesTestHelper
+        {
+            private const int MaxNeedleLength = 10;
+            private const int MaxHaystackLength = 40;
+
+            private static readonly char[] s_randomAsciiChars;
+            private static readonly char[] s_randomLatin1Chars;
+            private static readonly char[] s_randomChars;
+            private static readonly byte[] s_randomAsciiBytes;
+            private static readonly byte[] s_randomBytes;
+
+            static IndexOfAnyValuesTestHelper()
+            {
+                s_randomAsciiChars = new char[100 * 1024];
+                s_randomLatin1Chars = new char[100 * 1024];
+                s_randomChars = new char[1024 * 1024];
+                s_randomBytes = new byte[100 * 1024];
+
+                var rng = new Random(42);
+
+                for (int i = 0; i < s_randomAsciiChars.Length; i++)
+                {
+                    s_randomAsciiChars[i] = (char)rng.Next(0, 128);
+                }
+
+                for (int i = 0; i < s_randomLatin1Chars.Length; i++)
+                {
+                    s_randomLatin1Chars[i] = (char)rng.Next(0, 256);
+                }
+
+                rng.NextBytes(MemoryMarshal.Cast<char, byte>(s_randomChars));
+
+                s_randomAsciiBytes = Encoding.ASCII.GetBytes(s_randomAsciiChars);
+
+                rng.NextBytes(s_randomBytes);
+            }
+
+            public delegate int IndexOfAnySearchDelegate<T>(ReadOnlySpan<T> searchSpace, ReadOnlySpan<T> values) where T : IEquatable<T>?;
+
+            public delegate int IndexOfAnyValuesSearchDelegate<T>(ReadOnlySpan<T> searchSpace, IndexOfAnyValues<T> values) where T : IEquatable<T>?;
+
+            public static void TestRandomInputs(IndexOfAnySearchDelegate<byte> expected, IndexOfAnySearchDelegate<byte> indexOfAny, IndexOfAnyValuesSearchDelegate<byte> indexOfAnyValues)
+            {
+                var rng = new Random(42);
+
+                for (int iterations = 0; iterations < 1_000_000; iterations++)
+                {
+                    // There are more interesting corner cases with ASCII needles, test those more.
+                    Test(rng, s_randomBytes, s_randomAsciiBytes, expected, indexOfAny, indexOfAnyValues);
+
+                    Test(rng, s_randomBytes, s_randomBytes, expected, indexOfAny, indexOfAnyValues);
+                }
+            }
+
+            public static void TestRandomInputs(IndexOfAnySearchDelegate<char> expected, IndexOfAnySearchDelegate<char> indexOfAny, IndexOfAnyValuesSearchDelegate<char> indexOfAnyValues)
+            {
+                var rng = new Random(42);
+
+                for (int iterations = 0; iterations < 1_000_000; iterations++)
+                {
+                    // There are more interesting corner cases with ASCII needles, test those more.
+                    Test(rng, s_randomChars, s_randomAsciiChars, expected, indexOfAny, indexOfAnyValues);
+
+                    Test(rng, s_randomChars, s_randomLatin1Chars, expected, indexOfAny, indexOfAnyValues);
+
+                    Test(rng, s_randomChars, s_randomChars, expected, indexOfAny, indexOfAnyValues);
+                }
+            }
+
+            private static void Test<T>(Random rng, ReadOnlySpan<T> haystackRandom, ReadOnlySpan<T> needleRandom,
+                IndexOfAnySearchDelegate<T> expected, IndexOfAnySearchDelegate<T> indexOfAny, IndexOfAnyValuesSearchDelegate<T> indexOfAnyValues)
+                where T : struct, INumber<T>, IMinMaxValue<T>
+            {
+                ReadOnlySpan<T> haystack = GetRandomSlice(rng, haystackRandom, MaxHaystackLength);
+                ReadOnlySpan<T> needle = GetRandomSlice(rng, needleRandom, MaxNeedleLength);
+
+                IndexOfAnyValues<T> indexOfAnyValuesInstance = (IndexOfAnyValues<T>)(object)(typeof(T) == typeof(byte)
+                    ? IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, byte>(ref MemoryMarshal.GetReference(needle)), needle.Length))
+                    : IndexOfAnyValues.Create(MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<T, char>(ref MemoryMarshal.GetReference(needle)), needle.Length)));
+
+                int expectedIndex = expected(haystack, needle);
+                int indexOfAnyIndex = indexOfAny(haystack, needle);
+                int indexOfAnyValuesIndex = indexOfAnyValues(haystack, indexOfAnyValuesInstance);
+
+                if (expectedIndex != indexOfAnyIndex)
+                {
+                    AssertionFailed(haystack, needle, expectedIndex, indexOfAnyIndex, nameof(indexOfAny));
+                }
+
+                if (expectedIndex != indexOfAnyValuesIndex)
+                {
+                    AssertionFailed(haystack, needle, expectedIndex, indexOfAnyValuesIndex, nameof(indexOfAnyValues));
+                }
+            }
+
+            private static ReadOnlySpan<T> GetRandomSlice<T>(Random rng, ReadOnlySpan<T> span, int maxLength)
+            {
+                ReadOnlySpan<T> slice = span.Slice(rng.Next(span.Length + 1));
+                return slice.Slice(0, Math.Min(slice.Length, rng.Next(maxLength + 1)));
+            }
+
+            private static void AssertionFailed<T>(ReadOnlySpan<T> haystack, ReadOnlySpan<T> needle, int expected, int actual, string approach)
+                where T : INumber<T>
+            {
+                string readableHaystack = string.Join(", ", haystack.ToString().Select(c => int.CreateChecked(c)));
+                string readableNeedle = string.Join(", ", needle.ToString().Select(c => int.CreateChecked(c)));
+
+                Assert.True(false, $"Expected {expected}, got {approach}={actual} for needle='{readableNeedle}', haystack='{readableHaystack}'");
+            }
+        }
+    }
+}
index afc86a6..1ae582a 100644 (file)
@@ -951,51 +951,5 @@ namespace System.SpanTests
                 }
             }
         }
-
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestLastIndexOfAny_RandomInputs_Byte()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: LastIndexOfAnyReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values));
-
-            static int LastIndexOfAnyReferenceImpl(ReadOnlySpan<byte> searchSpace, ReadOnlySpan<byte> values)
-            {
-                for (int i = searchSpace.Length - 1; i >= 0; i--)
-                {
-                    if (values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
-
-        [Fact]
-        [OuterLoop("Takes about a second to execute")]
-        public static void TestLastIndexOfAny_RandomInputs_Char()
-        {
-            IndexOfAnyCharTestHelper.TestRandomInputs(
-                expected: LastIndexOfAnyReferenceImpl,
-                indexOfAny: (searchSpace, values) => searchSpace.LastIndexOfAny(values),
-                indexOfAnyValues: (searchSpace, values) => searchSpace.LastIndexOfAny(values));
-
-            static int LastIndexOfAnyReferenceImpl(ReadOnlySpan<char> searchSpace, ReadOnlySpan<char> values)
-            {
-                for (int i = searchSpace.Length - 1; i >= 0; i--)
-                {
-                    if (values.Contains(searchSpace[i]))
-                    {
-                        return i;
-                    }
-                }
-
-                return -1;
-            }
-        }
     }
 }
index e775856..5179cd1 100644 (file)
@@ -87,6 +87,7 @@
     <Compile Include="Span\IndexOfAny.T.cs" />
     <Compile Include="Span\IndexOfAnyExcept.T.cs" />
     <Compile Include="Span\IndexOfAnyInRange.cs" />
+    <Compile Include="Span\IndexOfAnyValues.cs" />
     <Compile Include="Span\IndexOfSequence.byte.cs" />
     <Compile Include="Span\IndexOfSequence.char.cs" />
     <Compile Include="Span\IndexOfSequence.T.cs" />
index 0be9ac6..565af9c 100644 (file)
@@ -2,12 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Diagnostics;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 
 namespace System.Buffers
 {
     internal sealed class IndexOfAny1Value<T> : IndexOfAnyValues<T>
-        where T : struct, IEquatable<T>
+        where T : struct, INumber<T>
     {
         private readonly T _e0;
 
@@ -20,6 +21,10 @@ namespace System.Buffers
         internal override T[] GetValues() => new[] { _e0 };
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(T value) =>
+            value == _e0;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<T> span) =>
             span.IndexOf(_e0);
 
index bab3007..cb89fcf 100644 (file)
@@ -2,12 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Diagnostics;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 
 namespace System.Buffers
 {
     internal sealed class IndexOfAny2Values<T> : IndexOfAnyValues<T>
-        where T : struct, IEquatable<T>
+        where T : struct, INumber<T>
     {
         private readonly T _e0, _e1;
 
@@ -20,6 +21,10 @@ namespace System.Buffers
         internal override T[] GetValues() => new[] { _e0, _e1 };
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(T value) =>
+            value == _e0 || value == _e1;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<T> span) =>
             span.IndexOfAny(_e0, _e1);
 
index 933d87b..f94509b 100644 (file)
@@ -2,12 +2,13 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 
 using System.Diagnostics;
+using System.Numerics;
 using System.Runtime.CompilerServices;
 
 namespace System.Buffers
 {
     internal sealed class IndexOfAny3Values<T> : IndexOfAnyValues<T>
-        where T : struct, IEquatable<T>
+        where T : struct, INumber<T>
     {
         private readonly T _e0, _e1, _e2;
 
@@ -20,6 +21,10 @@ namespace System.Buffers
         internal override T[] GetValues() => new[] { _e0, _e1, _e2 };
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(T value) =>
+            value == _e0 || value == _e1 || value == _e2;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<T> span) =>
             span.IndexOfAny(_e0, _e1, _e2);
 
index 0d59ca9..1ba4820 100644 (file)
@@ -6,6 +6,8 @@ using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
+#pragma warning disable 8500 // address of managed types
+
 namespace System.Buffers
 {
     internal sealed class IndexOfAny4Values<T, TImpl> : IndexOfAnyValues<T>
@@ -21,12 +23,19 @@ namespace System.Buffers
             (_e0, _e1, _e2, _e3) = (values[0], values[1], values[2], values[3]);
         }
 
-        internal override T[] GetValues()
+        internal override unsafe T[] GetValues()
         {
             TImpl e0 = _e0, e1 = _e1, e2 = _e2, e3 = _e3;
-            return new[] { Unsafe.As<TImpl, T>(ref e0), Unsafe.As<TImpl, T>(ref e1), Unsafe.As<TImpl, T>(ref e2), Unsafe.As<TImpl, T>(ref e3) };
+            return new[] { *(T*)&e0, *(T*)&e1, *(T*)&e2, *(T*)&e3 };
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override unsafe bool ContainsCore(T value) =>
+            *(TImpl*)&value == _e0 ||
+            *(TImpl*)&value == _e1 ||
+            *(TImpl*)&value == _e2 ||
+            *(TImpl*)&value == _e3;
+
 #if MONO // Revert this once https://github.com/dotnet/runtime/pull/78015 is merged
         internal override int IndexOfAny(ReadOnlySpan<T> span) =>
             span.IndexOfAny(GetValues());
index 5372bd0..55660a2 100644 (file)
@@ -6,6 +6,8 @@ using System.Numerics;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 
+#pragma warning disable 8500 // address of managed types
+
 namespace System.Buffers
 {
     internal sealed class IndexOfAny5Values<T, TImpl> : IndexOfAnyValues<T>
@@ -21,13 +23,21 @@ namespace System.Buffers
             (_e0, _e1, _e2, _e3, _e4) = (values[0], values[1], values[2], values[3], values[4]);
         }
 
-        internal override T[] GetValues()
+        internal override unsafe T[] GetValues()
         {
             TImpl e0 = _e0, e1 = _e1, e2 = _e2, e3 = _e3, e4 = _e4;
-            return new[] { Unsafe.As<TImpl, T>(ref e0), Unsafe.As<TImpl, T>(ref e1), Unsafe.As<TImpl, T>(ref e2), Unsafe.As<TImpl, T>(ref e3), Unsafe.As<TImpl, T>(ref e4) };
+            return new[] { *(T*)&e0, *(T*)&e1, *(T*)&e2, *(T*)&e3, *(T*)&e4 };
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override unsafe bool ContainsCore(T value) =>
+            *(TImpl*)&value == _e0 ||
+            *(TImpl*)&value == _e1 ||
+            *(TImpl*)&value == _e2 ||
+            *(TImpl*)&value == _e3 ||
+            *(TImpl*)&value == _e4;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<T> span) =>
             SpanHelpers.IndexOfAnyValueType(ref Unsafe.As<T, TImpl>(ref MemoryMarshal.GetReference(span)), _e0, _e1, _e2, _e3, _e4, span.Length);
 
index ac8815a..3a214b4 100644 (file)
@@ -22,6 +22,10 @@ namespace System.Buffers
         internal override byte[] GetValues() => _lookup.GetByteValues();
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(byte value) =>
+            _lookup.Contains(value);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<byte> span) =>
             IndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(ref MemoryMarshal.GetReference(span), span.Length);
 
index 6718782..0958f61 100644 (file)
@@ -22,6 +22,10 @@ namespace System.Buffers
         internal override char[] GetValues() => _lookup.GetCharValues();
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(char value) =>
+            _lookup.Contains128(value);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<char> span) =>
             IndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(ref MemoryMarshal.GetReference(span), span.Length);
 
index 9dc76d4..ffe6219 100644 (file)
@@ -19,6 +19,10 @@ namespace System.Buffers
         internal override byte[] GetValues() => _lookup.GetByteValues();
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(byte value) =>
+            _lookup.Contains(value);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<byte> span) =>
             IndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(ref MemoryMarshal.GetReference(span), span.Length);
 
index d8de8c0..0838d48 100644 (file)
@@ -20,6 +20,10 @@ namespace System.Buffers
         internal override char[] GetValues() => _values.ToCharArray();
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(char value) =>
+            ProbabilisticMap.Contains(ref Unsafe.As<ProbabilisticMap, uint>(ref _map), _values, value);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<char> span) =>
             IndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(ref MemoryMarshal.GetReference(span), span.Length);
 
index e48c000..5898493 100644 (file)
@@ -27,6 +27,10 @@ namespace System.Buffers
         internal override char[] GetValues() => _lookup.GetCharValues();
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(char value) =>
+            _lookup.Contains256(value);
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<char> span) =>
             IndexOfAny<IndexOfAnyAsciiSearcher.DontNegate>(ref MemoryMarshal.GetReference(span), span.Length);
 
index c96af29..1873ac4 100644 (file)
@@ -23,6 +23,15 @@ namespace System.Buffers
         /// <summary>Used by <see cref="IndexOfAnyValuesDebugView{T}"/>.</summary>
         internal virtual T[] GetValues() => throw new UnreachableException();
 
+        /// <summary>
+        /// Searches for the specified value and returns true if found. If not found, returns false.
+        /// </summary>
+        /// <param name="value">The value to search for.</param>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool Contains(T value) => ContainsCore(value);
+
+        internal virtual bool ContainsCore(T value) => throw new UnreachableException();
+
         internal virtual int IndexOfAny(ReadOnlySpan<T> span) => throw new UnreachableException();
         internal virtual int IndexOfAnyExcept(ReadOnlySpan<T> span) => throw new UnreachableException();
         internal virtual int LastIndexOfAny(ReadOnlySpan<T> span) => throw new UnreachableException();
index 19c5c9a..c9c4f1a 100644 (file)
@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics;
 using System.Numerics;
 using System.Runtime.CompilerServices;
 
@@ -10,13 +11,19 @@ namespace System.Buffers
         where T : struct, INumber<T>
     {
         private readonly T _lowInclusive, _highInclusive;
+        private readonly uint _lowUint, _highMinusLow;
 
-        public IndexOfAnyValuesInRange(T lowInclusive, T highInclusive) =>
+        public IndexOfAnyValuesInRange(T lowInclusive, T highInclusive)
+        {
+            Debug.Assert(lowInclusive is byte or char);
             (_lowInclusive, _highInclusive) = (lowInclusive, highInclusive);
+            _lowUint = uint.CreateChecked(lowInclusive);
+            _highMinusLow = uint.CreateChecked(highInclusive - lowInclusive);
+        }
 
         internal override T[] GetValues()
         {
-            T[] values = new T[int.CreateChecked(_highInclusive - _lowInclusive)];
+            T[] values = new T[_highMinusLow + 1];
 
             T element = _lowInclusive;
             for (int i = 0; i < values.Length; i++)
@@ -29,6 +36,10 @@ namespace System.Buffers
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal override bool ContainsCore(T value) =>
+            uint.CreateChecked(value) - _lowUint <= _highMinusLow;
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal override int IndexOfAny(ReadOnlySpan<T> span) =>
             span.IndexOfAnyInRange(_lowInclusive, _highInclusive);
 
index 91f50a1..d00069c 100644 (file)
@@ -6,7 +6,11 @@ namespace System.Buffers
     internal sealed class IndexOfEmptyValues<T> : IndexOfAnyValues<T>
         where T : IEquatable<T>?
     {
-        internal override T[] GetValues() => Array.Empty<T>();
+        internal override T[] GetValues() =>
+            Array.Empty<T>();
+
+        internal override bool ContainsCore(T value) =>
+            false;
 
         internal override int IndexOfAny(ReadOnlySpan<T> span) =>
             -1;
index 7d3a633..e9e5ed6 100644 (file)
@@ -7056,6 +7056,7 @@ namespace System.Buffers
     public class IndexOfAnyValues<T> where T : System.IEquatable<T>?
     {
         internal IndexOfAnyValues() { }
+        public bool Contains(T value) { throw null; }
     }
     public static class IndexOfAnyValues
     {