mirror of
https://github.com/oliverbooth/X10D
synced 2024-11-22 23:58:48 +00:00
Repurpose Span<T>.Split to accept generic
This commit is contained in:
parent
ed8651172b
commit
53e8b2ff64
@ -30,14 +30,16 @@
|
|||||||
- 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 `ReadOnlySpan<T>.Split(T)`
|
||||||
|
- X10D: Added `ReadOnlySpan<T>.Split(ReadOnlySpan<T>)`
|
||||||
- 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 `Span<T>.Split(T)`
|
||||||
|
- X10D: Added `Span<T>.Split(Span<T>)`
|
||||||
- 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
|
||||||
|
91
X10D.Tests/src/Collections/SpanTest.cs
Normal file
91
X10D.Tests/src/Collections/SpanTest.cs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using X10D.Collections;
|
||||||
|
|
||||||
|
namespace X10D.Tests.Collections;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class SpanTest
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnEmptySpan_ShouldYieldNothing()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = ReadOnlySpan<char>.Empty;
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (ReadOnlySpan<char> unused in span.Split(' '))
|
||||||
|
{
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnOneWord_ShouldYieldLength1()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = "Hello ".AsSpan();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (ReadOnlySpan<char> subSpan in span.Split(' '))
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
Assert.AreEqual("Hello", subSpan.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(1, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnTwoWords_ShouldYieldLength2()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = "Hello World ".AsSpan();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (ReadOnlySpan<char> subSpan in span.Split(' '))
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
Assert.AreEqual("Hello", subSpan.ToString());
|
||||||
|
}
|
||||||
|
else if (index == 1)
|
||||||
|
{
|
||||||
|
Assert.AreEqual("World", subSpan.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(2, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void Split_OnThreeWords_ShouldYieldLength3()
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> span = "Hello, the World ".AsSpan();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (ReadOnlySpan<char> subSpan in span.Split(' '))
|
||||||
|
{
|
||||||
|
if (index == 0)
|
||||||
|
{
|
||||||
|
Assert.AreEqual("Hello,", subSpan.ToString());
|
||||||
|
}
|
||||||
|
else if (index == 1)
|
||||||
|
{
|
||||||
|
Assert.AreEqual("the", subSpan.ToString());
|
||||||
|
}
|
||||||
|
else if (index == 2)
|
||||||
|
{
|
||||||
|
Assert.AreEqual("World", subSpan.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.AreEqual(3, index);
|
||||||
|
}
|
||||||
|
}
|
@ -36,53 +36,4 @@ public class CharSpanTests
|
|||||||
Assert.AreEqual(0, string.Empty.AsSpan().CountSubstring('\0'));
|
Assert.AreEqual(0, string.Empty.AsSpan().CountSubstring('\0'));
|
||||||
Assert.AreEqual(0, string.Empty.AsSpan().CountSubstring(string.Empty.AsSpan(), StringComparison.OrdinalIgnoreCase));
|
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
200
X10D/src/Collections/SpanExtensions.cs
Normal file
200
X10D/src/Collections/SpanExtensions.cs
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
namespace X10D.Collections;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Extension methods for <see cref="Span{T}" /> and <see cref="ReadOnlySpan{T}" />
|
||||||
|
/// </summary>
|
||||||
|
public static class SpanExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the number of times that a specified element appears in a span of elements of the same type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The source to search.</param>
|
||||||
|
/// <param name="element">The element to count.</param>
|
||||||
|
/// <typeparam name="T">The type of elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>The number of times that <paramref name="element" /> appears in <paramref name="source" />.</returns>
|
||||||
|
public static int Count<T>(this in Span<T> source, T element)
|
||||||
|
where T : IEquatable<T>
|
||||||
|
{
|
||||||
|
return source.AsReadOnly().Count(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the number of times that a specified element appears in a read-only span of elements of the same type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The source to search.</param>
|
||||||
|
/// <param name="element">The element to count.</param>
|
||||||
|
/// <typeparam name="T">The type of elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>The number of times that <paramref name="element" /> appears in <paramref name="source" />.</returns>
|
||||||
|
public static int Count<T>(this in ReadOnlySpan<T> source, T element)
|
||||||
|
where T : IEquatable<T>
|
||||||
|
{
|
||||||
|
var count = 0;
|
||||||
|
|
||||||
|
foreach (T item in source)
|
||||||
|
{
|
||||||
|
if (item.Equals(element))
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a read-only <see cref="ReadOnlySpan{T}" /> wrapper for the current span.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The source span.</param>
|
||||||
|
/// <typeparam name="T">The type of elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>A <see cref="ReadOnlySpan{T}" /> which wraps the elements in <paramref name="source" />.</returns>
|
||||||
|
public static ReadOnlySpan<T> AsReadOnly<T>(this in Span<T> source)
|
||||||
|
{
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a span of elements into sub-spans based on a delimiting element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The span to split.</param>
|
||||||
|
/// <param name="delimiter">The delimiting element.</param>
|
||||||
|
/// <typeparam name="T">The type of elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator which wraps <paramref name="source"/> and delimits the elements based on <paramref name="delimiter" />.
|
||||||
|
/// </returns>
|
||||||
|
public static SpanSplitEnumerator<T> Split<T>(this in Span<T> source, T delimiter)
|
||||||
|
where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
return source.AsReadOnly().Split(delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a span of elements into sub-spans based on a delimiting element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The span to split.</param>
|
||||||
|
/// <param name="delimiter">The delimiting element.</param>
|
||||||
|
/// <typeparam name="T">The type of elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator which wraps <paramref name="source"/> and delimits the elements based on <paramref name="delimiter" />.
|
||||||
|
/// </returns>
|
||||||
|
public static SpanSplitEnumerator<T> Split<T>(this in ReadOnlySpan<T> source, T delimiter)
|
||||||
|
where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
return new SpanSplitEnumerator<T>(source, delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a span of elements into sub-spans based on a span of delimiting elements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The span to split.</param>
|
||||||
|
/// <param name="delimiter">The span of delimiting elements.</param>
|
||||||
|
/// <typeparam name="T">The type of elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator which wraps <paramref name="source"/> and delimits the elements based on <paramref name="delimiter" />.
|
||||||
|
/// </returns>
|
||||||
|
public static SpanSplitEnumerator<T> Split<T>(this in Span<T> source, in ReadOnlySpan<T> delimiter)
|
||||||
|
where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
return source.AsReadOnly().Split(delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Splits a span of elements into sub-spans based on a span of delimiting elements.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The span to split.</param>
|
||||||
|
/// <param name="delimiter">The span of delimiting elements.</param>
|
||||||
|
/// <typeparam name="T">The type of elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>
|
||||||
|
/// An enumerator which wraps <paramref name="source"/> and delimits the elements based on <paramref name="delimiter" />.
|
||||||
|
/// </returns>
|
||||||
|
public static SpanSplitEnumerator<T> Split<T>(this in ReadOnlySpan<T> source, in ReadOnlySpan<T> delimiter)
|
||||||
|
where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
return new SpanSplitEnumerator<T>(source, delimiter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enumerates the elements of a <see cref="ReadOnlySpan{T}" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of elements in the span.</typeparam>
|
||||||
|
public ref struct SpanSplitEnumerator<T> where T : struct, IEquatable<T>
|
||||||
|
{
|
||||||
|
private ReadOnlySpan<T> _source;
|
||||||
|
private readonly ReadOnlySpan<T> _delimiterSpan;
|
||||||
|
private readonly T _delimiter;
|
||||||
|
private readonly bool _usingSpanDelimiter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SpanSplitEnumerator{T}" /> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The source span.</param>
|
||||||
|
/// <param name="delimiter">The delimiting span of elements.</param>
|
||||||
|
public SpanSplitEnumerator(in ReadOnlySpan<T> source, ReadOnlySpan<T> delimiter)
|
||||||
|
{
|
||||||
|
_usingSpanDelimiter = true;
|
||||||
|
_source = source;
|
||||||
|
_delimiter = default;
|
||||||
|
_delimiterSpan = delimiter;
|
||||||
|
Current = ReadOnlySpan<T>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="SpanSplitEnumerator{T}" /> struct.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">The source span.</param>
|
||||||
|
/// <param name="delimiter">The delimiting element.</param>
|
||||||
|
public SpanSplitEnumerator(in ReadOnlySpan<T> source, T delimiter)
|
||||||
|
{
|
||||||
|
_usingSpanDelimiter = false;
|
||||||
|
_source = source;
|
||||||
|
_delimiter = delimiter;
|
||||||
|
_delimiterSpan = ReadOnlySpan<T>.Empty;
|
||||||
|
Current = ReadOnlySpan<T>.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the element at the current position of the enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The element in the <see cref="ReadOnlySpan{T}" /> at the current position of the enumerator.</value>
|
||||||
|
public ReadOnlySpan<T> Current { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the current enumerator.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The current instance of <see cref="SpanSplitEnumerator{T}" />.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method exists to provide the ability to enumerate within a <c>foreach</c> loop. It should not be called
|
||||||
|
/// manually.
|
||||||
|
/// </remarks>
|
||||||
|
public readonly SpanSplitEnumerator<T> GetEnumerator()
|
||||||
|
{
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Advances the enumerator to the next element of the <see cref="ReadOnlySpan{T}" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>
|
||||||
|
/// <see langword="true" /> if the enumerator was successfully advanced to the next element; <see langword="false" />
|
||||||
|
/// if the enumerator has passed the end of the span.
|
||||||
|
/// </returns>
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
if (_source.Length == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = _usingSpanDelimiter ? _source.IndexOf(_delimiterSpan) : _source.IndexOf(_delimiter);
|
||||||
|
if (index == -1)
|
||||||
|
{
|
||||||
|
Current = _source;
|
||||||
|
_source = ReadOnlySpan<T>.Empty;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Current = _source[..index];
|
||||||
|
_source = _source[(index + 1)..];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -67,74 +67,4 @@ public static class CharSpanExtensions
|
|||||||
|
|
||||||
return 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user