From c4700f28b450ff9918f859ab57c65d6292adae48 Mon Sep 17 00:00:00 2001 From: Claire Novotny Date: Tue, 1 Feb 2022 09:42:52 -0500 Subject: [PATCH] Add MinByWithTies and MaxByWithTies to bring back a MinBy/MaxBy that returns a list. --- .../System/Linq/QueryableEx.Generated.cs | 84 ++++++++++++++++++- .../System.Interactive.Tests.csproj | 5 +- .../System/Linq/Operators/Max.cs | 4 +- .../System/Linq/Operators/MaxBy.cs | 38 +-------- .../System/Linq/Operators/MaxByWithTies.cs | 84 +++++++++++++++++++ .../System/Linq/Operators/Min.cs | 4 +- .../System/Linq/Operators/MinBy.cs | 4 +- .../System/Linq/Operators/MinByWithTies.cs | 50 +++++++++++ 8 files changed, 225 insertions(+), 48 deletions(-) create mode 100644 Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxByWithTies.cs create mode 100644 Ix.NET/Source/System.Interactive/System/Linq/Operators/MinByWithTies.cs diff --git a/Ix.NET/Source/System.Interactive.Providers/System/Linq/QueryableEx.Generated.cs b/Ix.NET/Source/System.Interactive.Providers/System/Linq/QueryableEx.Generated.cs index ac2b9f65f7..27eaa22cc5 100644 --- a/Ix.NET/Source/System.Interactive.Providers/System/Linq/QueryableEx.Generated.cs +++ b/Ix.NET/Source/System.Interactive.Providers/System/Linq/QueryableEx.Generated.cs @@ -108,7 +108,7 @@ public static IList MinBy(IEnumerable source, F #if REFERENCE_ASSEMBLY return default; #else - return EnumerableEx.MinBy(source, keySelector); + return EnumerableEx.MinByWithTies(source, keySelector); #endif } #pragma warning restore 1591 @@ -142,6 +142,35 @@ public static IList MinBy(this IQueryable sourc ); } + /// + /// Returns the elements with the minimum key value by using the specified comparer to compare key values. + /// + /// Source sequence element type. + /// Key type. + /// Source sequence. + /// Key selector used to extract the key for each element in the sequence. + /// Comparer used to determine the minimum key value. + /// List with the elements that share the same minimum key value. + public static IList MinByWithTies(this IQueryable source, Expression> keySelector, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return source.Provider.Execute>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector, + Expression.Constant(comparer, typeof(IComparer)) + ) + ); + } + #pragma warning disable 1591 [EditorBrowsable(EditorBrowsableState.Never)] public static IList MinBy(IEnumerable source, Func keySelector, IComparer comparer) @@ -149,7 +178,17 @@ public static IList MinBy(IEnumerable source, F #if REFERENCE_ASSEMBLY return default; #else - return EnumerableEx.MinBy(source, keySelector, comparer); + return EnumerableEx.MinByWithTies(source, keySelector, comparer); +#endif + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IList MinByWithTies(IEnumerable source, Func keySelector, IComparer comparer) + { +#if REFERENCE_ASSEMBLY + return default; +#else + return EnumerableEx.MinByWithTies(source, keySelector, comparer); #endif } #pragma warning restore 1591 @@ -215,6 +254,31 @@ public static IList MaxBy(this IQueryable sourc ); } + /// + /// Returns the elements with the maximum key value by using the default comparer to compare key values. + /// + /// Source sequence element type. + /// Key type. + /// Source sequence. + /// Key selector used to extract the key for each element in the sequence. + /// List with the elements that share the same maximum key value. + public static IList MaxByWithTies(this IQueryable source, Expression> keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return source.Provider.Execute>( + Expression.Call( + null, + ((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)), + source.Expression, + keySelector + ) + ); + } + #pragma warning disable 1591 [EditorBrowsable(EditorBrowsableState.Never)] public static IList MaxBy(IEnumerable source, Func keySelector) @@ -222,11 +286,23 @@ public static IList MaxBy(IEnumerable source, F #if REFERENCE_ASSEMBLY return default; #else - return EnumerableEx.MaxBy(source, keySelector); + return EnumerableEx.MaxByWithTies(source, keySelector); +#endif + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public static IList MaxByWithTies(IEnumerable source, Func keySelector) + { +#if REFERENCE_ASSEMBLY + return default; +#else + return EnumerableEx.MaxByWithTies(source, keySelector); #endif } #pragma warning restore 1591 + + /// /// Returns the elements with the minimum key value by using the specified comparer to compare key values. /// @@ -263,7 +339,7 @@ public static IList MaxBy(IEnumerable source, F #if REFERENCE_ASSEMBLY return default; #else - return EnumerableEx.MaxBy(source, keySelector, comparer); + return EnumerableEx.MaxByWithTies(source, keySelector, comparer); #endif } #pragma warning restore 1591 diff --git a/Ix.NET/Source/System.Interactive.Tests/System.Interactive.Tests.csproj b/Ix.NET/Source/System.Interactive.Tests/System.Interactive.Tests.csproj index 2d8b2b9bcf..7281ed0908 100644 --- a/Ix.NET/Source/System.Interactive.Tests/System.Interactive.Tests.csproj +++ b/Ix.NET/Source/System.Interactive.Tests/System.Interactive.Tests.csproj @@ -17,10 +17,7 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Ix.NET/Source/System.Interactive/System/Linq/Operators/Max.cs b/Ix.NET/Source/System.Interactive/System/Linq/Operators/Max.cs index 2cfdc18e1f..c871c98ed2 100644 --- a/Ix.NET/Source/System.Interactive/System/Linq/Operators/Max.cs +++ b/Ix.NET/Source/System.Interactive/System/Linq/Operators/Max.cs @@ -9,7 +9,7 @@ namespace System.Linq public static partial class EnumerableEx { -#if !(REFERENCE_ASSEMBLY && (NET6_0_OR_GREATER)) +#if !(REFERENCE_ASSEMBLY && NET6_0_OR_GREATER) /// /// Returns the maximum value in the enumerable sequence by using the specified comparer to compare values. /// @@ -24,7 +24,7 @@ public static TSource Max(this IEnumerable source, IComparer x, comparer).First(); + return MaxByWithTies(source, x => x, comparer).First(); } #endif } diff --git a/Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxBy.cs b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxBy.cs index aad3e6a76e..a7f98b1468 100644 --- a/Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxBy.cs +++ b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxBy.cs @@ -8,7 +8,7 @@ namespace System.Linq { public static partial class EnumerableEx { -#if !(REFERENCE_ASSEMBLY && (NET6_0_OR_GREATER)) +#if !(REFERENCE_ASSEMBLY && NET6_0_OR_GREATER) /// /// Returns the elements with the maximum key value by using the default comparer to compare key values. /// @@ -17,6 +17,7 @@ public static partial class EnumerableEx /// Source sequence. /// Key selector used to extract the key for each element in the sequence. /// List with the elements that share the same maximum key value. + [Obsolete("Use MaxByWithTies to maintain same behavior with .NET 6 and later", false)] public static IList MaxBy(this IEnumerable source, Func keySelector) { if (source == null) @@ -36,6 +37,7 @@ public static IList MaxBy(this IEnumerable sour /// Key selector used to extract the key for each element in the sequence. /// Comparer used to determine the maximum key value. /// List with the elements that share the same maximum key value. + [Obsolete("Use MaxByWithTies to maintain same behavior with .NET 6 and later", false)] public static IList MaxBy(this IEnumerable source, Func keySelector, IComparer comparer) { if (source == null) @@ -47,40 +49,6 @@ public static IList MaxBy(this IEnumerable sour return ExtremaBy(source, keySelector, (key, minValue) => comparer.Compare(key, minValue)); } - - private static IList ExtremaBy(IEnumerable source, Func keySelector, Func compare) - { - var result = new List(); - - using (var e = source.GetEnumerator()) - { - if (!e.MoveNext()) - throw new InvalidOperationException("Source sequence doesn't contain any elements."); - - var current = e.Current; - var resKey = keySelector(current); - result.Add(current); - - while (e.MoveNext()) - { - var cur = e.Current; - var key = keySelector(cur); - - var cmp = compare(key, resKey); - if (cmp == 0) - { - result.Add(cur); - } - else if (cmp > 0) - { - result = new List { cur }; - resKey = key; - } - } - } - - return result; - } #endif } } diff --git a/Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxByWithTies.cs b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxByWithTies.cs new file mode 100644 index 0000000000..c71d1f6cde --- /dev/null +++ b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxByWithTies.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Linq +{ + public static partial class EnumerableEx + { + /// + /// Returns the elements with the maximum key value by using the default comparer to compare key values. + /// + /// Source sequence element type. + /// Key type. + /// Source sequence. + /// Key selector used to extract the key for each element in the sequence. + /// List with the elements that share the same maximum key value. + public static IList MaxByWithTies(this IEnumerable source, Func keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return MaxByWithTies(source, keySelector, Comparer.Default); + } + + /// + /// Returns the elements with the minimum key value by using the specified comparer to compare key values. + /// + /// Source sequence element type. + /// Key type. + /// Source sequence. + /// Key selector used to extract the key for each element in the sequence. + /// Comparer used to determine the maximum key value. + /// List with the elements that share the same maximum key value. + public static IList MaxByWithTies(this IEnumerable source, Func keySelector, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return ExtremaBy(source, keySelector, (key, minValue) => comparer.Compare(key, minValue)); + } + + private static IList ExtremaBy(IEnumerable source, Func keySelector, Func compare) + { + var result = new List(); + + using (var e = source.GetEnumerator()) + { + if (!e.MoveNext()) + throw new InvalidOperationException("Source sequence doesn't contain any elements."); + + var current = e.Current; + var resKey = keySelector(current); + result.Add(current); + + while (e.MoveNext()) + { + var cur = e.Current; + var key = keySelector(cur); + + var cmp = compare(key, resKey); + if (cmp == 0) + { + result.Add(cur); + } + else if (cmp > 0) + { + result = new List { cur }; + resKey = key; + } + } + } + + return result; + } + } +} diff --git a/Ix.NET/Source/System.Interactive/System/Linq/Operators/Min.cs b/Ix.NET/Source/System.Interactive/System/Linq/Operators/Min.cs index 0479262fe9..87ad5ea4a3 100644 --- a/Ix.NET/Source/System.Interactive/System/Linq/Operators/Min.cs +++ b/Ix.NET/Source/System.Interactive/System/Linq/Operators/Min.cs @@ -8,7 +8,7 @@ namespace System.Linq { public static partial class EnumerableEx { -#if !(REFERENCE_ASSEMBLY && (NET6_0_OR_GREATER)) +#if !(REFERENCE_ASSEMBLY && NET6_0_OR_GREATER) /// /// Returns the minimum value in the enumerable sequence by using the specified comparer to compare values. /// @@ -23,7 +23,7 @@ public static TSource Min(this IEnumerable source, IComparer x, comparer).First(); + return MinByWithTies(source, x => x, comparer).First(); } #endif } diff --git a/Ix.NET/Source/System.Interactive/System/Linq/Operators/MinBy.cs b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MinBy.cs index ff9313e7a7..aff3db17a2 100644 --- a/Ix.NET/Source/System.Interactive/System/Linq/Operators/MinBy.cs +++ b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MinBy.cs @@ -9,7 +9,7 @@ namespace System.Linq public static partial class EnumerableEx { -#if !(REFERENCE_ASSEMBLY && (NET6_0_OR_GREATER)) +#if !(REFERENCE_ASSEMBLY && NET6_0_OR_GREATER) /// /// Returns the elements with the minimum key value by using the default comparer to compare key values. /// @@ -18,6 +18,7 @@ public static partial class EnumerableEx /// Source sequence. /// Key selector used to extract the key for each element in the sequence. /// List with the elements that share the same minimum key value. + [Obsolete("Use MinByWithTies to maintain same behavior with .NET 6 and later", false)] public static IList MinBy(this IEnumerable source, Func keySelector) { if (source == null) @@ -37,6 +38,7 @@ public static IList MinBy(this IEnumerable sour /// Key selector used to extract the key for each element in the sequence. /// Comparer used to determine the minimum key value. /// List with the elements that share the same minimum key value. + [Obsolete("Use MinByWithTies to maintain same behavior with .NET 6 and later", false)] public static IList MinBy(this IEnumerable source, Func keySelector, IComparer comparer) { if (source == null) diff --git a/Ix.NET/Source/System.Interactive/System/Linq/Operators/MinByWithTies.cs b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MinByWithTies.cs new file mode 100644 index 0000000000..68d1e55663 --- /dev/null +++ b/Ix.NET/Source/System.Interactive/System/Linq/Operators/MinByWithTies.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT License. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; + +namespace System.Linq +{ + public static partial class EnumerableEx + { + /// + /// Returns the elements with the minimum key value by using the default comparer to compare key values. + /// + /// Source sequence element type. + /// Key type. + /// Source sequence. + /// Key selector used to extract the key for each element in the sequence. + /// List with the elements that share the same minimum key value. + public static IList MinByWithTies(this IEnumerable source, Func keySelector) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + + return MinByWithTies(source, keySelector, Comparer.Default); + } + + /// + /// Returns the elements with the minimum key value by using the specified comparer to compare key values. + /// + /// Source sequence element type. + /// Key type. + /// Source sequence. + /// Key selector used to extract the key for each element in the sequence. + /// Comparer used to determine the minimum key value. + /// List with the elements that share the same minimum key value. + public static IList MinByWithTies(this IEnumerable source, Func keySelector, IComparer comparer) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + if (keySelector == null) + throw new ArgumentNullException(nameof(keySelector)); + if (comparer == null) + throw new ArgumentNullException(nameof(comparer)); + + return ExtremaBy(source, keySelector, (key, minValue) => -comparer.Compare(key, minValue)); + } + } +}