diff --git a/src/System.Memory/ref/System.Memory.cs b/src/System.Memory/ref/System.Memory.cs index ee872fc66921..8a79d0110a28 100644 --- a/src/System.Memory/ref/System.Memory.cs +++ b/src/System.Memory/ref/System.Memory.cs @@ -100,6 +100,10 @@ public static class MemoryExtensions public static int LastIndexOf(this Span span, T value) where T : IEquatable { throw null; } public static int LastIndexOf(this Span span, ReadOnlySpan value) where T : IEquatable { throw null; } + public static int LastIndexOfAny(this Span span, T value0, T value1) where T : IEquatable { throw null; } + public static int LastIndexOfAny(this Span span, T value0, T value1, T value2) where T : IEquatable { throw null; } + public static int LastIndexOfAny(this Span span, ReadOnlySpan values) where T : IEquatable { throw null; } + public static bool SequenceEqual(this Span first, ReadOnlySpan second) where T : IEquatable { throw null; } public static bool StartsWith(this Span span, ReadOnlySpan value) where T : IEquatable { throw null; } @@ -134,6 +138,10 @@ public static class MemoryExtensions public static int LastIndexOf(this ReadOnlySpan span, T value) where T : IEquatable { throw null; } public static int LastIndexOf(this ReadOnlySpan span, ReadOnlySpan value) where T : IEquatable { throw null; } + public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1) where T : IEquatable { throw null; } + public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1, T value2) where T : IEquatable { throw null; } + public static int LastIndexOfAny(this ReadOnlySpan span, ReadOnlySpan values) where T : IEquatable { throw null; } + public static bool SequenceEqual(this ReadOnlySpan first, ReadOnlySpan second) where T : IEquatable { throw null; } public static bool StartsWith(this ReadOnlySpan span, ReadOnlySpan value) where T : IEquatable { throw null; } @@ -354,7 +362,7 @@ namespace System.Buffers { public const byte MaxPrecision = 99; public const byte NoPrecision = 255; - public StandardFormat(char symbol, byte precision= 255) => throw null; + public StandardFormat(char symbol, byte precision = 255) => throw null; public bool HasPrecision => throw null; public bool IsDefault => throw null; public byte Precision => throw null; diff --git a/src/System.Memory/src/System/MemoryExtensions.cs b/src/System.Memory/src/System/MemoryExtensions.cs index 5a3013eec224..aaaff8221823 100644 --- a/src/System.Memory/src/System/MemoryExtensions.cs +++ b/src/System.Memory/src/System/MemoryExtensions.cs @@ -240,6 +240,122 @@ public static int IndexOfAny(this ReadOnlySpan span, ReadOnlySpan va return SpanHelpers.IndexOfAny(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length); } + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAny(this Span span, T value0, T value1) + where T : IEquatable + { + if (typeof(T) == typeof(byte)) + return SpanHelpers.LastIndexOfAny( + ref Unsafe.As(ref span.DangerousGetPinnableReference()), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, span.Length); + } + + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAny(this Span span, T value0, T value1, T value2) + where T : IEquatable + { + if (typeof(T) == typeof(byte)) + return SpanHelpers.LastIndexOfAny( + ref Unsafe.As(ref span.DangerousGetPinnableReference()), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, value2, span.Length); + } + + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// The set of values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAny(this Span span, ReadOnlySpan values) + where T : IEquatable + { + if (typeof(T) == typeof(byte)) + return SpanHelpers.LastIndexOfAny( + ref Unsafe.As(ref span.DangerousGetPinnableReference()), + span.Length, + ref Unsafe.As(ref values.DangerousGetPinnableReference()), + values.Length); + return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length); + } + + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1) + where T : IEquatable + { + if (typeof(T) == typeof(byte)) + return SpanHelpers.LastIndexOfAny( + ref Unsafe.As(ref span.DangerousGetPinnableReference()), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + span.Length); + return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, span.Length); + } + + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// One of the values to search for. + /// One of the values to search for. + /// One of the values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAny(this ReadOnlySpan span, T value0, T value1, T value2) + where T : IEquatable + { + if (typeof(T) == typeof(byte)) + return SpanHelpers.LastIndexOfAny( + ref Unsafe.As(ref span.DangerousGetPinnableReference()), + Unsafe.As(ref value0), + Unsafe.As(ref value1), + Unsafe.As(ref value2), + span.Length); + return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), value0, value1, value2, span.Length); + } + + /// + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// + /// The span to search. + /// The set of values to search for. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static int LastIndexOfAny(this ReadOnlySpan span, ReadOnlySpan values) + where T : IEquatable + { + if (typeof(T) == typeof(byte)) + return SpanHelpers.LastIndexOfAny( + ref Unsafe.As(ref span.DangerousGetPinnableReference()), + span.Length, + ref Unsafe.As(ref values.DangerousGetPinnableReference()), + values.Length); + return SpanHelpers.LastIndexOfAny(ref span.DangerousGetPinnableReference(), span.Length, ref values.DangerousGetPinnableReference(), values.Length); + } + /// /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). /// diff --git a/src/System.Memory/src/System/SpanHelpers.T.cs b/src/System.Memory/src/System/SpanHelpers.T.cs index db119a89130c..967e60557722 100644 --- a/src/System.Memory/src/System/SpanHelpers.T.cs +++ b/src/System.Memory/src/System/SpanHelpers.T.cs @@ -45,6 +45,27 @@ public static int IndexOf(ref T searchSpace, int searchSpaceLength, ref T val return -1; } + public static int LastIndexOfAny(ref T searchSpace, int searchSpaceLength, ref T value, int valueLength) + where T : IEquatable + { + Debug.Assert(searchSpaceLength >= 0); + Debug.Assert(valueLength >= 0); + + if (valueLength == 0) + return 0; // A zero-length sequence is always treated as "found" at the start of the search space. + + int index = -1; + for (int i = 0; i < valueLength; i++) + { + var tempIndex = LastIndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength); + if (tempIndex != -1) + { + index = (index == -1 || index < tempIndex) ? tempIndex : index; + } + } + return index; + } + public static unsafe int IndexOf(ref T searchSpace, T value, int length) where T : IEquatable { @@ -222,6 +243,170 @@ public static unsafe int LastIndexOf(ref T searchSpace, T value, int length) return length + 7; } + public static unsafe int LastIndexOfAny(ref T searchSpace, T value0, T value1, int length) + where T : IEquatable + { + Debug.Assert(length >= 0); + + T lookUp; + while (length >= 8) + { + length -= 8; + + lookUp = Unsafe.Add(ref searchSpace, length + 7); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found7; + lookUp = Unsafe.Add(ref searchSpace, length + 6); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found6; + lookUp = Unsafe.Add(ref searchSpace, length + 5); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found5; + lookUp = Unsafe.Add(ref searchSpace, length + 4); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found4; + lookUp = Unsafe.Add(ref searchSpace, length + 3); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, length + 2); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, length + 1); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, length); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found; + } + + if (length >= 4) + { + length -= 4; + + lookUp = Unsafe.Add(ref searchSpace, length + 3); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, length + 2); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, length + 1); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, length); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found; + } + + while (length > 0) + { + length--; + + lookUp = Unsafe.Add(ref searchSpace, length); + if (value0.Equals(lookUp) || value1.Equals(lookUp)) + goto Found; + } + return -1; + + Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549 + return length; + Found1: + return length + 1; + Found2: + return length + 2; + Found3: + return length + 3; + Found4: + return length + 4; + Found5: + return length + 5; + Found6: + return length + 6; + Found7: + return length + 7; + } + + public static unsafe int LastIndexOfAny(ref T searchSpace, T value0, T value1, T value2, int length) + where T : IEquatable + { + Debug.Assert(length >= 0); + + T lookUp; + while (length >= 8) + { + length -= 8; + + lookUp = Unsafe.Add(ref searchSpace, length + 7); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found7; + lookUp = Unsafe.Add(ref searchSpace, length + 6); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found6; + lookUp = Unsafe.Add(ref searchSpace, length + 5); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found5; + lookUp = Unsafe.Add(ref searchSpace, length + 4); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found4; + lookUp = Unsafe.Add(ref searchSpace, length + 3); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, length + 2); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, length + 1); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, length); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found; + } + + if (length >= 4) + { + length -= 4; + + lookUp = Unsafe.Add(ref searchSpace, length + 3); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, length + 2); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, length + 1); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, length); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found; + } + + while (length > 0) + { + length--; + + lookUp = Unsafe.Add(ref searchSpace, length); + if (value0.Equals(lookUp) || value1.Equals(lookUp) || value2.Equals(lookUp)) + goto Found; + } + return -1; + + Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549 + return length; + Found1: + return length + 1; + Found2: + return length + 2; + Found3: + return length + 3; + Found4: + return length + 4; + Found5: + return length + 5; + Found6: + return length + 6; + Found7: + return length + 7; + } + public static bool SequenceEqual(ref T first, ref T second, int length) where T : IEquatable { diff --git a/src/System.Memory/src/System/SpanHelpers.byte.cs b/src/System.Memory/src/System/SpanHelpers.byte.cs index 54a6a34a47bd..7e11676bdbcb 100644 --- a/src/System.Memory/src/System/SpanHelpers.byte.cs +++ b/src/System.Memory/src/System/SpanHelpers.byte.cs @@ -68,6 +68,26 @@ public static int IndexOfAny(ref byte searchSpace, int searchSpaceLength, ref by return index; } + public static int LastIndexOfAny(ref byte searchSpace, int searchSpaceLength, ref byte value, int valueLength) + { + Debug.Assert(searchSpaceLength >= 0); + Debug.Assert(valueLength >= 0); + + if (valueLength == 0) + return 0; // A zero-length sequence is always treated as "found" at the start of the search space. + + int index = -1; + for (int i = 0; i < valueLength; i++) + { + var tempIndex = LastIndexOf(ref searchSpace, Unsafe.Add(ref value, i), searchSpaceLength); + if (tempIndex != -1) + { + index = (index == -1 || index < tempIndex) ? tempIndex : index; + } + } + return index; + } + public static unsafe int IndexOf(ref byte searchSpace, byte value, int length) { Debug.Assert(length >= 0); @@ -598,6 +618,269 @@ public static unsafe int IndexOfAny(ref byte searchSpace, byte value0, byte valu return (int)(byte*)(index + 7); } + public static unsafe int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions + IntPtr index = (IntPtr)(uint)length; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations + IntPtr nLength = (IntPtr)(uint)length; +#if !netstandard11 + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + unchecked + { + int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); + nLength = (IntPtr)(((length & (Vector.Count - 1)) + unaligned) & (Vector.Count - 1)); + } + } + SequentialScan: +#endif + uint lookUp; + while ((byte*)nLength >= (byte*)8) + { + nLength -= 8; + index -= 8; + + lookUp = Unsafe.Add(ref searchSpace, index + 7); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found7; + lookUp = Unsafe.Add(ref searchSpace, index + 6); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found6; + lookUp = Unsafe.Add(ref searchSpace, index + 5); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found5; + lookUp = Unsafe.Add(ref searchSpace, index + 4); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found4; + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + } + + if ((byte*)nLength >= (byte*)4) + { + nLength -= 4; + index -= 4; + + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + } + + while ((byte*)nLength > (byte*)0) + { + nLength -= 1; + index -= 1; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp) + goto Found; + } +#if !netstandard11 + if (Vector.IsHardwareAccelerated && ((int)(byte*)index > 0)) + { + nLength = (IntPtr)(uint)((uint)index & ~(Vector.Count - 1)); + // Get comparison Vector + Vector values0 = GetVector(value0); + Vector values1 = GetVector(value1); + + while ((byte*)nLength > (byte*)(Vector.Count - 1)) + { + var vData = Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector.Count)); + var vMatches = Vector.BitwiseOr( + Vector.Equals(vData, values0), + Vector.Equals(vData, values1)); + if (Vector.Zero.Equals(vMatches)) + { + index -= Vector.Count; + nLength -= Vector.Count; + continue; + } + // Find offset of first match + return (int)(byte*)(index) - Vector.Count + LocateLastFoundByte(vMatches); + } + + if ((int)(byte*)index > 0) + { + nLength = index; + goto SequentialScan; + } + } +#endif + return -1; + Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549 + return (int)(byte*)index; + Found1: + return (int)(byte*)(index + 1); + Found2: + return (int)(byte*)(index + 2); + Found3: + return (int)(byte*)(index + 3); + Found4: + return (int)(byte*)(index + 4); + Found5: + return (int)(byte*)(index + 5); + Found6: + return (int)(byte*)(index + 6); + Found7: + return (int)(byte*)(index + 7); + } + + public static unsafe int LastIndexOfAny(ref byte searchSpace, byte value0, byte value1, byte value2, int length) + { + Debug.Assert(length >= 0); + + uint uValue0 = value0; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue1 = value1; // Use uint for comparisons to avoid unnecessary 8->32 extensions + uint uValue2 = value2; // Use uint for comparisons to avoid unnecessary 8->32 extensions + IntPtr index = (IntPtr)(uint)length; // Use UIntPtr for arithmetic to avoid unnecessary 64->32->64 truncations + IntPtr nLength = (IntPtr)(uint)length; +#if !netstandard11 + if (Vector.IsHardwareAccelerated && length >= Vector.Count * 2) + { + unchecked + { + int unaligned = (int)(byte*)Unsafe.AsPointer(ref searchSpace) & (Vector.Count - 1); + nLength = (IntPtr)(((length & (Vector.Count - 1)) + unaligned) & (Vector.Count - 1)); + } + } + SequentialScan: +#endif + uint lookUp; + while ((byte*)nLength >= (byte*)8) + { + nLength -= 8; + index -= 8; + + lookUp = Unsafe.Add(ref searchSpace, index + 7); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found7; + lookUp = Unsafe.Add(ref searchSpace, index + 6); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found6; + lookUp = Unsafe.Add(ref searchSpace, index + 5); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found5; + lookUp = Unsafe.Add(ref searchSpace, index + 4); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found4; + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + } + + if ((byte*)nLength >= (byte*)4) + { + nLength -= 4; + index -= 4; + + lookUp = Unsafe.Add(ref searchSpace, index + 3); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found3; + lookUp = Unsafe.Add(ref searchSpace, index + 2); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found2; + lookUp = Unsafe.Add(ref searchSpace, index + 1); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found1; + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + } + + while ((byte*)nLength > (byte*)0) + { + nLength -= 1; + index -= 1; + + lookUp = Unsafe.Add(ref searchSpace, index); + if (uValue0 == lookUp || uValue1 == lookUp || uValue2 == lookUp) + goto Found; + } +#if !netstandard11 + if (Vector.IsHardwareAccelerated && ((int)(byte*)index > 0)) + { + nLength = (IntPtr)(uint)((uint)index & ~(Vector.Count - 1)); + // Get comparison Vector + Vector values0 = GetVector(value0); + Vector values1 = GetVector(value1); + Vector values2 = GetVector(value2); + while ((byte*)nLength > (byte*)(Vector.Count - 1)) + { + var vData = Unsafe.ReadUnaligned>(ref Unsafe.AddByteOffset(ref searchSpace, index - Vector.Count)); + + var vMatches = Vector.BitwiseOr( + Vector.BitwiseOr( + Vector.Equals(vData, values0), + Vector.Equals(vData, values1)), + Vector.Equals(vData, values2)); + + if (Vector.Zero.Equals(vMatches)) + { + index -= Vector.Count; + nLength -= Vector.Count; + continue; + } + // Find offset of first match + return (int)(byte*)(index) - Vector.Count + LocateLastFoundByte(vMatches); + } + + if ((int)(byte*)index > 0) + { + nLength = index; + goto SequentialScan; + } + } +#endif + return -1; + Found: // Workaround for https://github.com/dotnet/coreclr/issues/13549 + return (int)(byte*)index; + Found1: + return (int)(byte*)(index + 1); + Found2: + return (int)(byte*)(index + 2); + Found3: + return (int)(byte*)(index + 3); + Found4: + return (int)(byte*)(index + 4); + Found5: + return (int)(byte*)(index + 5); + Found6: + return (int)(byte*)(index + 6); + Found7: + return (int)(byte*)(index + 7); + } + public static unsafe bool SequenceEqual(ref byte first, ref byte second, int length) { Debug.Assert(length >= 0); diff --git a/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj b/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj index a45918d6d5d8..f48d5284acc8 100644 --- a/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj +++ b/src/System.Memory/tests/Performance/System.Memory.Performance.Tests.csproj @@ -30,4 +30,4 @@ - + \ No newline at end of file diff --git a/src/System.Memory/tests/ReadOnlySpan/LastIndexOfAny.T.cs b/src/System.Memory/tests/ReadOnlySpan/LastIndexOfAny.T.cs new file mode 100644 index 000000000000..3bb87a091541 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/LastIndexOfAny.T.cs @@ -0,0 +1,937 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Fact] + public static void ZeroLengthLastIndexOfAny_TwoByte() + { + var sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_TwoByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + var span = new ReadOnlySpan(a); + + int[] targets = { default, 99 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 2) == 0 ? 0 : 1; + int target0 = targets[index]; + int target1 = targets[(index + 1) % 2]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_TwoByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = i + 1; + } + var span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = 0; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + int target0 = 0; + int target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_TwoByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + int target0 = rnd.Next(1, 256); + int target1 = rnd.Next(1, 256); + var span = new ReadOnlySpan(a); + + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_TwoByte() + { + for (int length = 3; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + int val = i + 1; + a[i] = val == 200 ? 201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + + var span = new ReadOnlySpan(a); + int idx = span.LastIndexOfAny(200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_TwoByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 98; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 99; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOf_ThreeByte() + { + var sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_ThreeByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + var span = new ReadOnlySpan(a); + + int[] targets = { default, 99, 98 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 3); + int target0 = targets[index]; + int target1 = targets[(index + 1) % 2]; + int target2 = targets[(index + 1) % 3]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_ThreeByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = i + 1; + } + var span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = 0; + int target2 = 0; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = a[targetIndex + 1]; + int target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + int target0 = 0; + int target1 = 0; + int target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_ThreeByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + int target0 = rnd.Next(1, 256); + int target1 = rnd.Next(1, 256); + int target2 = rnd.Next(1, 256); + var span = new ReadOnlySpan(a); + + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_ThreeByte() + { + for (int length = 4; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + int val = i + 1; + a[i] = val == 200 ? 201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + + var span = new ReadOnlySpan(a); + int idx = span.LastIndexOfAny(200, 200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_ThreeByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 98; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98, 99); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 99; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_ManyByte() + { + var sp = new ReadOnlySpan(Array.Empty()); + var values = new ReadOnlySpan(new int[] { 0, 0, 0, 0 }); + int idx = sp.LastIndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new int[] { }); + idx = sp.LastIndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + var span = new ReadOnlySpan(a); + + var values = new ReadOnlySpan(new int[] { default, 99, 98, 0 }); + + for (int i = 0; i < length; i++) + { + int idx = span.LastIndexOfAny(values); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = i + 1; + } + var span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new int[] { a[targetIndex], 0, 0, 0 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new int[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new int[] { 0, 0, 0, a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerLastIndexOfAny_ManyByte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + var a = new int[length]; + int expectedIndex = length / 2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + continue; + } + a[i] = 255; + } + var span = new ReadOnlySpan(a); + + var targets = new int[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + continue; + } + targets[i] = rnd.Next(1, 255); + } + + var values = new ReadOnlySpan(targets); + int idx = span.LastIndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length]; + var targets = new int[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256); + } + var span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerLastIndexOfAny_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length]; + var targets = new int[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256); + } + var span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_ManyByte() + { + for (int length = 5; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + int val = i + 1; + a[i] = val == 200 ? 201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + a[length - 5] = 200; + + var span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(new int[] { 200, 200, 200, 200, 200, 200, 200, 200, 200 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_ManyByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 98; + var span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new int[] { 99, 98, 99, 98, 99, 98 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 99; + var span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new int[] { 99, 99, 99, 99, 99, 99 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_String_TwoByte() + { + var sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.LastIndexOfAny("0", "0"); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_String_TwoByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + var tempSpan = new Span(a); + tempSpan.Fill(""); + ReadOnlySpan span = tempSpan; + + string[] targets = { "", "99" }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 2) == 0 ? 0 : 1; + string target0 = targets[index]; + string target1 = targets[(index + 1) % 2]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_String_TwoByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (i + 1).ToString(); + } + var span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = "0"; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + string target0 = "0"; + string target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_String_TwoByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + string target0 = rnd.Next(1, 256).ToString(); + string target1 = rnd.Next(1, 256).ToString(); + var span = new ReadOnlySpan(a); + + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_String_TwoByte() + { + for (int length = 3; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == "200" ? "201" : val; + } + + a[length - 1] = "200"; + a[length - 2] = "200"; + a[length - 3] = "200"; + + var span = new ReadOnlySpan(a); + int idx = span.LastIndexOfAny("200", "200"); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_String_TwoByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "98"; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "98"); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "99"; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "99"); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOf_String_ThreeByte() + { + var sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.LastIndexOfAny("0", "0", "0"); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_String_ThreeByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + var tempSpan = new Span(a); + tempSpan.Fill(""); + ReadOnlySpan span = tempSpan; + + string[] targets = { "", "99", "98" }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 3); + string target0 = targets[index]; + string target1 = targets[(index + 1) % 2]; + string target2 = targets[(index + 1) % 3]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_String_ThreeByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (i + 1).ToString(); + } + var span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = "0"; + string target2 = "0"; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = a[targetIndex + 1]; + string target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + string target0 = "0"; + string target1 = "0"; + string target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_String_ThreeByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + string target0 = rnd.Next(1, 256).ToString(); + string target1 = rnd.Next(1, 256).ToString(); + string target2 = rnd.Next(1, 256).ToString(); + var span = new ReadOnlySpan(a); + + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_String_ThreeByte() + { + for (int length = 4; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == "200" ? "201" : val; + } + + a[length - 1] = "200"; + a[length - 2] = "200"; + a[length - 3] = "200"; + a[length - 4] = "200"; + + var span = new ReadOnlySpan(a); + int idx = span.LastIndexOfAny("200", "200", "200"); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_String_ThreeByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "98"; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "98", "99"); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "99"; + var span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "99", "99"); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_String_ManyByte() + { + var sp = new ReadOnlySpan(Array.Empty()); + var values = new ReadOnlySpan(new string[] { "0", "0", "0", "0" }); + int idx = sp.LastIndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new string[] { }); + idx = sp.LastIndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_String_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + var tempSpan = new Span(a); + tempSpan.Fill(""); + ReadOnlySpan span = tempSpan; + + var values = new ReadOnlySpan(new string[] { "", "99", "98", "0" }); + + for (int i = 0; i < length; i++) + { + int idx = span.LastIndexOfAny(values); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_String_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (i + 1).ToString(); + } + var span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new string[] { a[targetIndex], "0", "0", "0" }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new string[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new string[] { "0", "0", "0", a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerLastIndexOfAny_String_ManyByte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + var a = new string[length]; + int expectedIndex = length / 2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + a[i] = "val"; + continue; + } + a[i] = "255"; + } + var span = new ReadOnlySpan(a); + + var targets = new string[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + targets[i] = "val"; + continue; + } + targets[i] = rnd.Next(1, 255).ToString(); + } + + var values = new ReadOnlySpan(targets); + int idx = span.LastIndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_String_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length]; + var targets = new string[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256).ToString(); + } + var span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerLastIndexOfAny_String_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length]; + var targets = new string[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256).ToString(); + } + var span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_String_ManyByte() + { + for (int length = 5; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == "200" ? "201" : val; + } + + a[length - 1] = "200"; + a[length - 2] = "200"; + a[length - 3] = "200"; + a[length - 4] = "200"; + a[length - 5] = "200"; + + var span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(new string[] { "200", "200", "200", "200", "200", "200", "200", "200", "200" }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_String_ManyByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "98"; + var span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new string[] { "99", "98", "99", "98", "99", "98" }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "99"; + var span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new string[] { "99", "99", "99", "99", "99", "99" }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + } + } +} diff --git a/src/System.Memory/tests/ReadOnlySpan/LastIndexOfAny.byte.cs b/src/System.Memory/tests/ReadOnlySpan/LastIndexOfAny.byte.cs new file mode 100644 index 000000000000..77f384c8b510 --- /dev/null +++ b/src/System.Memory/tests/ReadOnlySpan/LastIndexOfAny.byte.cs @@ -0,0 +1,521 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Xunit; + +namespace System.SpanTests +{ + public static partial class ReadOnlySpanTests + { + [Theory] + [InlineData("a", "a", 'a', 0)] + [InlineData("ab", "a", 'a', 0)] + [InlineData("aab", "a", 'a', 1)] + [InlineData("acab", "a", 'a', 2)] + [InlineData("acab", "c", 'c', 1)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "lo", 'o', 14)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "ol", 'o', 14)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "ll", 'l', 11)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "lmr", 'r', 17)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "rml", 'r', 17)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "mlr", 'r', 17)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'r', 43)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrzzzzzzzz", "lmr", 'r', 43)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqxzzzzzzzz", "lmr", 'm', 38)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqlzzzzzzzz", "lmr", 'l', 43)] + [InlineData("/localhost:5000/PATH/%2FPATH2/ HTTP/1.1", " %?", ' ', 30)] + [InlineData("/localhost:5000/PATH/%2FPATH2/?key=value HTTP/1.1", " %?", ' ', 40)] + [InlineData("/localhost:5000/PATH/PATH2/?key=value HTTP/1.1", " %?", ' ', 37)] + [InlineData("/localhost:5000/PATH/PATH2/ HTTP/1.1", " %?", ' ', 27)] + public static void LastIndexOfAnyStrings_Byte(string raw, string search, char expectResult, int expectIndex) + { + var buffers = Encoding.UTF8.GetBytes(raw); + var span = new ReadOnlySpan(buffers); + var searchFor = search.ToCharArray(); + var searchForBytes = Encoding.UTF8.GetBytes(searchFor); + + var index = -1; + if (searchFor.Length == 1) + { + index = span.LastIndexOf((byte)searchFor[0]); + } + else if (searchFor.Length == 2) + { + index = span.LastIndexOfAny((byte)searchFor[0], (byte)searchFor[1]); + } + else if (searchFor.Length == 3) + { + index = span.LastIndexOfAny((byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]); + } + else + { + index = span.LastIndexOfAny(new ReadOnlySpan(searchForBytes)); + } + + var found = span[index]; + Assert.Equal((byte)expectResult, found); + Assert.Equal(expectIndex, index); + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_Byte_TwoByte() + { + ReadOnlySpan sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_Byte_TwoByte() + { + Random rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + ReadOnlySpan span = new ReadOnlySpan(a); + + byte[] targets = { default, 99 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 2) == 0 ? 0 : 1; + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_Byte_TwoByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 0; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 0; + byte target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_Byte_TwoByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte target0 = (byte)rnd.Next(1, 256); + byte target1 = (byte)rnd.Next(1, 256); + ReadOnlySpan span = new ReadOnlySpan(a); + + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_Byte_TwoByte() + { + for (int length = 3; 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[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + + ReadOnlySpan span = new ReadOnlySpan(a); + int idx = span.LastIndexOfAny(200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_TwoByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOf_Byte_ThreeByte() + { + ReadOnlySpan sp = new ReadOnlySpan(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_Byte_ThreeByte() + { + Random rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + ReadOnlySpan span = new ReadOnlySpan(a); + + byte[] targets = { default, 99, 98 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 3); + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + byte target2 = targets[(index + 1) % 3]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_Byte_ThreeByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 0; + byte target2 = 0; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + byte target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = 0; + byte target1 = 0; + byte target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_Byte_ThreeByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte target0 = (byte)rnd.Next(1, 256); + byte target1 = (byte)rnd.Next(1, 256); + byte target2 = (byte)rnd.Next(1, 256); + ReadOnlySpan span = new ReadOnlySpan(a); + + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_Byte_ThreeByte() + { + 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[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + + ReadOnlySpan span = new ReadOnlySpan(a); + int idx = span.LastIndexOfAny(200, 200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ThreeByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98, 99); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_Byte_ManyByte() + { + ReadOnlySpan sp = new ReadOnlySpan(Array.Empty()); + var values = new ReadOnlySpan(new byte[] { 0, 0, 0, 0 }); + int idx = sp.LastIndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new byte[] { }); + idx = sp.LastIndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_Byte_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + ReadOnlySpan span = new ReadOnlySpan(a); + + var values = new ReadOnlySpan(new byte[] { default, 99, 98, 0 }); + + for (int i = 0; i < length; i++) + { + int idx = span.LastIndexOfAny(values); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_Byte_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + ReadOnlySpan span = new ReadOnlySpan(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], 0, 0, 0 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { 0, 0, 0, a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerLastIndexOfAny_Byte_ManyByte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + int expectedIndex = length / 2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + continue; + } + a[i] = 255; + } + ReadOnlySpan span = new ReadOnlySpan(a); + + byte[] targets = new byte[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + continue; + } + targets[i] = (byte)rnd.Next(1, 255); + } + + var values = new ReadOnlySpan(targets); + int idx = span.LastIndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_Byte_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte)rnd.Next(1, 256); + } + ReadOnlySpan span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerLastIndexOfAny_Byte_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte)rnd.Next(1, 256); + } + ReadOnlySpan span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_Byte_ManyByte() + { + for (int length = 5; 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[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + a[length - 5] = 200; + + ReadOnlySpan span = new ReadOnlySpan(a); + var values = new ReadOnlySpan(new byte[] { 200, 200, 200, 200, 200, 200, 200, 200, 200 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ManyByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 98, 99, 98, 99, 98 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + ReadOnlySpan span = new ReadOnlySpan(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 99, 99, 99, 99, 99 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + } + } +} diff --git a/src/System.Memory/tests/Span/LastIndexOfAny.T.cs b/src/System.Memory/tests/Span/LastIndexOfAny.T.cs new file mode 100644 index 000000000000..8b6570c54c6e --- /dev/null +++ b/src/System.Memory/tests/Span/LastIndexOfAny.T.cs @@ -0,0 +1,934 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Xunit; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Fact] + public static void ZeroLengthLastIndexOfAny_TwoByte() + { + var sp = new Span(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_TwoByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + var span = new Span(a); + + int[] targets = { default, 99 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 2) == 0 ? 0 : 1; + int target0 = targets[index]; + int target1 = targets[(index + 1) % 2]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_TwoByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = i + 1; + } + var span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = 0; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + int target0 = 0; + int target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_TwoByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + int target0 = rnd.Next(1, 256); + int target1 = rnd.Next(1, 256); + var span = new Span(a); + + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_TwoByte() + { + for (int length = 3; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + int val = i + 1; + a[i] = val == 200 ? 201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + + var span = new Span(a); + int idx = span.LastIndexOfAny(200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_TwoByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 98; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 99; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOf_ThreeByte() + { + var sp = new Span(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_ThreeByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + var span = new Span(a); + + int[] targets = { default, 99, 98 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 3); + int target0 = targets[index]; + int target1 = targets[(index + 1) % 2]; + int target2 = targets[(index + 1) % 3]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_ThreeByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = i + 1; + } + var span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = 0; + int target2 = 0; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + int target0 = a[targetIndex]; + int target1 = a[targetIndex + 1]; + int target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + int target0 = 0; + int target1 = 0; + int target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_ThreeByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + int target0 = rnd.Next(1, 256); + int target1 = rnd.Next(1, 256); + int target2 = rnd.Next(1, 256); + var span = new Span(a); + + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_ThreeByte() + { + for (int length = 4; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + int val = i + 1; + a[i] = val == 200 ? 201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + + var span = new Span(a); + int idx = span.LastIndexOfAny(200, 200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_ThreeByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 98; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98, 99); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 99; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_ManyByte() + { + var sp = new Span(Array.Empty()); + var values = new ReadOnlySpan(new int[] { 0, 0, 0, 0 }); + int idx = sp.LastIndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new int[] { }); + idx = sp.LastIndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + var span = new Span(a); + + var values = new ReadOnlySpan(new int[] { default, 99, 98, 0 }); + + for (int i = 0; i < length; i++) + { + int idx = span.LastIndexOfAny(values); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + a[i] = i + 1; + } + var span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new int[] { a[targetIndex], 0, 0, 0 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new int[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new int[] { 0, 0, 0, a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerLastIndexOfAny_ManyByte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + var a = new int[length]; + int expectedIndex = length / 2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + continue; + } + a[i] = 255; + } + var span = new Span(a); + + var targets = new int[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + continue; + } + targets[i] = rnd.Next(1, 255); + } + + var values = new ReadOnlySpan(targets); + int idx = span.LastIndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length]; + var targets = new int[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256); + } + var span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerLastIndexOfAny_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length]; + var targets = new int[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256); + } + var span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_ManyByte() + { + for (int length = 5; length < byte.MaxValue; length++) + { + var a = new int[length]; + for (int i = 0; i < length; i++) + { + int val = i + 1; + a[i] = val == 200 ? 201 : val; + } + + a[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + a[length - 5] = 200; + + var span = new Span(a); + var values = new ReadOnlySpan(new int[] { 200, 200, 200, 200, 200, 200, 200, 200, 200 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_ManyByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 98; + var span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new int[] { 99, 98, 99, 98, 99, 98 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new int[length + 2]; + a[0] = 99; + a[length + 1] = 99; + var span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new int[] { 99, 99, 99, 99, 99, 99 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_String_TwoByte() + { + var sp = new Span(Array.Empty()); + int idx = sp.LastIndexOfAny("0", "0"); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_String_TwoByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + var span = new Span(a); + span.Fill(""); + + string[] targets = { "", "99" }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 2) == 0 ? 0 : 1; + string target0 = targets[index]; + string target1 = targets[(index + 1) % 2]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_String_TwoByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (i + 1).ToString(); + } + var span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = "0"; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + string target0 = "0"; + string target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_String_TwoByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + string target0 = rnd.Next(1, 256).ToString(); + string target1 = rnd.Next(1, 256).ToString(); + var span = new Span(a); + + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_String_TwoByte() + { + for (int length = 3; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == "200" ? "201" : val; + } + + a[length - 1] = "200"; + a[length - 2] = "200"; + a[length - 3] = "200"; + + var span = new Span(a); + int idx = span.LastIndexOfAny("200", "200"); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_String_TwoByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "98"; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "98"); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "99"; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "99"); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOf_String_ThreeByte() + { + var sp = new Span(Array.Empty()); + int idx = sp.LastIndexOfAny("0", "0", "0"); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_String_ThreeByte() + { + var rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + var span = new Span(a); + span.Fill(""); + + string[] targets = { "", "99", "98" }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 3); + string target0 = targets[index]; + string target1 = targets[(index + 1) % 2]; + string target2 = targets[(index + 1) % 3]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_String_ThreeByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (i + 1).ToString(); + } + var span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = "0"; + string target2 = "0"; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + string target0 = a[targetIndex]; + string target1 = a[targetIndex + 1]; + string target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + string target0 = "0"; + string target1 = "0"; + string target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_String_ThreeByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + string target0 = rnd.Next(1, 256).ToString(); + string target1 = rnd.Next(1, 256).ToString(); + string target2 = rnd.Next(1, 256).ToString(); + var span = new Span(a); + + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_String_ThreeByte() + { + for (int length = 4; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == "200" ? "201" : val; + } + + a[length - 1] = "200"; + a[length - 2] = "200"; + a[length - 3] = "200"; + a[length - 4] = "200"; + + var span = new Span(a); + int idx = span.LastIndexOfAny("200", "200", "200"); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_String_ThreeByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "98"; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "98", "99"); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "99"; + var span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny("99", "99", "99"); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_String_ManyByte() + { + var sp = new Span(Array.Empty()); + var values = new ReadOnlySpan(new string[] { "0", "0", "0", "0" }); + int idx = sp.LastIndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new string[] { }); + idx = sp.LastIndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_String_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + var span = new Span(a); + span.Fill(""); + + var values = new ReadOnlySpan(new string[] { "", "99", "98", "0" }); + + for (int i = 0; i < length; i++) + { + int idx = span.LastIndexOfAny(values); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_String_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + a[i] = (i + 1).ToString(); + } + var span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new string[] { a[targetIndex], "0", "0", "0" }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new string[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new string[] { "0", "0", "0", a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerLastIndexOfAny_String_ManyByte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + var a = new string[length]; + int expectedIndex = length / 2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + a[i] = "val"; + continue; + } + a[i] = "255"; + } + var span = new Span(a); + + var targets = new string[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + targets[i] = "val"; + continue; + } + targets[i] = rnd.Next(1, 255).ToString(); + } + + var values = new ReadOnlySpan(targets); + int idx = span.LastIndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_String_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length]; + var targets = new string[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256).ToString(); + } + var span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerLastIndexOfAny_String_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length]; + var targets = new string[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = rnd.Next(1, 256).ToString(); + } + var span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_String_ManyByte() + { + for (int length = 5; length < byte.MaxValue; length++) + { + var a = new string[length]; + for (int i = 0; i < length; i++) + { + string val = (i + 1).ToString(); + a[i] = val == "200" ? "201" : val; + } + + a[length - 1] = "200"; + a[length - 2] = "200"; + a[length - 3] = "200"; + a[length - 4] = "200"; + a[length - 5] = "200"; + + var span = new Span(a); + var values = new ReadOnlySpan(new string[] { "200", "200", "200", "200", "200", "200", "200", "200", "200" }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_String_ManyByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "98"; + var span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new string[] { "99", "98", "99", "98", "99", "98" }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + var a = new string[length + 2]; + a[0] = "99"; + a[length + 1] = "99"; + var span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new string[] { "99", "99", "99", "99", "99", "99" }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + } + } +} diff --git a/src/System.Memory/tests/Span/LastIndexOfAny.byte.cs b/src/System.Memory/tests/Span/LastIndexOfAny.byte.cs new file mode 100644 index 000000000000..f09b57d602e1 --- /dev/null +++ b/src/System.Memory/tests/Span/LastIndexOfAny.byte.cs @@ -0,0 +1,521 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Text; +using Xunit; + +namespace System.SpanTests +{ + public static partial class SpanTests + { + [Theory] + [InlineData("a", "a", 'a', 0)] + [InlineData("ab", "a", 'a', 0)] + [InlineData("aab", "a", 'a', 1)] + [InlineData("acab", "a", 'a', 2)] + [InlineData("acab", "c", 'c', 1)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "lo", 'o', 14)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "ol", 'o', 14)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "ll", 'l', 11)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "lmr", 'r', 17)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "rml", 'r', 17)] + [InlineData("abcdefghijklmnopqrstuvwxyz", "mlr", 'r', 17)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz", "lmr", 'r', 43)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrzzzzzzzz", "lmr", 'r', 43)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqxzzzzzzzz", "lmr", 'm', 38)] + [InlineData("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqlzzzzzzzz", "lmr", 'l', 43)] + [InlineData("/localhost:5000/PATH/%2FPATH2/ HTTP/1.1", " %?", ' ', 30)] + [InlineData("/localhost:5000/PATH/%2FPATH2/?key=value HTTP/1.1", " %?", ' ', 40)] + [InlineData("/localhost:5000/PATH/PATH2/?key=value HTTP/1.1", " %?", ' ', 37)] + [InlineData("/localhost:5000/PATH/PATH2/ HTTP/1.1", " %?", ' ', 27)] + public static void LastIndexOfAnyStrings_Byte(string raw, string search, char expectResult, int expectIndex) + { + var buffers = Encoding.UTF8.GetBytes(raw); + var span = new Span(buffers); + var searchFor = search.ToCharArray(); + var searchForBytes = Encoding.UTF8.GetBytes(searchFor); + + var index = -1; + if (searchFor.Length == 1) + { + index = span.LastIndexOf((byte)searchFor[0]); + } + else if (searchFor.Length == 2) + { + index = span.LastIndexOfAny((byte)searchFor[0], (byte)searchFor[1]); + } + else if (searchFor.Length == 3) + { + index = span.LastIndexOfAny((byte)searchFor[0], (byte)searchFor[1], (byte)searchFor[2]); + } + else + { + index = span.LastIndexOfAny(new ReadOnlySpan(searchForBytes)); + } + + var found = span[index]; + Assert.Equal((byte)expectResult, found); + Assert.Equal(expectIndex, index); + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_Byte_TwoByte() + { + Span sp = new Span(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_Byte_TwoByte() + { + Random rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + Span span = new Span(a); + + byte[] targets = { default, 99 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 2) == 0 ? 0 : 1; + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_Byte_TwoByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 0; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + + for (int targetIndex = 0; targetIndex < length - 1; targetIndex++) + { + byte target0 = 0; + byte target1 = a[targetIndex + 1]; + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(targetIndex + 1, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_Byte_TwoByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte target0 = (byte)rnd.Next(1, 256); + byte target1 = (byte)rnd.Next(1, 256); + Span span = new Span(a); + + int idx = span.LastIndexOfAny(target0, target1); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_Byte_TwoByte() + { + for (int length = 3; 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[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + + Span span = new Span(a); + int idx = span.LastIndexOfAny(200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_TwoByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + Span span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + Span span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthIndexOf_Byte_ThreeByte() + { + Span sp = new Span(Array.Empty()); + int idx = sp.LastIndexOfAny(0, 0, 0); + Assert.Equal(-1, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_Byte_ThreeByte() + { + Random rnd = new Random(42); + + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + Span span = new Span(a); + + byte[] targets = { default, 99, 98 }; + + for (int i = 0; i < length; i++) + { + int index = rnd.Next(0, 3); + byte target0 = targets[index]; + byte target1 = targets[(index + 1) % 2]; + byte target2 = targets[(index + 1) % 3]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_Byte_ThreeByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = 0; + byte target2 = 0; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = a[targetIndex]; + byte target1 = a[targetIndex + 1]; + byte target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + + for (int targetIndex = 0; targetIndex < length - 2; targetIndex++) + { + byte target0 = 0; + byte target1 = 0; + byte target2 = a[targetIndex + 2]; + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(targetIndex + 2, idx); + } + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_Byte_ThreeByte() + { + var rnd = new Random(42); + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte target0 = (byte)rnd.Next(1, 256); + byte target1 = (byte)rnd.Next(1, 256); + byte target2 = (byte)rnd.Next(1, 256); + Span span = new Span(a); + + int idx = span.LastIndexOfAny(target0, target1, target2); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_Byte_ThreeByte() + { + 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[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + + Span span = new Span(a); + int idx = span.LastIndexOfAny(200, 200, 200); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ThreeByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + Span span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 98, 99); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + Span span = new Span(a, 1, length - 1); + int index = span.LastIndexOfAny(99, 99, 99); + Assert.Equal(-1, index); + } + } + + [Fact] + public static void ZeroLengthLastIndexOfAny_Byte_ManyByte() + { + Span sp = new Span(Array.Empty()); + var values = new ReadOnlySpan(new byte[] { 0, 0, 0, 0 }); + int idx = sp.LastIndexOfAny(values); + Assert.Equal(-1, idx); + + values = new ReadOnlySpan(new byte[] { }); + idx = sp.LastIndexOfAny(values); + Assert.Equal(0, idx); + } + + [Fact] + public static void DefaultFilledLastIndexOfAny_Byte_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + Span span = new Span(a); + + var values = new ReadOnlySpan(new byte[] { default, 99, 98, 0 }); + + for (int i = 0; i < length; i++) + { + int idx = span.LastIndexOfAny(values); + Assert.Equal(span.Length - 1, idx); + } + } + } + + [Fact] + public static void TestMatchLastIndexOfAny_Byte_ManyByte() + { + for (int length = 0; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + for (int i = 0; i < length; i++) + { + a[i] = (byte)(i + 1); + } + Span span = new Span(a); + + for (int targetIndex = 0; targetIndex < length; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], 0, 0, 0 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { a[targetIndex], a[targetIndex + 1], a[targetIndex + 2], a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + + for (int targetIndex = 0; targetIndex < length - 3; targetIndex++) + { + var values = new ReadOnlySpan(new byte[] { 0, 0, 0, a[targetIndex + 3] }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(targetIndex + 3, idx); + } + } + } + + [Fact] + public static void TestMatchValuesLargerLastIndexOfAny_Byte_ManyByte() + { + var rnd = new Random(42); + for (int length = 2; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + int expectedIndex = length / 2; + for (int i = 0; i < length; i++) + { + if (i == expectedIndex) + { + continue; + } + a[i] = 255; + } + Span span = new Span(a); + + byte[] targets = new byte[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + if (i == length + 1) + { + continue; + } + targets[i] = (byte)rnd.Next(1, 255); + } + + var values = new ReadOnlySpan(targets); + int idx = span.LastIndexOfAny(values); + Assert.Equal(expectedIndex, idx); + } + } + + [Fact] + public static void TestNoMatchLastIndexOfAny_Byte_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte)rnd.Next(1, 256); + } + Span span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestNoMatchValuesLargerLastIndexOfAny_Byte_ManyByte() + { + var rnd = new Random(42); + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length]; + byte[] targets = new byte[length * 2]; + for (int i = 0; i < targets.Length; i++) + { + targets[i] = (byte)rnd.Next(1, 256); + } + Span span = new Span(a); + var values = new ReadOnlySpan(targets); + + int idx = span.LastIndexOfAny(values); + Assert.Equal(-1, idx); + } + } + + [Fact] + public static void TestMultipleMatchLastIndexOfAny_Byte_ManyByte() + { + for (int length = 5; 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[length - 1] = 200; + a[length - 2] = 200; + a[length - 3] = 200; + a[length - 4] = 200; + a[length - 5] = 200; + + Span span = new Span(a); + var values = new ReadOnlySpan(new byte[] { 200, 200, 200, 200, 200, 200, 200, 200, 200 }); + int idx = span.LastIndexOfAny(values); + Assert.Equal(length - 1, idx); + } + } + + [Fact] + public static void MakeSureNoChecksGoOutOfRangeLastIndexOfAny_Byte_ManyByte() + { + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 98; + Span span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 98, 99, 98, 99, 98 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + + for (int length = 1; length < byte.MaxValue; length++) + { + byte[] a = new byte[length + 2]; + a[0] = 99; + a[length + 1] = 99; + Span span = new Span(a, 1, length - 1); + var values = new ReadOnlySpan(new byte[] { 99, 99, 99, 99, 99, 99 }); + int index = span.LastIndexOfAny(values); + Assert.Equal(-1, index); + } + } + } +} diff --git a/src/System.Memory/tests/System.Memory.Tests.csproj b/src/System.Memory/tests/System.Memory.Tests.csproj index 8a9cb59d7d31..fc258f3985ea 100644 --- a/src/System.Memory/tests/System.Memory.Tests.csproj +++ b/src/System.Memory/tests/System.Memory.Tests.csproj @@ -38,6 +38,8 @@ + + @@ -76,6 +78,8 @@ + + @@ -169,4 +173,4 @@ - + \ No newline at end of file