Reputation: 1356
I have a set of objects with two properties, A and B. I'd like to get the Min of A and the Max of B.
eg
var minA = objects.Min(o => o.A);
var maxB = objects.Max(o => o.B);
Using LINQ query syntax, is there a way to do this so it only passes over the set once?
Desired outcome would be an anonymous type (eg, results.MinA = x, results.MaxB = y)
Upvotes: 21
Views: 16718
Reputation: 4839
There are new helpers for LINQ in .NET 6 for this matter: MaxBy and MinBy
var people = GetPeople();
var oldest = people.MaxBy(p => p.Age);
var youngest = people.MinBy(p => p.Age);
Console.WriteLine($"The oldest person is {oldest.Age}");
Console.WriteLine($"The youngest person is {youngest.Age}");
Upvotes: -1
Reputation: 17600
Depending on definition on single linq query this is a decent fit for an extension method.
public static MinAndMax<T> MinAndMax<T>(this IEnumerable<T> source)
where T : IComparable<T>
{
Ensure.NotNullOrEmpty(source, nameof(source));
using (var enumerator = source.GetEnumerator())
{
if (enumerator.MoveNext())
{
var min = enumerator.Current;
var max = enumerator.Current;
while (enumerator.MoveNext())
{
if (Comparer<T>.Default.Compare(enumerator.Current, min) < 0)
{
min = enumerator.Current;
}
if (Comparer<T>.Default.Compare(enumerator.Current, max) > 0)
{
max = enumerator.Current;
}
}
return new MinAndMax<T>(min, max);
}
throw new InvalidOperationException("Sequence contains no elements.");
}
}
public struct MinAndMax<T>
where T : IComparable<T>
{
public MinAndMax(T min, T max)
{
this.Min = min;
this.Max = max;
}
public T Min { get; }
public T Max { get; }
}
Then you can use it like:
var minMax = xs.MinAndMax();
Upvotes: 0
Reputation: 447
If you want to get min and max values from object list (and you dont want to group by property) than you could use this linq:
var val = new List<dynamic>{
new { A=1, B=1 },
new { A=2, B=2 },
new { A=3, B=4 }
};
var minMax = val.GroupBy(f => string.Empty).Select(f => new
{
Min = f.Min(g => g.A),
Max = f.Max(g => g.B)
}).FirstOrDefault();
Upvotes: 0
Reputation: 17880
I know you wanted a Linq query, but I can't stop pointing out the non-linq version may be much more readable. Compare this:
IEnumerable<Message> messages = SomeMessages();
Func<DateTime, DateTime, DateTime> min = (dt1, dt2) => dt1 > dt2 ? dt2 : dt1;
Func<DateTime, DateTime, DateTime> max = (dt1, dt2) => dt1 > dt2 ? dt1 : dt2;
// linq version
var result = messages.Aggregate(
new { StartDate = DateTime.MaxValue, EndDate = DateTime.MinValue }, /* initial value */
(accumulate, current) => new { StartDate = min(accumulate.StartDate, current.ReceivedTime), EndDate = max(accumulate.EndDate, current.ReceivedTime) });
// non-linq version
DateTime start = DateTime.MaxValue;
DateTime end = DateTime.MinValue;
foreach (DateTime dt in messages.Select(msg => msg.ReceivedTime))
{
start = min(start, dt);
end = max(end, dt);
}
Upvotes: 3
Reputation: 146
Something like this will work for you My previous answer didn't work,
I went back and tried a couple of the different answers here and ended up with this solution, suggested previously
var values = new List<int> {1, 2, 3, 4, 5};
var maxmin = new {min = values.Min(), max = values.Max()};
Upvotes: -4
Reputation: 24166
Min and Max are both aggregates. The general linq aggregate function is Aggregate
Assuming property A is an integer, and B is a string, you could write something like this:
objects.Aggregate(
new {
MinA = int.MaxValue,
MaxB = string.Empty
},
(accumulator, o) => new {
MinA = Math.Min(o.A, accumulator.MinA),
MaxB = o.B > accumulator.MaxB ? o.B : accumulator.MaxB
});
Upvotes: 25
Reputation: 11860
You can use YourObject
as a container for the results, and use Math.Min
and Math.Max
to tidy the syntax:
var maxmin = objects.Aggregate(objects[0], (i, j) =>
new YourObject { A = Math.Min(i.A, j.A), B = Math.Max(i.B, j.B) });
Upvotes: 0
Reputation: 8447
You can use Aggregate
method.
var res = new { Min = objects[0].A, Max = objects[0].B }
var res = objects.Aggregate(res, (r, curr) => r.Min = r.Min < curr.A ? r.Min : curr.A; r.Max = r.Max > curr.B ? r.Max : curr.B);
Upvotes: 3