Skip to content

Commit

Permalink
Add initial version of {Last}IndexOfAnyExcept (#67941)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
stephentoub authored Apr 19, 2022
1 parent 765ecde commit e62a1fe
Show file tree
Hide file tree
Showing 4 changed files with 509 additions and 1 deletion.
16 changes: 16 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,14 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
public static int IndexOfAny<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAny<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAny<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.Span<T> span, T value) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
public static int IndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
public static int IndexOf<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
public static int IndexOf<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
public static int IndexOf<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
Expand All @@ -77,6 +85,14 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
public static int LastIndexOfAny<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAny<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAny<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, T value) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.Span<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOfAnyExcept<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOf<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOf<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
public static int LastIndexOf<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
Expand Down
185 changes: 185 additions & 0 deletions src/libraries/System.Memory/tests/Span/IndexOfAnyExcept.T.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.Generic;
using Xunit;

namespace System.SpanTests
{
public class IndexOfAnyExceptTests_Byte : IndexOfAnyExceptTests<byte> { protected override byte Create(int value) => (byte)value; }
public class IndexOfAnyExceptTests_Char : IndexOfAnyExceptTests<char> { protected override char Create(int value) => (char)value; }
public class IndexOfAnyExceptTests_Int32 : IndexOfAnyExceptTests<int> { protected override int Create(int value) => value; }
public class IndexOfAnyExceptTests_Int64 : IndexOfAnyExceptTests<long> { protected override long Create(int value) => value; }
public class IndexOfAnyExceptTests_String : IndexOfAnyExceptTests<string> { protected override string Create(int value) => ((char)value).ToString(); }
public class IndexOfAnyExceptTests_Record : IndexOfAnyExceptTests<SimpleRecord> { protected override SimpleRecord Create(int value) => new SimpleRecord(value); }

public record SimpleRecord(int Value);

public abstract class IndexOfAnyExceptTests<T> where T : IEquatable<T>
{
private readonly T _a, _b, _c, _d, _e;

public IndexOfAnyExceptTests()
{
_a = Create('a');
_b = Create('b');
_c = Create('c');
_d = Create('d');
_e = Create('e');
}

/// <summary>Validate that the methods return -1 when the source span is empty.</summary>
[Fact]
public void ZeroLengthSpan_ReturnNegative1()
{
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty));
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, _a));
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, _a, _b));
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, _a, _b, _c));
Assert.Equal(-1, IndexOfAnyExcept(Span<T>.Empty, new[] { _a, _b, _c, _d }));

Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty));
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, _a));
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, _a, _b));
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, _a, _b, _c));
Assert.Equal(-1, LastIndexOfAnyExcept(Span<T>.Empty, new[] { _a, _b, _c, _d }));
}

public static IEnumerable<object[]> AllElementsMatch_ReturnsNegative1_MemberData()
{
foreach (int length in new[] { 1, 2, 4, 7, 15, 16, 17, 31, 32, 33, 100 })
{
yield return new object[] { length };
}
}

/// <summary>Validate that the methods return -1 when the source span contains only the values being excepted.</summary>
[Theory]
[MemberData(nameof(AllElementsMatch_ReturnsNegative1_MemberData))]
public void AllElementsMatch_ReturnsNegative1(int length)
{
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a), _a));
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a, _b), _a, _b));
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a, _b, _c), _a, _b, _c));
Assert.Equal(-1, IndexOfAnyExcept(CreateArray(length, _a, _b, _c, _d), _a, _b, _c, _d));

Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a), _a));
Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a, _b), _a, _b));
Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a, _b, _c), _a, _b, _c));
Assert.Equal(-1, LastIndexOfAnyExcept(CreateArray(length, _a, _b, _c, _d), _a, _b, _c, _d));
}

public static IEnumerable<object[]> SomeElementsDontMatch_ReturnsOffset_MemberData()
{
yield return new object[] { 1, new[] { 0 } };
yield return new object[] { 2, new[] { 0, 1 } };
yield return new object[] { 4, new[] { 2 } };
yield return new object[] { 5, new[] { 2 } };
yield return new object[] { 31, new[] { 30 } };
yield return new object[] { 10, new[] { 1, 7 } };
yield return new object[] { 100, new[] { 19, 20, 21, 80 } };
}

/// <summary>Validate that the methods return the expected position when the source span contains some data other than the excepted.</summary>
[Theory]
[MemberData(nameof(SomeElementsDontMatch_ReturnsOffset_MemberData))]
public void SomeElementsDontMatch_ReturnsOffset(int length, int[] matchPositions)
{
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a), _e, matchPositions), _a));
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a, _b), _e, matchPositions), _a, _b));
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c), _e, matchPositions), _a, _b, _c));
Assert.Equal(matchPositions[0], IndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c, _d), _e, matchPositions), _a, _b, _c, _d));

Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a), _e, matchPositions), _a));
Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a, _b), _e, matchPositions), _a, _b));
Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c), _e, matchPositions), _a, _b, _c));
Assert.Equal(matchPositions[^1], LastIndexOfAnyExcept(Set(CreateArray(length, _a, _b, _c, _d), _e, matchPositions), _a, _b, _c, _d));
}

protected abstract T Create(int value);

private T[] CreateArray(int length, params T[] values)
{
var arr = new T[length];
for (int i = 0; i < arr.Length; i++)
{
arr[i] = values[i % values.Length];
}
return arr;
}

private T[] Set(T[] arr, T value, params int[] valuePositions)
{
foreach (int pos in valuePositions)
{
arr[pos] = value;
}
return arr;
}

// Wrappers for {Last}IndexOfAnyExcept that invoke both the Span and ReadOnlySpan overloads,
// as well as the values overloads, ensuring they all produce the same result, and returning that result.
// This avoids needing to code the same call sites twice in all the above tests.
private static int IndexOfAnyExcept(Span<T> span, T value)
{
int result = MemoryExtensions.IndexOfAnyExcept(span, value);
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, value));
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span<T>)span, new[] { value }));
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value }));
return result;
}
private static int IndexOfAnyExcept(Span<T> span, T value0, T value1)
{
int result = MemoryExtensions.IndexOfAnyExcept(span, value0, value1);
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1));
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span<T>)span, new[] { value0, value1 }));
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1 }));
return result;
}
private static int IndexOfAnyExcept(Span<T> span, T value0, T value1, T value2)
{
int result = MemoryExtensions.IndexOfAnyExcept(span, value0, value1, value2);
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1, value2));
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((Span<T>)span, new[] { value0, value1, value2 }));
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1, value2 }));
return result;
}
private static int IndexOfAnyExcept(Span<T> span, params T[] values)
{
int result = MemoryExtensions.IndexOfAnyExcept(span, values);
Assert.Equal(result, MemoryExtensions.IndexOfAnyExcept((ReadOnlySpan<T>)span, values));
return result;
}
private static int LastIndexOfAnyExcept(Span<T> span, T value)
{
int result = MemoryExtensions.LastIndexOfAnyExcept(span, value);
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, value));
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span<T>)span, new[] { value }));
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value }));
return result;
}
private static int LastIndexOfAnyExcept(Span<T> span, T value0, T value1)
{
int result = MemoryExtensions.LastIndexOfAnyExcept(span, value0, value1);
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1));
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span<T>)span, new[] { value0, value1 }));
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1 }));
return result;
}
private static int LastIndexOfAnyExcept(Span<T> span, T value0, T value1, T value2)
{
int result = MemoryExtensions.LastIndexOfAnyExcept(span, value0, value1, value2);
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, value0, value1, value2));
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((Span<T>)span, new[] { value0, value1, value2 }));
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, new[] { value0, value1, value2 }));
return result;
}
private static int LastIndexOfAnyExcept(Span<T> span, params T[] values)
{
int result = MemoryExtensions.LastIndexOfAnyExcept(span, values);
Assert.Equal(result, MemoryExtensions.LastIndexOfAnyExcept((ReadOnlySpan<T>)span, values));
return result;
}
}
}
3 changes: 2 additions & 1 deletion src/libraries/System.Memory/tests/System.Memory.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TestRuntime>true</TestRuntime>
Expand Down Expand Up @@ -81,6 +81,7 @@
<Compile Include="Span\IndexOfAny.byte.cs" />
<Compile Include="Span\IndexOfAny.char.cs" />
<Compile Include="Span\IndexOfAny.T.cs" />
<Compile Include="Span\IndexOfAnyExcept.T.cs" />
<Compile Include="Span\IndexOfSequence.byte.cs" />
<Compile Include="Span\IndexOfSequence.char.cs" />
<Compile Include="Span\IndexOfSequence.T.cs" />
Expand Down
Loading

0 comments on commit e62a1fe

Please sign in to comment.