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

Commit 92df694

Browse files
authored
String-like Span extension methods - Trim / IsWhiteSpace (#26560)
* String-like Span extension methods - Trim / IsWhiteSpace * Vectroized implementation and add tests * Test clean up and improve code coverage * Optimize Trim() and move helpers to SpanHelpers.char.cs * Addressing PR feedback * Revert back to the naiive impl for IsWhiteSpace and Trim * Remove length == 0 checks and vector code from IsWhiteSpace/Trim(char) * Fix test nits * Revert back to old impl of Trim(ReadOnySpan<char> trimChars) * Replace break with goto * Fix goto bug - use while loop * Fix nit in for loop * Change back to using for loop
1 parent b8a15a7 commit 92df694

File tree

7 files changed

+879
-0
lines changed

7 files changed

+879
-0
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public static void CopyTo<T>(this T[] array, System.Span<T> destination) { }
4343
public static int IndexOf<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T> { throw null; }
4444
public static int IndexOf<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
4545
public static int IndexOf<T>(this System.Span<T> span, T value) where T : System.IEquatable<T> { throw null; }
46+
public static bool IsWhiteSpace(this System.ReadOnlySpan<char> span) { throw null; }
4647
public static int LastIndexOfAny<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> values) where T : System.IEquatable<T> { throw null; }
4748
public static int LastIndexOfAny<T>(this System.ReadOnlySpan<T> span, T value0, T value1) where T : System.IEquatable<T> { throw null; }
4849
public static int LastIndexOfAny<T>(this System.ReadOnlySpan<T> span, T value0, T value1, T value2) where T : System.IEquatable<T> { throw null; }
@@ -66,6 +67,15 @@ public static void Reverse<T>(this System.Span<T> span) { }
6667
public static bool SequenceEqual<T>(this System.Span<T> first, System.ReadOnlySpan<T> second) where T : System.IEquatable<T> { throw null; }
6768
public static bool StartsWith<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
6869
public static bool StartsWith<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T> { throw null; }
70+
public static System.ReadOnlySpan<char> Trim(this System.ReadOnlySpan<char> span) { throw null; }
71+
public static System.ReadOnlySpan<char> Trim(this System.ReadOnlySpan<char> span, char trimChar) { throw null; }
72+
public static System.ReadOnlySpan<char> Trim(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> trimChars) { throw null; }
73+
public static System.ReadOnlySpan<char> TrimEnd(this System.ReadOnlySpan<char> span) { throw null; }
74+
public static System.ReadOnlySpan<char> TrimEnd(this System.ReadOnlySpan<char> span, char trimChar) { throw null; }
75+
public static System.ReadOnlySpan<char> TrimEnd(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> trimChars) { throw null; }
76+
public static System.ReadOnlySpan<char> TrimStart(this System.ReadOnlySpan<char> span) { throw null; }
77+
public static System.ReadOnlySpan<char> TrimStart(this System.ReadOnlySpan<char> span, char trimChar) { throw null; }
78+
public static System.ReadOnlySpan<char> TrimStart(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> trimChars) { throw null; }
6979
public static bool TryGetString(this System.ReadOnlyMemory<char> readOnlyMemory, out string text, out int start, out int length) { throw null; }
7080
}
7181
public readonly partial struct Memory<T>

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

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,157 @@ namespace System
1717
/// </summary>
1818
public static partial class MemoryExtensions
1919
{
20+
/// <summary>
21+
/// Removes all leading and trailing white-space characters from the span.
22+
/// </summary>
23+
/// <param name="span">The span</param>
24+
public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span)
25+
{
26+
return span.TrimStart().TrimEnd();
27+
}
28+
29+
/// <summary>
30+
/// Removes all leading white-space characters from the span.
31+
/// </summary>
32+
/// <param name="span">The span</param>
33+
public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span)
34+
{
35+
int start = 0;
36+
for (; start < span.Length; start++)
37+
{
38+
if (!char.IsWhiteSpace(span[start]))
39+
break;
40+
}
41+
return span.Slice(start);
42+
}
43+
44+
/// <summary>
45+
/// Removes all trailing white-space characters from the span.
46+
/// </summary>
47+
/// <param name="span">The span</param>
48+
public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span)
49+
{
50+
int end = span.Length - 1;
51+
for (; end >= 0; end--)
52+
{
53+
if (!char.IsWhiteSpace(span[end]))
54+
break;
55+
}
56+
return span.Slice(0, end + 1);
57+
}
58+
59+
/// <summary>
60+
/// Removes all leading and trailing occurrences of a specified character.
61+
/// </summary>
62+
/// <param name="span">The source span from which the character is removed.</param>
63+
/// <param name="trimChar">The specified character to look for and remove.</param>
64+
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
65+
public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, char trimChar)
66+
{
67+
return span.TrimStart(trimChar).TrimEnd(trimChar);
68+
}
69+
70+
/// <summary>
71+
/// Removes all leading occurrences of a specified character.
72+
/// </summary>
73+
/// <param name="span">The source span from which the character is removed.</param>
74+
/// <param name="trimChar">The specified character to look for and remove.</param>
75+
//[MethodImpl(MethodImplOptions.AggressiveInlining)]
76+
public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, char trimChar)
77+
{
78+
int start = 0;
79+
for (; start < span.Length; start++)
80+
{
81+
if (span[start] != trimChar)
82+
break;
83+
}
84+
return span.Slice(start);
85+
}
86+
87+
/// <summary>
88+
/// Removes all trailing occurrences of a specified character.
89+
/// </summary>
90+
/// <param name="span">The source span from which the character is removed.</param>
91+
/// <param name="trimChar">The specified character to look for and remove.</param>
92+
public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, char trimChar)
93+
{
94+
int end = span.Length - 1;
95+
for (; end >= 0; end--)
96+
{
97+
if (span[end] != trimChar)
98+
break;
99+
}
100+
return span.Slice(0, end + 1);
101+
}
102+
103+
/// <summary>
104+
/// Removes all leading and trailing occurrences of a set of characters specified
105+
/// in a readonly span from the span.
106+
/// </summary>
107+
/// <param name="span">The source span from which the characters are removed.</param>
108+
/// <param name="trimChars">The span which contains the set of characters to remove.</param>
109+
public static ReadOnlySpan<char> Trim(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
110+
{
111+
return span.TrimStart(trimChars).TrimEnd(trimChars);
112+
}
113+
114+
/// <summary>
115+
/// Removes all leading occurrences of a set of characters specified
116+
/// in a readonly span from the span.
117+
/// </summary>
118+
/// <param name="span">The source span from which the characters are removed.</param>
119+
/// <param name="trimChars">The span which contains the set of characters to remove.</param>
120+
public static ReadOnlySpan<char> TrimStart(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
121+
{
122+
int start = 0;
123+
for (; start < span.Length; start++)
124+
{
125+
for (int i = 0; i < trimChars.Length; i++)
126+
{
127+
if (span[start] == trimChars[i])
128+
goto Next;
129+
}
130+
break;
131+
Next: ;
132+
}
133+
return span.Slice(start);
134+
}
135+
136+
/// <summary>
137+
/// Removes all trailing occurrences of a set of characters specified
138+
/// in a readonly span from the span.
139+
/// </summary>
140+
/// <param name="span">The source span from which the characters are removed.</param>
141+
/// <param name="trimChars">The span which contains the set of characters to remove.</param>
142+
public static ReadOnlySpan<char> TrimEnd(this ReadOnlySpan<char> span, ReadOnlySpan<char> trimChars)
143+
{
144+
int end = span.Length - 1;
145+
for (; end >= 0; end--)
146+
{
147+
for (int i = 0; i < trimChars.Length; i++)
148+
{
149+
if (span[end] == trimChars[i])
150+
goto Next;
151+
}
152+
break;
153+
Next: ;
154+
}
155+
return span.Slice(0, end + 1);
156+
}
157+
158+
/// <summary>
159+
/// Indicates whether the specified span contains only white-space characters.
160+
/// </summary>
161+
public static bool IsWhiteSpace(this ReadOnlySpan<char> span)
162+
{
163+
for (int i = 0; i < span.Length; i++)
164+
{
165+
if (!char.IsWhiteSpace(span[i]))
166+
return false;
167+
}
168+
return true;
169+
}
170+
20171
/// <summary>
21172
/// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T).
22173
/// </summary>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
private static readonly char[] s_whiteSpaceCharacters = { '\u0009', '\u000a', '\u000b', '\u000c', '\u000d', '\u0020', '\u0085', '\u00a0', '\u1680' };
12+
13+
[Fact]
14+
public static void ZeroLengthIsWhiteSpace()
15+
{
16+
var span = new ReadOnlySpan<char>(Array.Empty<char>());
17+
bool result = span.IsWhiteSpace();
18+
Assert.Equal(string.IsNullOrWhiteSpace(""), result);
19+
}
20+
21+
[Fact]
22+
public static void IsWhiteSpaceTrueLatin1()
23+
{
24+
Random rand = new Random(42);
25+
for (int length = 0; length < 32; length++)
26+
{
27+
char[] a = new char[length];
28+
for (int i = 0; i < length; i++)
29+
{
30+
a[i] = s_whiteSpaceCharacters[rand.Next(0, s_whiteSpaceCharacters.Length - 1)];
31+
}
32+
var span = new Span<char>(a);
33+
bool result = span.AsReadOnlySpan().IsWhiteSpace();
34+
Assert.Equal(string.IsNullOrWhiteSpace(new string(a)), result);
35+
36+
for (int i = 0; i < s_whiteSpaceCharacters.Length - 1; i++)
37+
{
38+
span.Fill(s_whiteSpaceCharacters[i]);
39+
Assert.Equal(string.IsNullOrWhiteSpace(new string(span.ToArray())), span.AsReadOnlySpan().IsWhiteSpace());
40+
}
41+
}
42+
}
43+
44+
[Fact]
45+
public static void IsWhiteSpaceTrue()
46+
{
47+
Random rand = new Random(42);
48+
for (int length = 0; length < 32; length++)
49+
{
50+
char[] a = new char[length];
51+
for (int i = 0; i < length; i++)
52+
{
53+
a[i] = s_whiteSpaceCharacters[rand.Next(0, s_whiteSpaceCharacters.Length)];
54+
}
55+
var span = new ReadOnlySpan<char>(a);
56+
bool result = span.IsWhiteSpace();
57+
Assert.Equal(string.IsNullOrWhiteSpace(new string(span.ToArray())), result);
58+
}
59+
}
60+
61+
[Fact]
62+
public static void IsWhiteSpaceFalse()
63+
{
64+
Random rand = new Random(42);
65+
for (int length = 1; length < 32; length++)
66+
{
67+
char[] a = new char[length];
68+
for (int i = 0; i < length; i++)
69+
{
70+
a[i] = s_whiteSpaceCharacters[rand.Next(0, s_whiteSpaceCharacters.Length)];
71+
}
72+
var span = new Span<char>(a);
73+
74+
// first character is not a white-space character
75+
a[0] = 'a';
76+
bool result = span.AsReadOnlySpan().IsWhiteSpace();
77+
Assert.Equal(string.IsNullOrWhiteSpace(new string(span.ToArray())), result);
78+
a[0] = ' ';
79+
80+
// last character is not a white-space character
81+
a[length - 1] = 'a';
82+
result = span.AsReadOnlySpan().IsWhiteSpace();
83+
Assert.Equal(string.IsNullOrWhiteSpace(new string(span.ToArray())), result);
84+
a[length - 1] = ' ';
85+
86+
// character in the middle is not a white-space character
87+
a[length/2] = 'a';
88+
result = span.AsReadOnlySpan().IsWhiteSpace();
89+
Assert.Equal(string.IsNullOrWhiteSpace(new string(span.ToArray())), result);
90+
a[length/2] = ' ';
91+
92+
// no character is a white-space character
93+
span.Fill('a');
94+
result = span.AsReadOnlySpan().IsWhiteSpace();
95+
Assert.Equal(string.IsNullOrWhiteSpace(new string(span.ToArray())), result);
96+
}
97+
}
98+
99+
[Fact]
100+
public static void MakeSureNoIsWhiteSpaceChecksGoOutOfRange()
101+
{
102+
for (int length = 3; length < 64; length++)
103+
{
104+
char[] first = new char[length];
105+
first[0] = ' ';
106+
first[length - 1] = ' ';
107+
var span = new ReadOnlySpan<char>(first, 1, length - 2);
108+
bool result = span.IsWhiteSpace();
109+
Assert.Equal(string.IsNullOrWhiteSpace(new string(span.ToArray())), result);
110+
}
111+
}
112+
}
113+
}

0 commit comments

Comments
 (0)