mirror of
https://github.com/oliverbooth/X10D
synced 2024-11-23 00:38:47 +00:00
Add MinMax and MinMaxBy (resolves #72)
This commit is contained in:
parent
e00a673a04
commit
3b85419da3
@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- X10D: Added `IEnumerable<T>.FirstWhereNotOrDefault(Func<T, bool>)`.
|
- X10D: Added `IEnumerable<T>.FirstWhereNotOrDefault(Func<T, bool>)`.
|
||||||
- X10D: Added `IEnumerable<T>.LastWhereNot(Func<T, bool>)`.
|
- X10D: Added `IEnumerable<T>.LastWhereNot(Func<T, bool>)`.
|
||||||
- X10D: Added `IEnumerable<T>.LastWhereNotOrDefault(Func<T, bool>)`.
|
- X10D: Added `IEnumerable<T>.LastWhereNotOrDefault(Func<T, bool>)`.
|
||||||
|
- X10D: Added `IEnumerable<T>.MinMax()` and `IEnumerable<T>.MinMaxBy()`.
|
||||||
- X10D: Added `IEnumerable<T>.WhereNot(Func<T, bool>)`.
|
- X10D: Added `IEnumerable<T>.WhereNot(Func<T, bool>)`.
|
||||||
- X10D: Added `IEnumerable<T>.WhereNotNull()`.
|
- X10D: Added `IEnumerable<T>.WhereNotNull()`.
|
||||||
- X10D: Added `IList<T>.RemoveRange(Range)`.
|
- X10D: Added `IList<T>.RemoveRange(Range)`.
|
||||||
|
159
X10D.Tests/src/Linq/EnumerableTests.cs
Normal file
159
X10D.Tests/src/Linq/EnumerableTests.cs
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
using X10D.Linq;
|
||||||
|
|
||||||
|
namespace X10D.Tests.Linq;
|
||||||
|
|
||||||
|
[TestClass]
|
||||||
|
public class EnumerableTests
|
||||||
|
{
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMax_ShouldReturnCorrectValues_UsingDefaultComparer()
|
||||||
|
{
|
||||||
|
IEnumerable<int> source = Enumerable.Range(1, 10);
|
||||||
|
(int minimum, int maximum) = source.MinMax();
|
||||||
|
Assert.AreEqual(1, minimum);
|
||||||
|
Assert.AreEqual(10, maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMax_ShouldReturnCorrectSelectedValues_UsingDefaultComparer()
|
||||||
|
{
|
||||||
|
IEnumerable<Person> source = Enumerable.Range(1, 10).Select(i => new Person {Age = i});
|
||||||
|
(int minimum, int maximum) = source.MinMax(p => p.Age);
|
||||||
|
Assert.AreEqual(1, minimum);
|
||||||
|
Assert.AreEqual(10, maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMax_ShouldReturnOppositeSelectedValues_UsingInverseComparer()
|
||||||
|
{
|
||||||
|
IEnumerable<Person> source = Enumerable.Range(1, 10).Select(i => new Person {Age = i});
|
||||||
|
(int minimum, int maximum) = source.MinMax(p => p.Age, new InverseComparer<int>());
|
||||||
|
Assert.AreEqual(10, minimum);
|
||||||
|
Assert.AreEqual(1, maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMax_ShouldReturnOppositeValues_UsingInverseComparer()
|
||||||
|
{
|
||||||
|
IEnumerable<int> source = Enumerable.Range(1, 10);
|
||||||
|
(int minimum, int maximum) = source.MinMax(new InverseComparer<int>());
|
||||||
|
Assert.AreEqual(10, minimum);
|
||||||
|
Assert.AreEqual(1, maximum);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMax_ShouldThrowArgumentNullException_GivenNullSelector()
|
||||||
|
{
|
||||||
|
IEnumerable<int> source = Enumerable.Range(1, 10);
|
||||||
|
Assert.ThrowsException<ArgumentNullException>(() => source.MinMax((Func<int, int>?)null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMax_ShouldThrowArgumentNullException_GivenNullSource()
|
||||||
|
{
|
||||||
|
IEnumerable<int>? source = null;
|
||||||
|
Assert.ThrowsException<ArgumentNullException>(() => source!.MinMax());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMax_ShouldThrowInvalidOperationException_GivenEmptySource()
|
||||||
|
{
|
||||||
|
IEnumerable<int> source = ArraySegment<int>.Empty;
|
||||||
|
Assert.ThrowsException<InvalidOperationException>(() => source.MinMax());
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMaxBy_ShouldReturnCorrectSelectedValues_UsingDefaultComparer()
|
||||||
|
{
|
||||||
|
IEnumerable<Person> source = Enumerable.Range(1, 10).Select(i => new Person {Age = i});
|
||||||
|
(Person minimum, Person maximum) = source.MinMaxBy(p => p.Age);
|
||||||
|
Assert.AreEqual(1, minimum.Age);
|
||||||
|
Assert.AreEqual(10, maximum.Age);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMaxBy_ShouldReturnOppositeSelectedValues_UsingInverseComparer()
|
||||||
|
{
|
||||||
|
IEnumerable<Person> source = Enumerable.Range(1, 10).Select(i => new Person {Age = i});
|
||||||
|
(Person minimum, Person maximum) = source.MinMaxBy(p => p.Age, new InverseComparer<int>());
|
||||||
|
Assert.AreEqual(10, minimum.Age);
|
||||||
|
Assert.AreEqual(1, maximum.Age);
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMaxBy_ShouldThrowArgumentNullException_GivenNullSelector()
|
||||||
|
{
|
||||||
|
IEnumerable<Person> source = Enumerable.Range(1, 10).Select(i => new Person {Age = i});
|
||||||
|
Assert.ThrowsException<ArgumentNullException>(() => source.MinMaxBy((Func<Person, int>?)null!));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMaxBy_ShouldThrowArgumentNullException_GivenNullSource()
|
||||||
|
{
|
||||||
|
IEnumerable<Person>? source = null;
|
||||||
|
Assert.ThrowsException<ArgumentNullException>(() => source!.MinMaxBy(p => p.Age));
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
public void MinMaxBy_ShouldThrowInvalidOperationException_GivenEmptySource()
|
||||||
|
{
|
||||||
|
IEnumerable<Person> source = ArraySegment<Person>.Empty;
|
||||||
|
Assert.ThrowsException<InvalidOperationException>(() => source.MinMaxBy(p => p.Age));
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct InverseComparer<T> : IComparer<T> where T : IComparable<T>
|
||||||
|
{
|
||||||
|
public int Compare(T? x, T? y)
|
||||||
|
{
|
||||||
|
if (x is null)
|
||||||
|
{
|
||||||
|
return y is null ? 0 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return y is null ? -1 : y.CompareTo(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Person : IComparable<Person>, IComparable
|
||||||
|
{
|
||||||
|
public int Age { get; set; }
|
||||||
|
|
||||||
|
public static bool operator <(Person left, Person right)
|
||||||
|
{
|
||||||
|
return left.CompareTo(right) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >(Person left, Person right)
|
||||||
|
{
|
||||||
|
return left.CompareTo(right) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <=(Person left, Person right)
|
||||||
|
{
|
||||||
|
return left.CompareTo(right) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator >=(Person left, Person right)
|
||||||
|
{
|
||||||
|
return left.CompareTo(right) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Person other)
|
||||||
|
{
|
||||||
|
return Age.CompareTo(other.Age);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(object? obj)
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(null, obj))
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj is Person other
|
||||||
|
? CompareTo(other)
|
||||||
|
: throw new ArgumentException($"Object must be of type {nameof(Person)}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
399
X10D/src/Linq/EnumerableExtensions.cs
Normal file
399
X10D/src/Linq/EnumerableExtensions.cs
Normal file
@ -0,0 +1,399 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace X10D.Linq;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// LINQ-inspired extension methods for <see cref="IEnumerable{T}" />.
|
||||||
|
/// </summary>
|
||||||
|
public static class EnumerableExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the minimum and maximum values in a sequence of values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">A sequence of values to determine the minimum and maximum values of.</param>
|
||||||
|
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>A tuple containing the minimum and maximum values in <paramref name="source" />.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException"><paramref name="source" /> contains no elements.</exception>
|
||||||
|
public static (T? Minimum, T? Maximum) MinMax<T>(this IEnumerable<T> source)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
#else
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return MinMax(source, Comparer<T>.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the minimum and maximum values in a sequence of values, using a specified comparer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">A sequence of values to determine the minimum and maximum values of.</param>
|
||||||
|
/// <param name="comparer">The comparer which shall be used to compare each element in the sequence.</param>
|
||||||
|
/// <typeparam name="T">The type of the elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <returns>A tuple containing the minimum and maximum values in <paramref name="source" />.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException"><paramref name="source" /> contains no elements.</exception>
|
||||||
|
public static (T? Minimum, T? Maximum) MinMax<T>(this IEnumerable<T> source, IComparer<T>? comparer)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
#else
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
comparer ??= Comparer<T>.Default;
|
||||||
|
T? minValue;
|
||||||
|
T? maxValue;
|
||||||
|
|
||||||
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
|
if (source.TryGetSpan(out ReadOnlySpan<T> span))
|
||||||
|
{
|
||||||
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Source contains no elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
minValue = span[0];
|
||||||
|
maxValue = minValue;
|
||||||
|
|
||||||
|
for (var index = 1; (uint)index < (uint)span.Length; index++)
|
||||||
|
{
|
||||||
|
T current = span[index];
|
||||||
|
|
||||||
|
if (comparer.Compare(current, minValue) < 0)
|
||||||
|
{
|
||||||
|
minValue = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comparer.Compare(current, maxValue) > 0)
|
||||||
|
{
|
||||||
|
maxValue = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
|
using (IEnumerator<T> enumerator = source.GetEnumerator())
|
||||||
|
{
|
||||||
|
if (!enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Source contains no elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
minValue = enumerator.Current;
|
||||||
|
maxValue = minValue;
|
||||||
|
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
T current = enumerator.Current;
|
||||||
|
|
||||||
|
if (minValue is null || comparer.Compare(current, minValue) < 0)
|
||||||
|
{
|
||||||
|
minValue = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxValue is null || comparer.Compare(current, maxValue) > 0)
|
||||||
|
{
|
||||||
|
maxValue = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes a transform function on each element of a sequence of elements and returns the minimum and maximum values.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">A sequence of values to determine the minimum and maximum values of.</param>
|
||||||
|
/// <param name="selector">A transform function to apply to each element.</param>
|
||||||
|
/// <typeparam name="TSource">The type of the elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <typeparam name="TResult">The type of the elements to compare.</typeparam>
|
||||||
|
/// <returns>A tuple containing the minimum and maximum values in <paramref name="source" />.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException"><paramref name="source" /> contains no elements.</exception>
|
||||||
|
public static (TResult? Minimum, TResult? Maximum) MinMax<TSource, TResult>(this IEnumerable<TSource> source,
|
||||||
|
Func<TSource, TResult> selector)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
ArgumentNullException.ThrowIfNull(selector);
|
||||||
|
#else
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selector is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(selector));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return MinMax(source, selector, Comparer<TResult>.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invokes a transform function on each element of a sequence of elements and returns the minimum and maximum values,
|
||||||
|
/// using a specified comparer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">A sequence of values to determine the minimum and maximum values of.</param>
|
||||||
|
/// <param name="selector">A transform function to apply to each element.</param>
|
||||||
|
/// <param name="comparer">The comparer which shall be used to compare each element in the sequence.</param>
|
||||||
|
/// <typeparam name="TSource">The type of the elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <typeparam name="TResult">The type of the elements to compare.</typeparam>
|
||||||
|
/// <returns>A tuple containing the minimum and maximum values in <paramref name="source" />.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException"><paramref name="source" /> contains no elements.</exception>
|
||||||
|
public static (TResult? Minimum, TResult? Maximum) MinMax<TSource, TResult>(this IEnumerable<TSource> source,
|
||||||
|
Func<TSource, TResult> selector,
|
||||||
|
IComparer<TResult>? comparer)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
ArgumentNullException.ThrowIfNull(selector);
|
||||||
|
#else
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selector is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(selector));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
comparer ??= Comparer<TResult>.Default;
|
||||||
|
TResult? minValue;
|
||||||
|
TResult? maxValue;
|
||||||
|
|
||||||
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
|
if (source.TryGetSpan(out ReadOnlySpan<TSource> span))
|
||||||
|
{
|
||||||
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Source contains no elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
minValue = selector(span[0]);
|
||||||
|
maxValue = minValue;
|
||||||
|
|
||||||
|
for (var index = 1; (uint)index < (uint)span.Length; index++)
|
||||||
|
{
|
||||||
|
TResult current = selector(span[index]);
|
||||||
|
|
||||||
|
if (minValue is null || comparer.Compare(current, minValue) < 0)
|
||||||
|
{
|
||||||
|
minValue = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxValue is null || comparer.Compare(current, maxValue) > 0)
|
||||||
|
{
|
||||||
|
maxValue = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
|
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
|
||||||
|
{
|
||||||
|
if (!enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Source contains no elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
minValue = selector(enumerator.Current);
|
||||||
|
maxValue = minValue;
|
||||||
|
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
TResult current = selector(enumerator.Current);
|
||||||
|
|
||||||
|
if (minValue is null || comparer.Compare(current, minValue) < 0)
|
||||||
|
{
|
||||||
|
minValue = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxValue is null || comparer.Compare(current, maxValue) > 0)
|
||||||
|
{
|
||||||
|
maxValue = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the minimum and maximum values in a sequence according to a specified key selector function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">A sequence of values to determine the minimum and maximum values of.</param>
|
||||||
|
/// <param name="keySelector">A function to extract the key for each element.</param>
|
||||||
|
/// <typeparam name="TSource">The type of the elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <typeparam name="TResult">The type of the elements to compare.</typeparam>
|
||||||
|
/// <returns>A tuple containing the minimum and maximum values in <paramref name="source" />.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException"><paramref name="source" /> contains no elements.</exception>
|
||||||
|
public static (TSource? Minimum, TSource? Maximum) MinMaxBy<TSource, TResult>(this IEnumerable<TSource> source,
|
||||||
|
Func<TSource, TResult> keySelector)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
ArgumentNullException.ThrowIfNull(keySelector);
|
||||||
|
#else
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keySelector is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(keySelector));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return MinMaxBy(source, keySelector, Comparer<TResult>.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the minimum and maximum values in a sequence according to a specified key selector function.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="source">A sequence of values to determine the minimum and maximum values of.</param>
|
||||||
|
/// <param name="keySelector">A function to extract the key for each element.</param>
|
||||||
|
/// <param name="comparer">The comparer which shall be used to compare each element in the sequence.</param>
|
||||||
|
/// <typeparam name="TSource">The type of the elements in <paramref name="source" />.</typeparam>
|
||||||
|
/// <typeparam name="TResult">The type of the elements to compare.</typeparam>
|
||||||
|
/// <returns>A tuple containing the minimum and maximum values in <paramref name="source" />.</returns>
|
||||||
|
/// <exception cref="ArgumentNullException"><paramref name="source" /> is <see langword="null" />.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException"><paramref name="source" /> contains no elements.</exception>
|
||||||
|
public static (TSource? Minimum, TSource? Maximum) MinMaxBy<TSource, TResult>(this IEnumerable<TSource> source,
|
||||||
|
Func<TSource, TResult> keySelector,
|
||||||
|
IComparer<TResult>? comparer)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
ArgumentNullException.ThrowIfNull(keySelector);
|
||||||
|
#else
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keySelector is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(keySelector));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
comparer ??= Comparer<TResult>.Default;
|
||||||
|
TSource? minValue;
|
||||||
|
TSource? maxValue;
|
||||||
|
|
||||||
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
|
if (source.TryGetSpan(out ReadOnlySpan<TSource> span))
|
||||||
|
{
|
||||||
|
if (span.IsEmpty)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Source contains no elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
minValue = span[0];
|
||||||
|
maxValue = minValue;
|
||||||
|
|
||||||
|
for (var index = 1; (uint)index < (uint)span.Length; index++)
|
||||||
|
{
|
||||||
|
TSource current = span[index];
|
||||||
|
TResult transformedCurrent = keySelector(current);
|
||||||
|
|
||||||
|
if (minValue is null || comparer.Compare(transformedCurrent, keySelector(minValue)) < 0)
|
||||||
|
{
|
||||||
|
minValue = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxValue is null || comparer.Compare(transformedCurrent, keySelector(maxValue)) > 0)
|
||||||
|
{
|
||||||
|
maxValue = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReSharper disable once PossibleMultipleEnumeration
|
||||||
|
using (IEnumerator<TSource> enumerator = source.GetEnumerator())
|
||||||
|
{
|
||||||
|
if (!enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Source contains no elements");
|
||||||
|
}
|
||||||
|
|
||||||
|
minValue = enumerator.Current;
|
||||||
|
maxValue = minValue;
|
||||||
|
|
||||||
|
while (enumerator.MoveNext())
|
||||||
|
{
|
||||||
|
TSource current = enumerator.Current;
|
||||||
|
TResult transformedCurrent = keySelector(current);
|
||||||
|
|
||||||
|
if (minValue is null || comparer.Compare(transformedCurrent, keySelector(minValue)) < 0)
|
||||||
|
{
|
||||||
|
minValue = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxValue is null || comparer.Compare(transformedCurrent, keySelector(maxValue)) > 0)
|
||||||
|
{
|
||||||
|
maxValue = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (minValue, maxValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetSpan<TSource>(this IEnumerable<TSource> source, out ReadOnlySpan<TSource> span)
|
||||||
|
{
|
||||||
|
#if NET6_0_OR_GREATER
|
||||||
|
ArgumentNullException.ThrowIfNull(source);
|
||||||
|
#else
|
||||||
|
if (source is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(source));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var result = true;
|
||||||
|
|
||||||
|
switch (source)
|
||||||
|
{
|
||||||
|
case TSource[] array:
|
||||||
|
span = array;
|
||||||
|
break;
|
||||||
|
|
||||||
|
#if NET5_0_OR_GREATER
|
||||||
|
case List<TSource> list:
|
||||||
|
span = CollectionsMarshal.AsSpan(list);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
span = default;
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user