Skip to content

Commit e62a1fe

Browse files
authored
Add initial version of {Last}IndexOfAnyExcept (#67941)
* Add initial version of {Last}IndexOfAnyExcept These are functional but not vectorized. At least some of these should be vectorized for at least some data types subsequently, but that's a more intensive change. Once that's in, we can update a few places to use these, e.g. Regex should end up using any of the overloads that are vectorized. * Fix comments
1 parent 765ecde commit e62a1fe

File tree

4 files changed

+509
-1
lines changed

4 files changed

+509
-1
lines changed

src/libraries/System.Memory/ref/System.Memory.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
6565
public static int IndexOfAny<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
6666
public static int IndexOfAny<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
6767
public static int IndexOfAny<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
68+
public static int IndexOfAnyExcept<T>(this System.Span<T> span, T value) where T : System.IEquatable<T> { throw null; }
69+
public static int IndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
70+
public static int IndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
71+
public static int IndexOfAnyExcept<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
72+
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
73+
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
74+
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
75+
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
6876
public static int IndexOf<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
6977
public static int IndexOf<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
7078
public static int IndexOf<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
@@ -77,6 +85,14 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
7785
public static int LastIndexOfAny<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
7886
public static int LastIndexOfAny<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
7987
public static int LastIndexOfAny<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
88+
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, T value) where T : System.IEquatable<T> { throw null; }
89+
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
90+
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
91+
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
92+
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
93+
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
94+
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
95+
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
8096
public static int LastIndexOf<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
8197
public static int LastIndexOf<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
8298
public static int LastIndexOf<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections;
5+
using System.Collections.Generic;
6+
using Xunit;
7+
8+
namespace System.SpanTests
9+
{
10+
public class IndexOfAnyExceptTests_Byte : IndexOfAnyExceptTests<byte> { protected override byte Create(int value) => (byte)value; }
11+
public class IndexOfAnyExceptTests_Char : IndexOfAnyExceptTests<char> { protected override char Create(int value) => (char)value; }
12+
public class IndexOfAnyExceptTests_Int32 : IndexOfAnyExceptTests<int> { protected override int Create(int value) => value; }
13+
public class IndexOfAnyExceptTests_Int64 : IndexOfAnyExceptTests<long> { protected override long Create(int value) => value; }
14+
public class IndexOfAnyExceptTests_String : IndexOfAnyExceptTests<string> { protected override string Create(int value) => ((char)value).ToString(); }
15+
public class IndexOfAnyExceptTests_Record : IndexOfAnyExceptTests<SimpleRecord> { protected override SimpleRecord Create(int value) => new SimpleRecord(value); }
16+
17+
public record SimpleRecord(int Value);
18+
19+
public abstract class IndexOfAnyExceptTests<T> where T : IEquatable<T>
20+
{
21+
private readonly T _a, _b, _c, _d, _e;
22+
23+
public IndexOfAnyExceptTests()
24+
{
25+
_a = Create('a');
26+
_b = Create('b');
27+
_c = Create('c');
28+
_d = Create('d');
29+
_e = Create('e');
30+
}
31+
32+
/// <summary>Validate that the methods return -1 when the source span is empty.</summary>
33+
[Fact]
34+
public void ZeroLengthSpan_ReturnNegative1()
35+
{
36+
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty));
37+
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, _a));
38+
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, _a, _b));
39+
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, _a, _b, _c));
40+
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, new[] { _a, _b, _c, _d }));
41+
42+
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty));
43+
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, _a));
44+
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, _a, _b));
45+
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, _a, _b, _c));
46+
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, new[] { _a, _b, _c, _d }));
47+
}
48+
49+
public static IEnumerable<object[]> AllElementsMatch_ReturnsNegative1_MemberData()
50+
{
51+
foreach (int length in new[] { 1, 2, 4, 7, 15, 16, 17, 31, 32, 33, 100 })
52+
{
53+
yield return new object[] { length };
54+
}
55+
}
56+
57+
/// <summary>Validate that the methods return -1 when the source span contains only the values being excepted.</summary>
58+
[Theory]
59+
[MemberData(nameof(AllElementsMatch_ReturnsNegative1_MemberData))]
60+
public void AllElementsMatch_ReturnsNegative1(int length)
61+
{
62+
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a), _a));
63+
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a, _b), _a, _b));
64+
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a, _b, _c), _a, _b, _c));
65+
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a, _b, _c, _d), _a, _b, _c, _d));
66+
67+
Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a), _a));
68+
Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a, _b), _a, _b));
69+
Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a, _b, _c), _a, _b, _c));
70+
Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a, _b, _c, _d), _a, _b, _c, _d));
71+
}
72+
73+
public static IEnumerable<object[]> SomeElementsDontMatch_ReturnsOffset_MemberData()
74+
{
75+
yield return new object[] { 1, new[] { 0 } };
76+
yield return new object[] { 2, new[] { 0, 1 } };
77+
yield return new object[] { 4, new[] { 2 } };
78+
yield return new object[] { 5, new[] { 2 } };
79+
yield return new object[] { 31, new[] { 30 } };
80+
yield return new object[] { 10, new[] { 1, 7 } };
81+
yield return new object[] { 100, new[] { 19, 20, 21, 80 } };
82+
}
83+
84+
/// <summary>Validate that the methods return the expected position when the source span contains some data other than the excepted.</summary>
85+
[Theory]
86+
[MemberData(nameof(SomeElementsDontMatch_ReturnsOffset_MemberData))]
87+
public void SomeElementsDontMatch_ReturnsOffset(int length, int[] matchPositions)
88+
{
89+
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a), _e, matchPositions), _a));
90+
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a, _b), _e, matchPositions), _a, _b));
91+
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c), _e, matchPositions), _a, _b, _c));
92+
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c, _d), _e, matchPositions), _a, _b, _c, _d));
93+
94+
Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a), _e, matchPositions), _a));
95+
Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a, _b), _e, matchPositions), _a, _b));
96+
Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c), _e, matchPositions), _a, _b, _c));
97+
Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c, _d), _e, matchPositions), _a, _b, _c, _d));
98+
}
99+
100+
protected abstract T Create(int value);
101+
102+
private T[] CreateArray(int length, params T[] values)
103+
{
104+
var arr = new T[length];
105+
for (int i = 0; i < arr.Length; i++)
106+
{
107+
arr[i] = values[i % values.Length];
108+
}
109+
return arr;
110+
}
111+
112+
private T[] Set(T[] arr, T value, params int[] valuePositions)
113+
{
114+
foreach (int pos in valuePositions)
115+
{
116+
arr[pos] = value;
117+
}
118+
return arr;
119+
}
120+
121+
// Wrappers for {Last}IndexOfAnyExcept that invoke both the Span and ReadOnlySpan overloads,
122+
// as well as the values overloads, ensuring they all produce the same result, and returning that result.
123+
// This avoids needing to code the same call sites twice in all the above tests.
124+
private static int IndexOfAnyExcept(Span<T> span, T value)
125+
{
126+
int result = MemoryExtensions.IndexOfAnyExcept(span, value);
127+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, value));
128+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span<T>)span, new[] { value }));
129+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value }));
130+
return result;
131+
}
132+
private static int IndexOfAnyExcept(Span<T> span, T value0, T value1)
133+
{
134+
int result = MemoryExtensions.IndexOfAnyExcept(span, value0, value1);
135+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1));
136+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span<T>)span, new[] { value0, value1 }));
137+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1 }));
138+
return result;
139+
}
140+
private static int IndexOfAnyExcept(Span<T> span, T value0, T value1, T value2)
141+
{
142+
int result = MemoryExtensions.IndexOfAnyExcept(span, value0, value1, value2);
143+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1, value2));
144+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span<T>)span, new[] { value0, value1, value2 }));
145+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1, value2 }));
146+
return result;
147+
}
148+
private static int IndexOfAnyExcept(Span<T> span, params T[] values)
149+
{
150+
int result = MemoryExtensions.IndexOfAnyExcept(span, values);
151+
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, values));
152+
return result;
153+
}
154+
private static int LastIndexOfAnyExcept(Span<T> span, T value)
155+
{
156+
int result = MemoryExtensions.LastIndexOfAnyExcept(span, value);
157+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, value));
158+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span<T>)span, new[] { value }));
159+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value }));
160+
return result;
161+
}
162+
private static int LastIndexOfAnyExcept(Span<T> span, T value0, T value1)
163+
{
164+
int result = MemoryExtensions.LastIndexOfAnyExcept(span, value0, value1);
165+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1));
166+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span<T>)span, new[] { value0, value1 }));
167+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1 }));
168+
return result;
169+
}
170+
private static int LastIndexOfAnyExcept(Span<T> span, T value0, T value1, T value2)
171+
{
172+
int result = MemoryExtensions.LastIndexOfAnyExcept(span, value0, value1, value2);
173+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1, value2));
174+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span<T>)span, new[] { value0, value1, value2 }));
175+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1, value2 }));
176+
return result;
177+
}
178+
private static int LastIndexOfAnyExcept(Span<T> span, params T[] values)
179+
{
180+
int result = MemoryExtensions.LastIndexOfAnyExcept(span, values);
181+
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, values));
182+
return result;
183+
}
184+
}
185+
}

src/libraries/System.Memory/tests/System.Memory.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
44
<TestRuntime>true</TestRuntime>
@@ -81,6 +81,7 @@
8181
<Compile Include="Span\IndexOfAny.byte.cs" />
8282
<Compile Include="Span\IndexOfAny.char.cs" />
8383
<Compile Include="Span\IndexOfAny.T.cs" />
84+
<Compile Include="Span\IndexOfAnyExcept.T.cs" />
8485
<Compile Include="Span\IndexOfSequence.byte.cs" />
8586
<Compile Include="Span\IndexOfSequence.char.cs" />
8687
<Compile Include="Span\IndexOfSequence.T.cs" />

0 commit comments

Comments
 (0)