mirror of
https://github.com/oliverbooth/X10D
synced 2024-11-23 00:18:47 +00:00
Add [ReadOnly]Span<char>.Split
This commit also migrates CountSubstring from StringExtensions to CharSpanExtensions
This commit is contained in:
parent
4af5a712f4
commit
ed8651172b
@ -30,12 +30,14 @@
|
|||||||
- X10D: Added `PopCount()` for built-in integer types
|
- X10D: Added `PopCount()` for built-in integer types
|
||||||
- X10D: Added `ReadOnlySpan<char>.CountSubstring(char)`
|
- X10D: Added `ReadOnlySpan<char>.CountSubstring(char)`
|
||||||
- X10D: Added `ReadOnlySpan<char>.CountSubstring(ReadOnlySpan<char>[, StringComparison])`
|
- X10D: Added `ReadOnlySpan<char>.CountSubstring(ReadOnlySpan<char>[, StringComparison])`
|
||||||
|
- X10D: Added `ReadOnlySpan<char>.Split(ReadOnlySpan<char>[, StringComparison])`
|
||||||
- X10D: Added `RoundUpToPowerOf2()` for built-in integer types
|
- X10D: Added `RoundUpToPowerOf2()` for built-in integer types
|
||||||
- X10D: Added `Size.ToPoint()`
|
- X10D: Added `Size.ToPoint()`
|
||||||
- X10D: Added `Size.ToPointF()`
|
- X10D: Added `Size.ToPointF()`
|
||||||
- X10D: Added `Size.ToVector2()`
|
- X10D: Added `Size.ToVector2()`
|
||||||
- X10D: Added `Span<char>.CountSubstring(char)`
|
- X10D: Added `Span<char>.CountSubstring(char)`
|
||||||
- X10D: Added `Span<char>.CountSubstring(Span<char>[, StringComparison])`
|
- X10D: Added `Span<char>.CountSubstring(Span<char>[, StringComparison])`
|
||||||
|
- X10D: Added `Span<char>.Split(char, Span<Range>)`
|
||||||
- X10D: Added `string.CountSubstring(char)`
|
- X10D: Added `string.CountSubstring(char)`
|
||||||
- X10D: Added `string.CountSubstring(string[, StringComparison])`
|
- X10D: Added `string.CountSubstring(string[, StringComparison])`
|
||||||
- X10D: Added `Quaternion.Multiply(Vector3)` - this functions as an equivalent to Unity's `Quaternion * Vector3` operator
|
- X10D: Added `Quaternion.Multiply(Vector3)` - this functions as an equivalent to Unity's `Quaternion * Vector3` operator
|
||||||
|
88
X10D.Tests/src/Text/CharSpanTests.cs
Normal file
88
X10D.Tests/src/Text/CharSpanTests.cs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using X10D.Text;
|
||||||
|
|
||||||
|
namespace X10D.Tests.Text;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class CharSpanTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void CountSubstring_ShouldHonor_StringComparison()
|
||||||
|
{
|
||||||
|
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring('E'));
|
||||||
|
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring("E".AsSpan()));
|
||||||
|
Assert.AreEqual(1, "Hello World".AsSpan().CountSubstring("E".AsSpan(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CountSubstring_ShouldReturn0_GivenNoInstanceChar()
|
||||||
|
{
|
||||||
|
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring('z'));
|
||||||
|
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring("z".AsSpan()));
|
||||||
|
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring("z".AsSpan(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CountSubstring_ShouldReturn1_GivenSingleInstanceChar()
|
||||||
|
{
|
||||||
|
Assert.AreEqual(1, "Hello World".AsSpan().CountSubstring('e'));
|
||||||
|
Assert.AreEqual(1, "Hello World".AsSpan().CountSubstring("e".AsSpan()));
|
||||||
|
Assert.AreEqual(1, "Hello World".AsSpan().CountSubstring("e".AsSpan(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void CountSubstring_ShouldReturn0_GivenEmptyHaystack()
|
||||||
|
{
|
||||||
|
Assert.AreEqual(0, string.Empty.AsSpan().CountSubstring('\0'));
|
||||||
|
Assert.AreEqual(0, string.Empty.AsSpan().CountSubstring(string.Empty.AsSpan(), StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnEmptySpan_ShouldYieldNothing()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = ReadOnlySpan<char>.Empty;
|
||||||
|
Assert.AreEqual(0, span.Split(' ', Span<Range>.Empty));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnOneWord_ShouldYieldLength1()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = "Hello".AsSpan();
|
||||||
|
Span<Range> wordRanges = stackalloc Range[1];
|
||||||
|
|
||||||
|
Assert.AreEqual(1, span.Split(' ', wordRanges));
|
||||||
|
Assert.AreEqual(..5, wordRanges[0]);
|
||||||
|
|
||||||
|
Assert.AreEqual("Hello", span[wordRanges[0]].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnTwoWords_ShouldYieldLength2()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = "Hello World".AsSpan();
|
||||||
|
Span<Range> wordRanges = stackalloc Range[2];
|
||||||
|
|
||||||
|
Assert.AreEqual(2, span.Split(' ', wordRanges));
|
||||||
|
Assert.AreEqual(..5, wordRanges[0]);
|
||||||
|
Assert.AreEqual(6..11, wordRanges[1]);
|
||||||
|
|
||||||
|
Assert.AreEqual("Hello", span[wordRanges[0]].ToString());
|
||||||
|
Assert.AreEqual("World", span[wordRanges[1]].ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnThreeWords_ShouldYieldLength2()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = "Hello, the World".AsSpan();
|
||||||
|
Span<Range> wordRanges = stackalloc Range[3];
|
||||||
|
|
||||||
|
Assert.AreEqual(3, span.Split(' ', wordRanges));
|
||||||
|
Assert.AreEqual(..6, wordRanges[0]);
|
||||||
|
Assert.AreEqual(7..10, wordRanges[1]);
|
||||||
|
Assert.AreEqual(11..16, wordRanges[2]);
|
||||||
|
|
||||||
|
Assert.AreEqual("Hello,", span[wordRanges[0]].ToString());
|
||||||
|
Assert.AreEqual("the", span[wordRanges[1]].ToString());
|
||||||
|
Assert.AreEqual("World", span[wordRanges[2]].ToString());
|
||||||
|
}
|
||||||
|
}
|
@ -107,8 +107,6 @@ public class StringTests
|
|||||||
Assert.AreEqual(0, "Hello World".CountSubstring('E'));
|
Assert.AreEqual(0, "Hello World".CountSubstring('E'));
|
||||||
Assert.AreEqual(0, "Hello World".CountSubstring("E"));
|
Assert.AreEqual(0, "Hello World".CountSubstring("E"));
|
||||||
Assert.AreEqual(1, "Hello World".CountSubstring("E", StringComparison.OrdinalIgnoreCase));
|
Assert.AreEqual(1, "Hello World".CountSubstring("E", StringComparison.OrdinalIgnoreCase));
|
||||||
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring('E'));
|
|
||||||
Assert.AreEqual(1, "Hello World".AsSpan().CountSubstring("E".AsSpan(), StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@ -117,8 +115,6 @@ public class StringTests
|
|||||||
Assert.AreEqual(0, "Hello World".CountSubstring('z'));
|
Assert.AreEqual(0, "Hello World".CountSubstring('z'));
|
||||||
Assert.AreEqual(0, "Hello World".CountSubstring("z"));
|
Assert.AreEqual(0, "Hello World".CountSubstring("z"));
|
||||||
Assert.AreEqual(0, "Hello World".CountSubstring("z", StringComparison.OrdinalIgnoreCase));
|
Assert.AreEqual(0, "Hello World".CountSubstring("z", StringComparison.OrdinalIgnoreCase));
|
||||||
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring('z'));
|
|
||||||
Assert.AreEqual(0, "Hello World".AsSpan().CountSubstring("z".AsSpan(), StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@ -127,8 +123,6 @@ public class StringTests
|
|||||||
Assert.AreEqual(1, "Hello World".CountSubstring('e'));
|
Assert.AreEqual(1, "Hello World".CountSubstring('e'));
|
||||||
Assert.AreEqual(1, "Hello World".CountSubstring("e"));
|
Assert.AreEqual(1, "Hello World".CountSubstring("e"));
|
||||||
Assert.AreEqual(1, "Hello World".CountSubstring("e", StringComparison.OrdinalIgnoreCase));
|
Assert.AreEqual(1, "Hello World".CountSubstring("e", StringComparison.OrdinalIgnoreCase));
|
||||||
Assert.AreEqual(1, "Hello World".AsSpan().CountSubstring('e'));
|
|
||||||
Assert.AreEqual(1, "Hello World".AsSpan().CountSubstring("e".AsSpan(), StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
@ -137,8 +131,6 @@ public class StringTests
|
|||||||
Assert.AreEqual(0, string.Empty.CountSubstring('\0'));
|
Assert.AreEqual(0, string.Empty.CountSubstring('\0'));
|
||||||
Assert.AreEqual(0, string.Empty.CountSubstring(string.Empty));
|
Assert.AreEqual(0, string.Empty.CountSubstring(string.Empty));
|
||||||
Assert.AreEqual(0, string.Empty.CountSubstring(string.Empty, StringComparison.OrdinalIgnoreCase));
|
Assert.AreEqual(0, string.Empty.CountSubstring(string.Empty, StringComparison.OrdinalIgnoreCase));
|
||||||
Assert.AreEqual(0, string.Empty.AsSpan().CountSubstring('\0'));
|
|
||||||
Assert.AreEqual(0, string.Empty.AsSpan().CountSubstring(string.Empty.AsSpan(), StringComparison.OrdinalIgnoreCase));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
|
140
X10D/src/Text/CharSpanExtensions.cs
Normal file
140
X10D/src/Text/CharSpanExtensions.cs
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
namespace X10D.Text;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="ReadOnlySpan{T}" /> and <see cref="Span{T}" /> of <see cref="char" />.
|
||||||
|
/// </summary>
|
||||||
|
public static class CharSpanExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Counts the occurrences of a substring within the current character span.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="haystack">The haystack search space.</param>
|
||||||
|
/// <param name="needle">The character span to count.</param>
|
||||||
|
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
||||||
|
public static int CountSubstring(this Span<char> haystack, Span<char> needle)
|
||||||
|
{
|
||||||
|
return CountSubstring(haystack, needle, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts the occurrences of a substring within the current character span, using a specified string comparison method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="haystack">The haystack search space.</param>
|
||||||
|
/// <param name="needle">The character span to count.</param>
|
||||||
|
/// <param name="comparison">The string comparison method used for determining substring count.</param>
|
||||||
|
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
||||||
|
public static int CountSubstring(this Span<char> haystack, Span<char> needle, StringComparison comparison)
|
||||||
|
{
|
||||||
|
return CountSubstring((ReadOnlySpan<char>)haystack, needle, comparison);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts the occurrences of a substring within the current character span.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="haystack">The haystack search space.</param>
|
||||||
|
/// <param name="needle">The character span to count.</param>
|
||||||
|
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
||||||
|
public static int CountSubstring(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle)
|
||||||
|
{
|
||||||
|
return CountSubstring(haystack, needle, StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Counts the occurrences of a substring within the current character span, using a specified string comparison method.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="haystack">The haystack search space.</param>
|
||||||
|
/// <param name="needle">The character span to count.</param>
|
||||||
|
/// <param name="comparison">The string comparison method used for determining substring count.</param>
|
||||||
|
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
||||||
|
public static int CountSubstring(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, StringComparison comparison)
|
||||||
|
{
|
||||||
|
if (haystack.IsEmpty || needle.IsEmpty)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int haystackLength = haystack.Length;
|
||||||
|
int needleLength = needle.Length;
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
for (var index = 0; index < haystackLength - needleLength - 1; index++)
|
||||||
|
{
|
||||||
|
if (haystack[index..(index + needleLength)].Equals(needle, comparison))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a span of characters into substrings based on a specific delimiting character.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The span of characters to split.</param>
|
||||||
|
/// <param name="separator">A character that delimits the substring in this character span.</param>
|
||||||
|
/// <param name="destination">
|
||||||
|
/// When this method returns, will be populated with the <see cref="Range" /> values pointing to where each substring
|
||||||
|
/// starts and ends in <paramref name="value" />.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of substrings within <paramref name="value" />. This value is always correct regardless of the length of
|
||||||
|
/// <paramref name="destination" />.
|
||||||
|
/// </returns>
|
||||||
|
public static int Split(this Span<char> value, char separator, Span<Range> destination)
|
||||||
|
{
|
||||||
|
return ((ReadOnlySpan<char>)value).Split(separator, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a span of characters into substrings based on a specific delimiting character.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The span of characters to split.</param>
|
||||||
|
/// <param name="separator">A character that delimits the substring in this character span.</param>
|
||||||
|
/// <param name="destination">
|
||||||
|
/// When this method returns, will be populated with the <see cref="Range" /> values pointing to where each substring
|
||||||
|
/// starts and ends in <paramref name="value" />.
|
||||||
|
/// </param>
|
||||||
|
/// <returns>
|
||||||
|
/// The number of substrings within <paramref name="value" />. This value is always correct regardless of the length of
|
||||||
|
/// <paramref name="destination" />.
|
||||||
|
/// </returns>
|
||||||
|
public static int Split(this ReadOnlySpan<char> value, char separator, Span<Range> destination)
|
||||||
|
{
|
||||||
|
Span<char> buffer = stackalloc char[value.Length];
|
||||||
|
var matches = 0;
|
||||||
|
|
||||||
|
for (int index = 0, bufferLength = 0, startIndex = 0; index < value.Length; index++)
|
||||||
|
{
|
||||||
|
bool end = index == value.Length - 1;
|
||||||
|
if (end)
|
||||||
|
{
|
||||||
|
bufferLength++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value[index] == separator || end)
|
||||||
|
{
|
||||||
|
if (destination.Length > matches)
|
||||||
|
{
|
||||||
|
// I was going to use new Range(startIndex, startIndex + bufferLength)
|
||||||
|
// but the .. operator is just so fucking cool so +1 for brevity over
|
||||||
|
// clarity!
|
||||||
|
// ... Ok I know this is probably a bad idea but come on, isn't it neat
|
||||||
|
// that you can use any integer expression as either operand to the .. operator?
|
||||||
|
// SOMEBODY AGREE WITH ME!
|
||||||
|
destination[matches] = startIndex..(startIndex + bufferLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex = index + 1;
|
||||||
|
bufferLength = 0;
|
||||||
|
matches++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buffer[bufferLength++] = value[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
}
|
@ -214,69 +214,6 @@ public static class StringExtensions
|
|||||||
return haystack.AsSpan().CountSubstring(needle);
|
return haystack.AsSpan().CountSubstring(needle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Counts the occurrences of a substring within the current character span.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="haystack">The haystack search space.</param>
|
|
||||||
/// <param name="needle">The character span to count.</param>
|
|
||||||
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
|
||||||
public static int CountSubstring(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle)
|
|
||||||
{
|
|
||||||
return CountSubstring(haystack, needle, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Counts the occurrences of a substring within the current character span, using a specified string comparison method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="haystack">The haystack search space.</param>
|
|
||||||
/// <param name="needle">The character span to count.</param>
|
|
||||||
/// <param name="comparison">The string comparison method used for determining substring count.</param>
|
|
||||||
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
|
||||||
public static int CountSubstring(this ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle, StringComparison comparison)
|
|
||||||
{
|
|
||||||
if (haystack.IsEmpty || needle.IsEmpty)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int haystackLength = haystack.Length;
|
|
||||||
int needleLength = needle.Length;
|
|
||||||
var count = 0;
|
|
||||||
|
|
||||||
for (var index = 0; index < haystackLength - needleLength - 1; index++)
|
|
||||||
{
|
|
||||||
if (haystack[index..(index + needleLength)].Equals(needle, comparison))
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Counts the occurrences of a substring within the current character span.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="haystack">The haystack search space.</param>
|
|
||||||
/// <param name="needle">The character span to count.</param>
|
|
||||||
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
|
||||||
public static int CountSubstring(this Span<char> haystack, Span<char> needle)
|
|
||||||
{
|
|
||||||
return CountSubstring(haystack, needle, StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Counts the occurrences of a substring within the current character span, using a specified string comparison method.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="haystack">The haystack search space.</param>
|
|
||||||
/// <param name="needle">The character span to count.</param>
|
|
||||||
/// <param name="comparison">The string comparison method used for determining substring count.</param>
|
|
||||||
/// <returns>An integer representing the count of <paramref name="needle" /> inside <paramref name="haystack" />.</returns>
|
|
||||||
public static int CountSubstring(this Span<char> haystack, Span<char> needle, StringComparison comparison)
|
|
||||||
{
|
|
||||||
return CountSubstring((ReadOnlySpan<char>)haystack, needle, comparison);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Counts the occurrences of a substring within the current string.
|
/// Counts the occurrences of a substring within the current string.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
Loading…
Reference in New Issue
Block a user