Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MinByWithTies and MaxByWithTies to bring back a MinBy/MaxBy that returns a list #1700

Merged
merged 1 commit into from
Feb 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public static TSource Min<TSource>(IEnumerable<TSource> source, IComparer<TSourc
#if REFERENCE_ASSEMBLY
return default;
#else
return EnumerableEx.MinBy(source, keySelector);
return EnumerableEx.MinByWithTies(source, keySelector);
#endif
}
#pragma warning restore 1591
Expand Down Expand Up @@ -142,14 +142,53 @@ public static TSource Min<TSource>(IEnumerable<TSource> source, IComparer<TSourc
);
}

/// <summary>
/// Returns the elements with the minimum key value by using the specified comparer to compare key values.
/// </summary>
/// <typeparam name="TSource">Source sequence element type.</typeparam>
/// <typeparam name="TKey">Key type.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <param name="comparer">Comparer used to determine the minimum key value.</param>
/// <returns>List with the elements that share the same minimum key value.</returns>
public static IList<TSource> MinByWithTies<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector, IComparer<TKey> 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<IList<TSource>>(
Expression.Call(
null,
((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)),
source.Expression,
keySelector,
Expression.Constant(comparer, typeof(IComparer<TKey>))
)
);
}

#pragma warning disable 1591
[EditorBrowsable(EditorBrowsableState.Never)]
public static IList<TSource> MinBy<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
{
#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<TSource> MinByWithTies<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
{
#if REFERENCE_ASSEMBLY
return default;
#else
return EnumerableEx.MinByWithTies(source, keySelector, comparer);
#endif
}
#pragma warning restore 1591
Expand Down Expand Up @@ -215,18 +254,55 @@ public static TSource Max<TSource>(IEnumerable<TSource> source, IComparer<TSourc
);
}

/// <summary>
/// Returns the elements with the maximum key value by using the default comparer to compare key values.
/// </summary>
/// <typeparam name="TSource">Source sequence element type.</typeparam>
/// <typeparam name="TKey">Key type.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <returns>List with the elements that share the same maximum key value.</returns>
public static IList<TSource> MaxByWithTies<TSource, TKey>(this IQueryable<TSource> source, Expression<Func<TSource, TKey>> keySelector)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));

return source.Provider.Execute<IList<TSource>>(
Expression.Call(
null,
((MethodInfo)MethodInfo.GetCurrentMethod()).MakeGenericMethod(typeof(TSource), typeof(TKey)),
source.Expression,
keySelector
)
);
}

#pragma warning disable 1591
[EditorBrowsable(EditorBrowsableState.Never)]
public static IList<TSource> MaxBy<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
#if REFERENCE_ASSEMBLY
return default;
#else
return EnumerableEx.MaxBy(source, keySelector);
return EnumerableEx.MaxByWithTies(source, keySelector);
#endif
}

[EditorBrowsable(EditorBrowsableState.Never)]
public static IList<TSource> MaxByWithTies<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
#if REFERENCE_ASSEMBLY
return default;
#else
return EnumerableEx.MaxByWithTies(source, keySelector);
#endif
}
#pragma warning restore 1591



/// <summary>
/// Returns the elements with the minimum key value by using the specified comparer to compare key values.
/// </summary>
Expand Down Expand Up @@ -263,7 +339,7 @@ public static TSource Max<TSource>(IEnumerable<TSource> source, IComparer<TSourc
#if REFERENCE_ASSEMBLY
return default;
#else
return EnumerableEx.MaxBy(source, keySelector, comparer);
return EnumerableEx.MaxByWithTies(source, keySelector, comparer);
#endif
}
#pragma warning restore 1591
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="FluentAssertions" Version="6.4.0" />
<PackageReference Include="xunit" Version="2.4.1" />
</ItemGroup>
Expand Down
4 changes: 2 additions & 2 deletions Ix.NET/Source/System.Interactive/System/Linq/Operators/Max.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/// <summary>
/// Returns the maximum value in the enumerable sequence by using the specified comparer to compare values.
/// </summary>
Expand All @@ -24,7 +24,7 @@ public static TSource Max<TSource>(this IEnumerable<TSource> source, IComparer<T
if (comparer == null)
throw new ArgumentNullException(nameof(comparer));

return MaxBy(source, x => x, comparer).First();
return MaxByWithTies(source, x => x, comparer).First();
}
#endif
}
Expand Down
38 changes: 3 additions & 35 deletions Ix.NET/Source/System.Interactive/System/Linq/Operators/MaxBy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/// <summary>
/// Returns the elements with the maximum key value by using the default comparer to compare key values.
/// </summary>
Expand All @@ -17,6 +17,7 @@ public static partial class EnumerableEx
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <returns>List with the elements that share the same maximum key value.</returns>
[Obsolete("Use MaxByWithTies to maintain same behavior with .NET 6 and later", false)]
public static IList<TSource> MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
if (source == null)
Expand All @@ -36,6 +37,7 @@ public static partial class EnumerableEx
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <param name="comparer">Comparer used to determine the maximum key value.</param>
/// <returns>List with the elements that share the same maximum key value.</returns>
[Obsolete("Use MaxByWithTies to maintain same behavior with .NET 6 and later", false)]
public static IList<TSource> MaxBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
{
if (source == null)
Expand All @@ -47,40 +49,6 @@ public static partial class EnumerableEx

return ExtremaBy(source, keySelector, (key, minValue) => comparer.Compare(key, minValue));
}

private static IList<TSource> ExtremaBy<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, TKey, int> compare)
{
var result = new List<TSource>();

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<TSource> { cur };
resKey = key;
}
}
}

return result;
}
#endif
}
}
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Returns the elements with the maximum key value by using the default comparer to compare key values.
/// </summary>
/// <typeparam name="TSource">Source sequence element type.</typeparam>
/// <typeparam name="TKey">Key type.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <returns>List with the elements that share the same maximum key value.</returns>
public static IList<TSource> MaxByWithTies<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
if (keySelector == null)
throw new ArgumentNullException(nameof(keySelector));

return MaxByWithTies(source, keySelector, Comparer<TKey>.Default);
}

/// <summary>
/// Returns the elements with the minimum key value by using the specified comparer to compare key values.
/// </summary>
/// <typeparam name="TSource">Source sequence element type.</typeparam>
/// <typeparam name="TKey">Key type.</typeparam>
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <param name="comparer">Comparer used to determine the maximum key value.</param>
/// <returns>List with the elements that share the same maximum key value.</returns>
public static IList<TSource> MaxByWithTies<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> 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<TSource> ExtremaBy<TSource, TKey>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TKey, TKey, int> compare)
{
var result = new List<TSource>();

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<TSource> { cur };
resKey = key;
}
}
}

return result;
}
}
}
4 changes: 2 additions & 2 deletions Ix.NET/Source/System.Interactive/System/Linq/Operators/Min.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/// <summary>
/// Returns the minimum value in the enumerable sequence by using the specified comparer to compare values.
/// </summary>
Expand All @@ -23,7 +23,7 @@ public static TSource Min<TSource>(this IEnumerable<TSource> source, IComparer<T
if (comparer == null)
throw new ArgumentNullException(nameof(comparer));

return MinBy(source, x => x, comparer).First();
return MinByWithTies(source, x => x, comparer).First();
}
#endif
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
/// <summary>
/// Returns the elements with the minimum key value by using the default comparer to compare key values.
/// </summary>
Expand All @@ -18,6 +18,7 @@ public static partial class EnumerableEx
/// <param name="source">Source sequence.</param>
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <returns>List with the elements that share the same minimum key value.</returns>
[Obsolete("Use MinByWithTies to maintain same behavior with .NET 6 and later", false)]
public static IList<TSource> MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
if (source == null)
Expand All @@ -37,6 +38,7 @@ public static partial class EnumerableEx
/// <param name="keySelector">Key selector used to extract the key for each element in the sequence.</param>
/// <param name="comparer">Comparer used to determine the minimum key value.</param>
/// <returns>List with the elements that share the same minimum key value.</returns>
[Obsolete("Use MinByWithTies to maintain same behavior with .NET 6 and later", false)]
public static IList<TSource> MinBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
{
if (source == null)
Expand Down