Skip to content

Commit d22f546

Browse files
committed
fixed and optimized split methods
1 parent b6e584a commit d22f546

13 files changed

+419
-451
lines changed

src/CountExceedingBehaviour.cs

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,45 @@
1-
namespace SpanExtensions
1+
using System;
2+
3+
namespace SpanExtensions
24
{
35
/// <summary>
4-
/// Defines the behaviour of a split operation when there are more split instances than there may be.
5-
/// </summary>
6+
/// Defines the behaviour of a split operation when there are more split instances than there may be.
7+
/// </summary>
68
public enum CountExceedingBehaviour
79
{
810
/// <summary>
911
/// The last element returned will be all the remaining elements appended as one.
1012
/// </summary>
1113
AppendLastElements,
1214
/// <summary>
13-
/// Every split instance more than permitted will not be returned.
14-
/// </summary>
15+
/// Every split instance more than permitted will not be returned.
16+
/// </summary>
1517
CutLastElements
1618
}
19+
20+
/// <summary>
21+
/// Extension methods for <see cref="CountExceedingBehaviour"/>.
22+
/// </summary>
23+
static class CountExceedingBehaviourExtensions
24+
{
25+
/// <summary>
26+
/// Validates whether a specified value is a valid <see cref="CountExceedingBehaviour"/>.
27+
/// </summary>
28+
/// <param name="countExceedingBehaviour">The <see cref="CountExceedingBehaviour"/> to validate.</param>
29+
/// <returns>The specified <see cref="CountExceedingBehaviour"/> if valid.</returns>
30+
/// <exception cref="InvalidCountExceedingBehaviourException">If <paramref name="countExceedingBehaviour"/> is not valid.</exception>
31+
public static CountExceedingBehaviour ThrowIfInvalid(this CountExceedingBehaviour countExceedingBehaviour)
32+
{
33+
#if NET5_0_OR_GREATER
34+
if(!Enum.IsDefined(countExceedingBehaviour))
35+
#else
36+
if(!Enum.IsDefined(typeof(CountExceedingBehaviour), countExceedingBehaviour))
37+
#endif
38+
{
39+
throw new InvalidCountExceedingBehaviourException(countExceedingBehaviour);
40+
}
41+
42+
return countExceedingBehaviour;
43+
}
44+
}
1745
}

src/Enumerators/Split/SpanSplitEnumerator.cs

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ namespace SpanExtensions.Enumerators
1010
{
1111
ReadOnlySpan<T> Span;
1212
readonly T Delimiter;
13-
bool enumerationDone;
13+
const int DelimiterLength = 1;
14+
bool EnumerationDone;
1415

1516
/// <summary>
1617
/// Gets the element in the collection at the current position of the enumerator.
@@ -26,8 +27,8 @@ public SpanSplitEnumerator(ReadOnlySpan<T> source, T delimiter)
2627
{
2728
Span = source;
2829
Delimiter = delimiter;
30+
EnumerationDone = false;
2931
Current = default;
30-
enumerationDone = false;
3132
}
3233

3334
/// <summary>
@@ -44,22 +45,25 @@ public readonly SpanSplitEnumerator<T> GetEnumerator()
4445
/// <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 collection.</returns>
4546
public bool MoveNext()
4647
{
47-
if(enumerationDone)
48+
if(EnumerationDone)
4849
{
4950
return false;
5051
}
5152

52-
ReadOnlySpan<T> span = Span;
53-
int index = span.IndexOf(Delimiter);
53+
int delimiterIndex = Span.IndexOf(Delimiter);
5454

55-
if(index == -1 || index >= span.Length)
55+
if(delimiterIndex == -1)
5656
{
57-
enumerationDone = true;
58-
Current = span;
57+
EnumerationDone = true;
58+
59+
Current = Span;
60+
5961
return true;
6062
}
61-
Current = span[..index];
62-
Span = span[(index + 1)..];
63+
64+
Current = Span[..delimiterIndex];
65+
Span = Span[(delimiterIndex + DelimiterLength)..];
66+
6367
return true;
6468
}
6569
}

src/Enumerators/Split/SpanSplitStringSplitOptionsEnumerator.cs

Lines changed: 35 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ public ref struct SpanSplitStringSplitOptionsEnumerator
99
{
1010
ReadOnlySpan<char> Span;
1111
readonly char Delimiter;
12-
readonly StringSplitOptions Options;
13-
bool enumerationDone;
12+
const int DelimiterLength = 1;
13+
readonly bool TrimEntries;
14+
readonly bool RemoveEmptyEntries;
15+
bool EnumerationDone;
1416

1517
/// <summary>
1618
/// Gets the element in the collection at the current position of the enumerator.
@@ -27,9 +29,14 @@ public SpanSplitStringSplitOptionsEnumerator(ReadOnlySpan<char> source, char del
2729
{
2830
Span = source;
2931
Delimiter = delimiter;
30-
Options = options;
32+
#if NET5_0_OR_GREATER
33+
TrimEntries = options.HasFlag(StringSplitOptions.TrimEntries);
34+
#else
35+
TrimEntries = false;
36+
#endif
37+
RemoveEmptyEntries = options.HasFlag(StringSplitOptions.RemoveEmptyEntries);
38+
EnumerationDone = false;
3139
Current = default;
32-
enumerationDone = false;
3340
}
3441

3542
/// <summary>
@@ -46,49 +53,44 @@ public readonly SpanSplitStringSplitOptionsEnumerator GetEnumerator()
4653
/// <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 collection.</returns>
4754
public bool MoveNext()
4855
{
49-
if(enumerationDone)
56+
if(EnumerationDone)
5057
{
5158
return false;
5259
}
5360

54-
ReadOnlySpan<char> span = Span;
55-
int index = span.IndexOf(Delimiter);
56-
57-
if(index == -1 || index >= span.Length)
58-
{
59-
enumerationDone = true;
60-
Current = span;
61-
return true;
62-
}
63-
Current = span[..index];
64-
#if NET5_0_OR_GREATER
65-
if(Options.HasFlag(StringSplitOptions.TrimEntries))
66-
{
67-
Current = Current.Trim();
68-
}
69-
#endif
70-
if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries))
61+
while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached
7162
{
72-
if(Current.IsEmpty)
63+
int delimiterIndex = Span.IndexOf(Delimiter);
64+
65+
if(delimiterIndex == -1)
7366
{
74-
Span = span[(index + 1)..];
75-
if(Span.IsEmpty)
67+
EnumerationDone = true;
68+
69+
Current = Span;
70+
71+
if(TrimEntries)
7672
{
77-
enumerationDone = true;
78-
return false;
73+
Current = Current.Trim();
7974
}
80-
return MoveNext();
75+
76+
return !(RemoveEmptyEntries && Current.IsEmpty);
8177
}
8278

83-
Span = span[(index + 1)..];
84-
if(Span.IsEmpty)
79+
Current = Span[..delimiterIndex];
80+
Span = Span[(delimiterIndex + DelimiterLength)..];
81+
82+
if(TrimEntries)
8583
{
86-
enumerationDone = true;
84+
Current = Current.Trim();
8785
}
86+
87+
if(RemoveEmptyEntries && Current.IsEmpty)
88+
{
89+
continue;
90+
}
91+
8892
return true;
8993
}
90-
Span = span[(index + 1)..];
91-
return true;
9294
}
9395
}
9496
}

src/Enumerators/Split/SpanSplitStringSplitOptionsWithCountEnumerator.cs

Lines changed: 59 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ public ref struct SpanSplitStringSplitOptionsWithCountEnumerator
99
{
1010
ReadOnlySpan<char> Span;
1111
readonly char Delimiter;
12-
readonly StringSplitOptions Options;
13-
readonly int Count;
12+
const int DelimiterLength = 1;
13+
readonly bool TrimEntries;
14+
readonly bool RemoveEmptyEntries;
1415
readonly CountExceedingBehaviour CountExceedingBehaviour;
15-
int currentCount;
16-
bool enumerationDone;
17-
readonly int CountMinusOne;
16+
int CurrentCount;
17+
bool EnumerationDone;
1818

1919
/// <summary>
2020
/// Gets the element in the collection at the current position of the enumerator.
@@ -33,13 +33,16 @@ public SpanSplitStringSplitOptionsWithCountEnumerator(ReadOnlySpan<char> source,
3333
{
3434
Span = source;
3535
Delimiter = delimiter;
36-
Count = count;
37-
CountExceedingBehaviour = countExceedingBehaviour;
38-
Options = options;
36+
CurrentCount = Math.Max(1, count);
37+
#if NET5_0_OR_GREATER
38+
TrimEntries = options.HasFlag(StringSplitOptions.TrimEntries);
39+
#else
40+
TrimEntries = false;
41+
#endif
42+
RemoveEmptyEntries = options.HasFlag(StringSplitOptions.RemoveEmptyEntries);
43+
CountExceedingBehaviour = countExceedingBehaviour.ThrowIfInvalid();
44+
EnumerationDone = false;
3945
Current = default;
40-
currentCount = 0;
41-
enumerationDone = false;
42-
CountMinusOne = Math.Max(Count - 1, 0);
4346
}
4447

4548
/// <summary>
@@ -56,75 +59,68 @@ public readonly SpanSplitStringSplitOptionsWithCountEnumerator GetEnumerator()
5659
/// <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 collection.</returns>
5760
public bool MoveNext()
5861
{
59-
if(enumerationDone)
62+
if(EnumerationDone)
6063
{
6164
return false;
6265
}
6366

64-
ReadOnlySpan<char> span = Span;
65-
if(currentCount == Count)
67+
while(true) // if RemoveEmptyEntries options flag is set, repeat until a non-empty span is found, or the end is reached
6668
{
67-
return false;
68-
}
69-
int index = span.IndexOf(Delimiter);
69+
int delimiterIndex = Span.IndexOf(Delimiter);
7070

71-
switch(CountExceedingBehaviour)
72-
{
73-
case CountExceedingBehaviour.CutLastElements:
74-
break;
75-
case CountExceedingBehaviour.AppendLastElements:
76-
if(currentCount == CountMinusOne)
71+
if(delimiterIndex == -1 || CurrentCount == 1)
72+
{
73+
EnumerationDone = true;
74+
75+
if(delimiterIndex != -1 && RemoveEmptyEntries && CountExceedingBehaviour == CountExceedingBehaviour.AppendLastElements) // skip all empty (after trimming if necessary) entries from the left
7776
{
78-
ReadOnlySpan<char> lower = span[..index];
79-
ReadOnlySpan<char> upper = span[(index + 1)..];
80-
Span<char> temp = new char[lower.Length + upper.Length];
81-
lower.CopyTo(temp[..index]);
82-
upper.CopyTo(temp[index..]);
83-
Current = temp;
84-
currentCount++;
85-
return true;
77+
do
78+
{
79+
ReadOnlySpan<char> beforeDelimiter = Span[..delimiterIndex];
80+
81+
if(TrimEntries ? beforeDelimiter.IsWhiteSpace() : beforeDelimiter.IsEmpty)
82+
{
83+
Span = Span[(delimiterIndex + DelimiterLength)..];
84+
delimiterIndex = Span.IndexOf(Delimiter);
85+
86+
continue;
87+
}
88+
89+
break;
90+
}
91+
while(delimiterIndex != -1);
92+
93+
Current = Span;
94+
}
95+
else
96+
{
97+
Current = delimiterIndex == -1 || CountExceedingBehaviour == CountExceedingBehaviour.AppendLastElements ? Span : Span[..delimiterIndex];
8698
}
87-
break;
88-
default:
89-
throw new InvalidCountExceedingBehaviourException(CountExceedingBehaviour);
90-
}
91-
if(index == -1 || index >= span.Length)
92-
{
93-
enumerationDone = true;
94-
Current = span;
95-
return true;
96-
}
97-
currentCount++;
98-
Current = span[..index];
9999

100-
#if NET5_0_OR_GREATER
101-
if(Options.HasFlag(StringSplitOptions.TrimEntries))
102-
{
103-
Current = Current.Trim();
104-
}
105-
#endif
106-
if(Options.HasFlag(StringSplitOptions.RemoveEmptyEntries))
107-
{
108-
if(Current.IsEmpty)
109-
{
110-
Span = span[(index + 1)..];
111-
if(Span.IsEmpty)
100+
if(TrimEntries)
112101
{
113-
enumerationDone = true;
114-
return false;
102+
Current = Current.Trim();
115103
}
116-
return MoveNext();
104+
105+
return !(RemoveEmptyEntries && Current.IsEmpty);
117106
}
118107

119-
Span = span[(index + 1)..];
120-
if(Span.IsEmpty)
108+
Current = Span[..delimiterIndex];
109+
Span = Span[(delimiterIndex + DelimiterLength)..];
110+
111+
if(TrimEntries)
121112
{
122-
enumerationDone = true;
113+
Current = Current.Trim();
123114
}
115+
116+
if(RemoveEmptyEntries && Current.IsEmpty)
117+
{
118+
continue;
119+
}
120+
121+
CurrentCount--;
124122
return true;
125123
}
126-
Span = span[(index + 1)..];
127-
return true;
128124
}
129125
}
130126
}

0 commit comments

Comments
 (0)