Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 436690c

Browse files
zaytsev-victorahsonkhan
authored andcommitted
Added SpanExtensions.EndsWith (#25416)
* Added SpanExtensions.EndsWith * Address PR feedback * Added tests for ReadOnlySpan.
1 parent 3240aab commit 436690c

File tree

7 files changed

+445
-0
lines changed

7 files changed

+445
-0
lines changed

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ public static class MemoryExtensions
101101

102102
public static bool StartsWith<T>(this Span<T> span, ReadOnlySpan<T> value) where T : IEquatable<T> { throw null; }
103103

104+
public static bool EndsWith<T>(this Span<T> span, ReadOnlySpan<T> value) where T : IEquatable<T> { throw null; }
105+
public static bool EndsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value) where T : IEquatable<T> { throw null; }
106+
104107
public static Span<byte> AsBytes<T>(this Span<T> source) where T : struct { throw null; }
105108

106109
public static Span<TTo> NonPortableCast<TFrom, TTo>(this Span<TFrom> source) where TFrom : struct where TTo : struct { throw null; }

src/System.Memory/src/System/MemoryExtensions.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,50 @@ ref Unsafe.As<T, byte>(ref value.DangerousGetPinnableReference()),
222222
return valueLength <= span.Length && SpanHelpers.SequenceEqual(ref span.DangerousGetPinnableReference(), ref value.DangerousGetPinnableReference(), valueLength);
223223
}
224224

225+
/// <summary>
226+
/// Determines whether the specified sequence appears at the end of the span.
227+
/// </summary>
228+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
229+
public static bool EndsWith<T>(this Span<T> span, ReadOnlySpan<T> value)
230+
where T : IEquatable<T>
231+
{
232+
int spanLength = span.Length;
233+
int valueLength = value.Length;
234+
if (typeof(T) == typeof(byte))
235+
return valueLength <= spanLength &&
236+
SpanHelpers.SequenceEqual(
237+
ref Unsafe.As<T, byte>(ref Unsafe.Add(ref span.DangerousGetPinnableReference(), spanLength - valueLength)),
238+
ref Unsafe.As<T, byte>(ref value.DangerousGetPinnableReference()),
239+
valueLength);
240+
return valueLength <= spanLength &&
241+
SpanHelpers.SequenceEqual(
242+
ref Unsafe.Add(ref span.DangerousGetPinnableReference(), spanLength - valueLength),
243+
ref value.DangerousGetPinnableReference(),
244+
valueLength);
245+
}
246+
247+
/// <summary>
248+
/// Determines whether the specified sequence appears at the end of the span.
249+
/// </summary>
250+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
251+
public static bool EndsWith<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> value)
252+
where T : IEquatable<T>
253+
{
254+
int spanLength = span.Length;
255+
int valueLength = value.Length;
256+
if (typeof(T) == typeof(byte))
257+
return valueLength <= spanLength &&
258+
SpanHelpers.SequenceEqual(
259+
ref Unsafe.As<T, byte>(ref Unsafe.Add(ref span.DangerousGetPinnableReference(), spanLength - valueLength)),
260+
ref Unsafe.As<T, byte>(ref value.DangerousGetPinnableReference()),
261+
valueLength);
262+
return valueLength <= spanLength &&
263+
SpanHelpers.SequenceEqual(
264+
ref Unsafe.Add(ref span.DangerousGetPinnableReference(), spanLength - valueLength),
265+
ref value.DangerousGetPinnableReference(),
266+
valueLength);
267+
}
268+
225269
/// <summary>
226270
/// Creates a new span over the portion of the target array.
227271
/// </summary>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
// See the LICENSE file in the project root for more information.
4+
5+
using Xunit;
6+
7+
namespace System.SpanTests
8+
{
9+
public static partial class ReadOnlySpanTests
10+
{
11+
[Fact]
12+
public static void ZeroLengthEndsWith()
13+
{
14+
int[] a = new int[3];
15+
16+
ReadOnlySpan<int> first = new ReadOnlySpan<int>(a, 1, 0);
17+
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 2, 0);
18+
bool b = first.EndsWith(second);
19+
Assert.True(b);
20+
}
21+
22+
[Fact]
23+
public static void SameSpanEndsWith()
24+
{
25+
int[] a = { 4, 5, 6 };
26+
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a);
27+
bool b = span.EndsWith(span);
28+
Assert.True(b);
29+
}
30+
31+
[Fact]
32+
public static void LengthMismatchEndsWith()
33+
{
34+
int[] a = { 4, 5, 6 };
35+
ReadOnlySpan<int> first = new ReadOnlySpan<int>(a, 0, 2);
36+
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 0, 3);
37+
bool b = first.EndsWith(second);
38+
Assert.False(b);
39+
}
40+
41+
[Fact]
42+
public static void EndsWithMatch()
43+
{
44+
int[] a = { 4, 5, 6 };
45+
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a, 0, 3);
46+
ReadOnlySpan<int> slice = new ReadOnlySpan<int>(a, 1, 2);
47+
bool b = span.EndsWith(slice);
48+
Assert.True(b);
49+
}
50+
51+
[Fact]
52+
public static void EndsWithMatchDifferentSpans()
53+
{
54+
int[] a = { 4, 5, 6 };
55+
int[] b = { 4, 5, 6 };
56+
ReadOnlySpan<int> span = new ReadOnlySpan<int>(a, 0, 3);
57+
ReadOnlySpan<int> slice = new ReadOnlySpan<int>(b, 0, 3);
58+
bool c = span.EndsWith(slice);
59+
Assert.True(c);
60+
}
61+
62+
[Fact]
63+
public static void OnEndsWithOfEqualSpansMakeSureEveryElementIsCompared()
64+
{
65+
for (int length = 0; length < 100; length++)
66+
{
67+
TIntLog log = new TIntLog();
68+
69+
TInt[] first = new TInt[length];
70+
TInt[] second = new TInt[length];
71+
for (int i = 0; i < length; i++)
72+
{
73+
first[i] = second[i] = new TInt(10 * (i + 1), log);
74+
}
75+
76+
ReadOnlySpan<TInt> firstSpan = new ReadOnlySpan<TInt>(first);
77+
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second);
78+
bool b = firstSpan.EndsWith(secondSpan);
79+
Assert.True(b);
80+
81+
// Make sure each element of the array was compared once. (Strictly speaking, it would not be illegal for
82+
// EndsWith to compare an element more than once but that would be a non-optimal implementation and
83+
// a red flag. So we'll stick with the stricter test.)
84+
Assert.Equal(first.Length, log.Count);
85+
foreach (TInt elem in first)
86+
{
87+
int numCompares = log.CountCompares(elem.Value, elem.Value);
88+
Assert.True(numCompares == 1, $"Expected {numCompares} == 1 for element {elem.Value}.");
89+
}
90+
}
91+
}
92+
}
93+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
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+
// See the LICENSE file in the project root for more information.
4+
5+
using Xunit;
6+
7+
namespace System.SpanTests
8+
{
9+
public static partial class ReadOnlySpanTests
10+
{
11+
[Fact]
12+
public static void ZeroLengthEndsWith_Byte()
13+
{
14+
byte[] a = new byte[3];
15+
16+
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);
17+
ReadOnlySpan<byte> slice = new ReadOnlySpan<byte>(a, 2, 0);
18+
bool b = span.EndsWith(slice);
19+
Assert.True(b);
20+
}
21+
22+
[Fact]
23+
public static void SameSpanEndsWith_Byte()
24+
{
25+
byte[] a = { 4, 5, 6 };
26+
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a);
27+
bool b = span.EndsWith(span);
28+
Assert.True(b);
29+
}
30+
31+
[Fact]
32+
public static void LengthMismatchEndsWith_Byte()
33+
{
34+
byte[] a = { 4, 5, 6 };
35+
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a, 0, 2);
36+
ReadOnlySpan<byte> slice = new ReadOnlySpan<byte>(a, 0, 3);
37+
bool b = span.EndsWith(slice);
38+
Assert.False(b);
39+
}
40+
41+
[Fact]
42+
public static void EndsWithMatch_Byte()
43+
{
44+
byte[] a = { 4, 5, 6 };
45+
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a, 0, 3);
46+
ReadOnlySpan<byte> slice = new ReadOnlySpan<byte>(a, 1, 2);
47+
bool b = span.EndsWith(slice);
48+
Assert.True(b);
49+
}
50+
51+
[Fact]
52+
public static void EndsWithMatchDifferentSpans_Byte()
53+
{
54+
byte[] a = { 4, 5, 6 };
55+
byte[] b = { 4, 5, 6 };
56+
ReadOnlySpan<byte> span = new ReadOnlySpan<byte>(a, 0, 3);
57+
ReadOnlySpan<byte> slice = new ReadOnlySpan<byte>(b, 0, 3);
58+
bool c = span.EndsWith(slice);
59+
Assert.True(c);
60+
}
61+
62+
[Fact]
63+
public static void EndsWithNoMatch_Byte()
64+
{
65+
for (int length = 1; length < 32; length++)
66+
{
67+
for (int mismatchIndex = 0; mismatchIndex < length; mismatchIndex++)
68+
{
69+
byte[] first = new byte[length];
70+
byte[] second = new byte[length];
71+
for (int i = 0; i < length; i++)
72+
{
73+
first[i] = second[i] = (byte)(i + 1);
74+
}
75+
76+
second[mismatchIndex] = (byte)(second[mismatchIndex] + 1);
77+
78+
ReadOnlySpan<byte> firstSpan = new ReadOnlySpan<byte>(first);
79+
ReadOnlySpan<byte> secondSpan = new ReadOnlySpan<byte>(second);
80+
bool b = firstSpan.EndsWith(secondSpan);
81+
Assert.False(b);
82+
}
83+
}
84+
}
85+
86+
[Fact]
87+
public static void MakeSureNoEndsWithChecksGoOutOfRange_Byte()
88+
{
89+
for (int length = 0; length < 100; length++)
90+
{
91+
byte[] first = new byte[length + 2];
92+
first[0] = 99;
93+
first[length + 1] = 99;
94+
byte[] second = new byte[length + 2];
95+
second[0] = 100;
96+
second[length + 1] = 100;
97+
ReadOnlySpan<byte> span1 = new ReadOnlySpan<byte>(first, 1, length);
98+
ReadOnlySpan<byte> span2 = new ReadOnlySpan<byte>(second, 1, length);
99+
bool b = span1.EndsWith(span2);
100+
Assert.True(b);
101+
}
102+
}
103+
}
104+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
// See the LICENSE file in the project root for more information.
4+
5+
using Xunit;
6+
7+
namespace System.SpanTests
8+
{
9+
public static partial class SpanTests
10+
{
11+
[Fact]
12+
public static void ZeroLengthEndsWith()
13+
{
14+
int[] a = new int[3];
15+
16+
Span<int> first = new Span<int>(a, 1, 0);
17+
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 2, 0);
18+
bool b = first.EndsWith(second);
19+
Assert.True(b);
20+
}
21+
22+
[Fact]
23+
public static void SameSpanEndsWith()
24+
{
25+
int[] a = { 4, 5, 6 };
26+
Span<int> span = new Span<int>(a);
27+
bool b = span.EndsWith(span);
28+
Assert.True(b);
29+
}
30+
31+
[Fact]
32+
public static void LengthMismatchEndsWith()
33+
{
34+
int[] a = { 4, 5, 6 };
35+
Span<int> first = new Span<int>(a, 0, 2);
36+
ReadOnlySpan<int> second = new ReadOnlySpan<int>(a, 0, 3);
37+
bool b = first.EndsWith(second);
38+
Assert.False(b);
39+
}
40+
41+
[Fact]
42+
public static void EndsWithMatch()
43+
{
44+
int[] a = { 4, 5, 6 };
45+
Span<int> span = new Span<int>(a, 0, 3);
46+
ReadOnlySpan<int> slice = new ReadOnlySpan<int>(a, 1, 2);
47+
bool b = span.EndsWith(slice);
48+
Assert.True(b);
49+
}
50+
51+
[Fact]
52+
public static void EndsWithMatchDifferentSpans()
53+
{
54+
int[] a = { 4, 5, 6 };
55+
int[] b = { 4, 5, 6 };
56+
Span<int> span = new Span<int>(a, 0, 3);
57+
ReadOnlySpan<int> slice = new ReadOnlySpan<int>(b, 0, 3);
58+
bool c = span.EndsWith(slice);
59+
Assert.True(c);
60+
}
61+
62+
[Fact]
63+
public static void OnEndsWithOfEqualSpansMakeSureEveryElementIsCompared()
64+
{
65+
for (int length = 0; length < 100; length++)
66+
{
67+
TIntLog log = new TIntLog();
68+
69+
TInt[] first = new TInt[length];
70+
TInt[] second = new TInt[length];
71+
for (int i = 0; i < length; i++)
72+
{
73+
first[i] = second[i] = new TInt(10 * (i + 1), log);
74+
}
75+
76+
Span<TInt> firstSpan = new Span<TInt>(first);
77+
ReadOnlySpan<TInt> secondSpan = new ReadOnlySpan<TInt>(second);
78+
bool b = firstSpan.EndsWith(secondSpan);
79+
Assert.True(b);
80+
81+
// Make sure each element of the array was compared once. (Strictly speaking, it would not be illegal for
82+
// EndsWith to compare an element more than once but that would be a non-optimal implementation and
83+
// a red flag. So we'll stick with the stricter test.)
84+
Assert.Equal(first.Length, log.Count);
85+
foreach (TInt elem in first)
86+
{
87+
int numCompares = log.CountCompares(elem.Value, elem.Value);
88+
Assert.True(numCompares == 1, $"Expected {numCompares} == 1 for element {elem.Value}.");
89+
}
90+
}
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)