1
1
using System ;
2
2
using System . Collections . Generic ;
3
+ using System . Diagnostics . CodeAnalysis ;
3
4
using JetBrains . Annotations ;
4
5
using Linq . Extras . Internal ;
5
6
using Linq . Extras . Properties ;
@@ -15,7 +16,14 @@ partial class XEnumerable
15
16
/// <param name="source">The sequence to return the maximum element from.</param>
16
17
/// <param name="comparer">The comparer used to compare elements.</param>
17
18
/// <returns>The maximum element according to the specified comparer.</returns>
19
+ /// <remarks>
20
+ /// If <c>TSource</c> is a reference type or nullable value type, null values are ignored, unless the sequence consists
21
+ /// entirely of null values (in which case the method will return null).
22
+ /// If <c>TSource</c> is a reference type or nullable value type, and the sequence is empty, the method will return <c>null</c>.
23
+ /// If <c>TSource</c> is a value type, and the sequence is empty, the method will throw an <see cref="InvalidOperationException"/>.
24
+ /// </remarks>
18
25
[ Pure ]
26
+ [ return : MaybeNull ]
19
27
public static TSource Max < TSource > (
20
28
[ NotNull ] this IEnumerable < TSource > source ,
21
29
[ NotNull ] IComparer < TSource > comparer )
@@ -32,7 +40,14 @@ public static TSource Max<TSource>(
32
40
/// <param name="source">The sequence to return the minimum element from.</param>
33
41
/// <param name="comparer">The comparer used to compare elements.</param>
34
42
/// <returns>The minimum element according to the specified comparer.</returns>
43
+ /// <remarks>
44
+ /// If <c>TSource</c> is a reference type or nullable value type, null values are ignored, unless the sequence consists
45
+ /// entirely of null values (in which case the method will return null).
46
+ /// If <c>TSource</c> is a reference type or nullable value type, and the sequence is empty, the method will return <c>null</c>.
47
+ /// If <c>TSource</c> is a value type, and the sequence is empty, the method will throw an <see cref="InvalidOperationException"/>.
48
+ /// </remarks>
35
49
[ Pure ]
50
+ [ return : MaybeNull ]
36
51
public static TSource Min < TSource > (
37
52
[ NotNull ] this IEnumerable < TSource > source ,
38
53
[ NotNull ] IComparer < TSource > comparer )
@@ -47,22 +62,56 @@ private static TSource Extreme<TSource>(this IEnumerable<TSource> source, ICompa
47
62
{
48
63
comparer = comparer ?? Comparer < TSource > . Default ;
49
64
TSource extreme = default ! ;
50
- bool first = true ;
51
- foreach ( var item in source )
65
+
66
+ using var e = source . GetEnumerator ( ) ;
67
+ if ( extreme is null )
52
68
{
53
- int compare = 0 ;
54
- if ( ! first )
55
- compare = comparer . Compare ( item , extreme ) ;
69
+ // For nullable types, return null if the sequence is empty
70
+ // or contains only null values.
56
71
57
- if ( Math . Sign ( compare ) == sign || first )
72
+ // First, skip until the first non-null value, if any
73
+ do
58
74
{
59
- extreme = item ;
75
+ if ( ! e . MoveNext ( ) )
76
+ {
77
+ return extreme ;
78
+ }
79
+
80
+ extreme = e . Current ;
81
+ } while ( extreme is null ) ;
82
+
83
+ while ( e . MoveNext ( ) )
84
+ {
85
+ if ( e . Current is null )
86
+ {
87
+ continue ;
88
+ }
89
+
90
+ if ( Math . Sign ( comparer . Compare ( e . Current , extreme ) ) == sign )
91
+ {
92
+ extreme = e . Current ;
93
+ }
60
94
}
61
- first = false ;
62
95
}
96
+ else
97
+ {
98
+ // For non-nullable types, throw an exception if the sequence is empty
99
+
100
+ if ( ! e . MoveNext ( ) )
101
+ {
102
+ throw EmptySequenceException ( ) ;
103
+ }
104
+
105
+ extreme = e . Current ;
63
106
64
- if ( first )
65
- throw EmptySequenceException ( ) ;
107
+ while ( e . MoveNext ( ) )
108
+ {
109
+ if ( Math . Sign ( comparer . Compare ( e . Current , extreme ) ) == sign )
110
+ {
111
+ extreme = e . Current ;
112
+ }
113
+ }
114
+ }
66
115
67
116
return extreme ;
68
117
}
@@ -81,16 +130,22 @@ private static InvalidOperationException EmptySequenceException()
81
130
/// <param name="keySelector">A delegate that returns the key used to compare elements.</param>
82
131
/// <param name="keyComparer">A comparer to compare the keys.</param>
83
132
/// <returns>The element of <c>source</c> that has the maximum value for the specified key.</returns>
133
+ /// <remarks>
134
+ /// If <c>TKey</c> is a reference type or nullable value type, null keys are ignored, unless the sequence consists
135
+ /// entirely of items with null keys (in which case the method will return null).
136
+ /// If <c>TKey</c> is a reference type or nullable value type, and the sequence is empty, the method will return <c>null</c>.
137
+ /// If <c>TKey</c> is a value type, and the sequence is empty, the method will throw an <see cref="InvalidOperationException"/>.
138
+ /// </remarks>
84
139
[ Pure ]
140
+ [ return : MaybeNull ]
85
141
public static TSource MaxBy < TSource , TKey > (
86
142
[ NotNull ] this IEnumerable < TSource > source ,
87
143
[ NotNull ] Func < TSource , TKey > keySelector ,
88
144
IComparer < TKey > ? keyComparer = null )
89
145
{
90
146
source . CheckArgumentNull ( nameof ( source ) ) ;
91
147
keySelector . CheckArgumentNull ( nameof ( keySelector ) ) ;
92
- var comparer = XComparer . By ( keySelector , keyComparer ) ;
93
- return source . Max ( comparer ) ;
148
+ return source . ExtremeBy ( keySelector , keyComparer , 1 ) ;
94
149
}
95
150
96
151
/// <summary>
@@ -102,16 +157,93 @@ public static TSource MaxBy<TSource, TKey>(
102
157
/// <param name="keySelector">A delegate that returns the key used to compare elements.</param>
103
158
/// <param name="keyComparer">A comparer to compare the keys.</param>
104
159
/// <returns>The element of <c>source</c> that has the minimum value for the specified key.</returns>
160
+ /// <remarks>
161
+ /// If <c>TKey</c> is a reference type or nullable value type, null keys are ignored, unless the sequence consists
162
+ /// entirely of items with null keys (in which case the method will return null).
163
+ /// If <c>TKey</c> is a reference type or nullable value type, and the sequence is empty, the method will return <c>null</c>.
164
+ /// If <c>TKey</c> is a value type, and the sequence is empty, the method will throw an <see cref="InvalidOperationException"/>.
165
+ /// </remarks>
105
166
[ Pure ]
167
+ [ return : MaybeNull ]
106
168
public static TSource MinBy < TSource , TKey > (
107
169
[ NotNull ] this IEnumerable < TSource > source ,
108
170
[ NotNull ] Func < TSource , TKey > keySelector ,
109
171
IComparer < TKey > ? keyComparer = null )
110
172
{
111
173
source . CheckArgumentNull ( nameof ( source ) ) ;
112
174
keySelector . CheckArgumentNull ( nameof ( keySelector ) ) ;
113
- var comparer = XComparer . By ( keySelector , keyComparer ) ;
114
- return source . Min ( comparer ) ;
175
+ return source . ExtremeBy ( keySelector , keyComparer , - 1 ) ;
176
+ }
177
+
178
+ [ Pure ]
179
+ private static TSource ExtremeBy < TSource , TKey > (
180
+ this IEnumerable < TSource > source ,
181
+ Func < TSource , TKey > keySelector ,
182
+ IComparer < TKey > ? keyComparer ,
183
+ int sign )
184
+ {
185
+ keyComparer = keyComparer ?? Comparer < TKey > . Default ;
186
+ TSource extreme = default ! ;
187
+ TKey extremeKey = default ! ;
188
+
189
+ using var e = source . GetEnumerator ( ) ;
190
+
191
+ if ( extremeKey is null )
192
+ {
193
+ // For nullable types, return null if the sequence is empty
194
+ // or contains only values with null keys.
195
+
196
+ // First, skip until the first non-null key value, if any
197
+ do
198
+ {
199
+ if ( ! e . MoveNext ( ) )
200
+ {
201
+ return extreme ;
202
+ }
203
+
204
+ extreme = e . Current ;
205
+ extremeKey = keySelector ( extreme ) ;
206
+ } while ( extremeKey is null ) ;
207
+
208
+ while ( e . MoveNext ( ) )
209
+ {
210
+ var currentKey = keySelector ( e . Current ) ;
211
+ if ( currentKey is null )
212
+ {
213
+ continue ;
214
+ }
215
+
216
+ if ( Math . Sign ( keyComparer . Compare ( currentKey , extremeKey ) ) == sign )
217
+ {
218
+ extreme = e . Current ;
219
+ extremeKey = currentKey ;
220
+ }
221
+ }
222
+ }
223
+ else
224
+ {
225
+ // For non-nullable types, throw an exception if the sequence is empty
226
+
227
+ if ( ! e . MoveNext ( ) )
228
+ {
229
+ throw EmptySequenceException ( ) ;
230
+ }
231
+
232
+ extreme = e . Current ;
233
+ extremeKey = keySelector ( e . Current ) ;
234
+
235
+ while ( e . MoveNext ( ) )
236
+ {
237
+ var currentKey = keySelector ( e . Current ) ;
238
+ if ( Math . Sign ( keyComparer . Compare ( currentKey , extremeKey ) ) == sign )
239
+ {
240
+ extreme = e . Current ;
241
+ extremeKey = currentKey ;
242
+ }
243
+ }
244
+ }
245
+
246
+ return extreme ;
115
247
}
116
248
}
117
249
}
0 commit comments