-
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Segment.cs
168 lines (142 loc) · 7.52 KB
/
Segment.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
namespace SuperLinq.Async;
public static partial class AsyncSuperEnumerable
{
/// <summary>
/// Divides a sequence into multiple sequences by using a segment detector based on the original sequence
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="source">The sequence to segment</param>
/// <param name="newSegmentPredicate">A function, which returns <see langword="true"/> if the given element begins a new segment, and <see langword="false"/> otherwise</param>
/// <returns>A sequence of segment, each of which is a portion of the original sequence</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if either <paramref name="source"/> or <paramref name="newSegmentPredicate"/> are <see langword="null"/>.
/// </exception>
public static IAsyncEnumerable<IReadOnlyList<T>> Segment<T>(this IAsyncEnumerable<T> source, Func<T, bool> newSegmentPredicate)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(newSegmentPredicate);
return Segment(source, (curr, prev, index) => new ValueTask<bool>(newSegmentPredicate(curr)));
}
/// <summary>
/// Divides a sequence into multiple sequences by using a segment detector based on the original sequence
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="source">The sequence to segment</param>
/// <param name="newSegmentPredicate">A function, which returns <see langword="true"/> if the given element begins a new segment, and <see langword="false"/> otherwise</param>
/// <returns>A sequence of segment, each of which is a portion of the original sequence</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if either <paramref name="source"/> or <paramref name="newSegmentPredicate"/> are <see langword="null"/>.
/// </exception>
public static IAsyncEnumerable<IReadOnlyList<T>> Segment<T>(this IAsyncEnumerable<T> source, Func<T, ValueTask<bool>> newSegmentPredicate)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(newSegmentPredicate);
return Segment(source, (curr, prev, index) => newSegmentPredicate(curr));
}
/// <summary>
/// Divides a sequence into multiple sequences by using a segment detector based on the original sequence
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="source">The sequence to segment</param>
/// <param name="newSegmentPredicate">A function, which returns <see langword="true"/> if the given element or index indicate a new segment, and <see langword="false"/> otherwise</param>
/// <returns>A sequence of segment, each of which is a portion of the original sequence</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if either <paramref name="source"/> or <paramref name="newSegmentPredicate"/> are <see langword="null"/>.
/// </exception>
public static IAsyncEnumerable<IReadOnlyList<T>> Segment<T>(
this IAsyncEnumerable<T> source,
Func<T, int, bool> newSegmentPredicate
)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(newSegmentPredicate);
return Segment(source, (curr, prev, index) => new ValueTask<bool>(newSegmentPredicate(curr, index)));
}
/// <summary>
/// Divides a sequence into multiple sequences by using a segment detector based on the original sequence
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="source">The sequence to segment</param>
/// <param name="newSegmentPredicate">A function, which returns <see langword="true"/> if the given element or index indicate a new segment, and <see langword="false"/> otherwise</param>
/// <returns>A sequence of segment, each of which is a portion of the original sequence</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if either <paramref name="source"/> or <paramref name="newSegmentPredicate"/> are <see langword="null"/>.
/// </exception>
public static IAsyncEnumerable<IReadOnlyList<T>> Segment<T>(
this IAsyncEnumerable<T> source,
Func<T, int, ValueTask<bool>> newSegmentPredicate
)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(newSegmentPredicate);
return Segment(source, (curr, prev, index) => newSegmentPredicate(curr, index));
}
/// <summary>
/// Divides a sequence into multiple sequences by using a segment detector based on the original sequence
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="source">The sequence to segment</param>
/// <param name="newSegmentPredicate">A function, which returns <see langword="true"/> if the given current element, previous element or index indicate a new segment, and <see langword="false"/> otherwise</param>
/// <returns>A sequence of segment, each of which is a portion of the original sequence</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if either <paramref name="source"/> or <paramref name="newSegmentPredicate"/> are <see langword="null"/>.
/// </exception>
public static IAsyncEnumerable<IReadOnlyList<T>> Segment<T>(
this IAsyncEnumerable<T> source,
Func<T, T, int, bool> newSegmentPredicate
)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(newSegmentPredicate);
return Segment(source, (curr, prev, index) => new ValueTask<bool>(newSegmentPredicate(curr, prev, index)));
}
/// <summary>
/// Divides a sequence into multiple sequences by using a segment detector based on the original sequence
/// </summary>
/// <typeparam name="T">The type of the elements in the sequence</typeparam>
/// <param name="source">The sequence to segment</param>
/// <param name="newSegmentPredicate">A function, which returns <see langword="true"/> if the given current element, previous element or index indicate a new segment, and <see langword="false"/> otherwise</param>
/// <returns>A sequence of segment, each of which is a portion of the original sequence</returns>
/// <exception cref="ArgumentNullException">
/// Thrown if either <paramref name="source"/> or <paramref name="newSegmentPredicate"/> are <see langword="null"/>.
/// </exception>
public static IAsyncEnumerable<IReadOnlyList<T>> Segment<T>(
this IAsyncEnumerable<T> source,
Func<T, T, int, ValueTask<bool>> newSegmentPredicate
)
{
ArgumentNullException.ThrowIfNull(source);
ArgumentNullException.ThrowIfNull(newSegmentPredicate);
return Core(source, newSegmentPredicate);
static async IAsyncEnumerable<IReadOnlyList<T>> Core(
IAsyncEnumerable<T> source,
Func<T, T, int, ValueTask<bool>> newSegmentPredicate,
[EnumeratorCancellation] CancellationToken cancellationToken = default
)
{
await using var e = source.GetConfiguredAsyncEnumerator(cancellationToken);
if (!await e.MoveNextAsync()) // break early (it's empty)
yield break;
// Ensure that the first item is always part of the first
// segment. This is an intentional behavior. Segmentation always
// begins with the second element in the sequence.
var previous = e.Current;
var segment = new List<T> { previous };
for (var index = 1; await e.MoveNextAsync(); index++)
{
var current = e.Current;
if (await newSegmentPredicate(current, previous, index).ConfigureAwait(false))
{
yield return segment; // yield the completed segment
segment = [current]; // start a new segment
}
else // not a new segment, append and continue
{
segment.Add(current);
}
previous = current;
}
yield return segment;
}
}
}