11using System ;
22using System . Collections . Generic ;
3+ using System . Diagnostics . CodeAnalysis ;
34using JetBrains . Annotations ;
45using Linq . Extras . Internal ;
56using Linq . Extras . Properties ;
@@ -15,7 +16,14 @@ partial class XEnumerable
1516 /// <param name="source">The sequence to return the maximum element from.</param>
1617 /// <param name="comparer">The comparer used to compare elements.</param>
1718 /// <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>
1825 [ Pure ]
26+ [ return : MaybeNull ]
1927 public static TSource Max < TSource > (
2028 [ NotNull ] this IEnumerable < TSource > source ,
2129 [ NotNull ] IComparer < TSource > comparer )
@@ -32,7 +40,14 @@ public static TSource Max<TSource>(
3240 /// <param name="source">The sequence to return the minimum element from.</param>
3341 /// <param name="comparer">The comparer used to compare elements.</param>
3442 /// <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>
3549 [ Pure ]
50+ [ return : MaybeNull ]
3651 public static TSource Min < TSource > (
3752 [ NotNull ] this IEnumerable < TSource > source ,
3853 [ NotNull ] IComparer < TSource > comparer )
@@ -47,22 +62,56 @@ private static TSource Extreme<TSource>(this IEnumerable<TSource> source, ICompa
4762 {
4863 comparer = comparer ?? Comparer < TSource > . Default ;
4964 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 )
5268 {
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.
5671
57- if ( Math . Sign ( compare ) == sign || first )
72+ // First, skip until the first non-null value, if any
73+ do
5874 {
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+ }
6094 }
61- first = false ;
6295 }
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 ;
63106
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+ }
66115
67116 return extreme ;
68117 }
@@ -81,16 +130,22 @@ private static InvalidOperationException EmptySequenceException()
81130 /// <param name="keySelector">A delegate that returns the key used to compare elements.</param>
82131 /// <param name="keyComparer">A comparer to compare the keys.</param>
83132 /// <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>
84139 [ Pure ]
140+ [ return : MaybeNull ]
85141 public static TSource MaxBy < TSource , TKey > (
86142 [ NotNull ] this IEnumerable < TSource > source ,
87143 [ NotNull ] Func < TSource , TKey > keySelector ,
88144 IComparer < TKey > ? keyComparer = null )
89145 {
90146 source . CheckArgumentNull ( nameof ( source ) ) ;
91147 keySelector . CheckArgumentNull ( nameof ( keySelector ) ) ;
92- var comparer = XComparer . By ( keySelector , keyComparer ) ;
93- return source . Max ( comparer ) ;
148+ return source . ExtremeBy ( keySelector , keyComparer , 1 ) ;
94149 }
95150
96151 /// <summary>
@@ -102,16 +157,93 @@ public static TSource MaxBy<TSource, TKey>(
102157 /// <param name="keySelector">A delegate that returns the key used to compare elements.</param>
103158 /// <param name="keyComparer">A comparer to compare the keys.</param>
104159 /// <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>
105166 [ Pure ]
167+ [ return : MaybeNull ]
106168 public static TSource MinBy < TSource , TKey > (
107169 [ NotNull ] this IEnumerable < TSource > source ,
108170 [ NotNull ] Func < TSource , TKey > keySelector ,
109171 IComparer < TKey > ? keyComparer = null )
110172 {
111173 source . CheckArgumentNull ( nameof ( source ) ) ;
112174 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 ;
115247 }
116248 }
117249}
0 commit comments