LINQ: как выполнить .Max() по свойству всех объектов в коллекции и вернуть объект с максимальным значением
У меня есть список объектов, которые имеют два свойства int. Список - это вывод другого запроса linq. Объект:
public class DimensionPair
{
public int Height { get; set; }
public int Width { get; set; }
}
Я хочу найти и вернуть объект в списке с наибольшим значением свойства Height
.
Я могу получить максимальное значение Height
, но не сам объект.
Можно ли это сделать с Linq? Как?
Ответы
Ответ 1
У нас есть метод расширения, чтобы сделать именно это в MoreLINQ. Вы можете посмотреть на реализацию там, но в основном это случай итерации через данные, помня о максимальном элементе, который мы видели до сих пор, и о максимальном значении, которое оно произвело при проектировании.
В вашем случае вы сделаете что-то вроде:
var item = items.MaxBy(x => x.Height);
Это лучше (IMO), чем любое из представленных здесь решений, кроме второго решения Мехрдада (которое в основном совпадает с MaxBy
):
- Это O (n), в отличие от предыдущего принятого ответа , который находит максимальное значение на каждой итерации (делая его O (n ^ 2))
- Решение для упорядочения - это O (n log n)
- Принимая значение
Max
, а затем находим первый элемент с этим значением O (n), но повторяем по последовательности дважды. По возможности, вы должны использовать LINQ в однопроходном режиме.
- Гораздо проще читать и понимать, чем сводная версия, и оценивает только один раз за элемент
Ответ 2
Для этого потребуется сортировка (O (n log n)), но очень простая и гибкая. Еще одно преимущество - использовать его с LINQ to SQL:
var maxObject = list.OrderByDescending(item => item.Height).First();
Обратите внимание, что это имеет преимущество перечисления последовательности list
только один раз. Хотя не имеет значения, если list
является List<T>
, который не изменяется в то же время, это может иметь значение для любых объектов IEnumerable<T>
. Ничто не гарантирует, что последовательность не изменяется в разных перечислениях, поэтому методы, которые делают это несколько раз, могут быть опасными (и неэффективными, в зависимости от характера последовательности). Тем не менее, это еще не идеальное решение для больших последовательностей. Я предлагаю написать собственное расширение MaxObject
вручную, если у вас есть большой набор элементов, которые можно сделать за один проход без сортировки и другого материала (O (n)):
static class EnumerableExtensions {
public static T MaxObject<T,U>(this IEnumerable<T> source, Func<T,U> selector)
where U : IComparable<U> {
if (source == null) throw new ArgumentNullException("source");
bool first = true;
T maxObj = default(T);
U maxKey = default(U);
foreach (var item in source) {
if (first) {
maxObj = item;
maxKey = selector(maxObj);
first = false;
} else {
U currentKey = selector(item);
if (currentKey.CompareTo(maxKey) > 0) {
maxKey = currentKey;
maxObj = item;
}
}
}
if (first) throw new InvalidOperationException("Sequence is empty.");
return maxObj;
}
}
и используйте его с помощью:
var maxObject = list.MaxObject(item => item.Height);
Ответ 3
Выполнение заказа, а затем выбор первого элемента тратит много времени на упорядочивание элементов после первого. Вы не заботитесь о порядке этих действий.
Вместо этого вы можете использовать функцию aggregate для выбора лучшего элемента на основе того, что вы ищете.
var maxHeight = dimensions
.Aggregate((agg, next) =>
next.Height > agg.Height ? next : agg);
var maxHeightAndWidth = dimensions
.Aggregate((agg, next) =>
next.Height >= agg.Height && next.Width >= agg.Width ? next: agg);
Ответ 4
И почему бы вам не попробовать?
var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height));
ИЛИ больше оптимизировать:
var itemMaxHeight = items.Max(y => y.Height);
var itemsMax = items.Where(x => x.Height == itemMaxHeight);
mmm?
Ответ 5
Ответы пока замечательные! Но я вижу необходимость в решении со следующими ограничениями:
- Обычный, сжатый LINQ;
- O (n) сложность;
- Не оценивайте свойство более одного раза для каждого элемента.
Вот он:
public static T MaxBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
.Aggregate((max, next) => next.Item2.CompareTo(max.Item2) > 0 ? next : max).Item1;
}
public static T MinBy<T, R>(this IEnumerable<T> en, Func<T, R> evaluate) where R : IComparable<R> {
return en.Select(t => new Tuple<T, R>(t, evaluate(t)))
.Aggregate((max, next) => next.Item2.CompareTo(max.Item2) < 0 ? next : max).Item1;
}
Использование:
IEnumerable<Tuple<string, int>> list = new[] {
new Tuple<string, int>("other", 2),
new Tuple<string, int>("max", 4),
new Tuple<string, int>("min", 1),
new Tuple<string, int>("other", 3),
};
Tuple<string, int> min = list.MinBy(x => x.Item2); // "min", 1
Tuple<string, int> max = list.MaxBy(x => x.Item2); // "max", 4
Ответ 6
Я считаю, что сортировка по столбцу, который вы хотите получить MAX, а затем захват первого должен работать. Однако, если есть несколько объектов с одинаковым значением MAX, только один будет схвачен:
private void Test()
{
test v1 = new test();
v1.Id = 12;
test v2 = new test();
v2.Id = 12;
test v3 = new test();
v3.Id = 12;
List<test> arr = new List<test>();
arr.Add(v1);
arr.Add(v2);
arr.Add(v3);
test max = arr.OrderByDescending(t => t.Id).First();
}
class test
{
public int Id { get; set; }
}
Ответ 7
В NHibernate (с NHibernate.Linq) вы можете сделать это следующим образом:
return session.Query<T>()
.Single(a => a.Filter == filter &&
a.Id == session.Query<T>()
.Where(a2 => a2.Filter == filter)
.Max(a2 => a2.Id));
Что будет генерировать SQL следующим образом:
select *
from TableName foo
where foo.Filter = 'Filter On String'
and foo.Id = (select cast(max(bar.RowVersion) as INT)
from TableName bar
where bar.Name = 'Filter On String')
Что мне кажется довольно эффективным.
Ответ 8
Основываясь на исходном ответе Камерона, вот что я только что добавил в моей расширенной версии библиотеки SilverFlow FloatingWindowHost (копирование из FloatingWindowHost.cs в http://clipflair.codeplex.com)
public int MaxZIndex
{
get {
return FloatingWindows.Aggregate(-1, (maxZIndex, window) => {
int w = Canvas.GetZIndex(window);
return (w > maxZIndex) ? w : maxZIndex;
});
}
}
private void SetTopmost(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
Canvas.SetZIndex(element, MaxZIndex + 1);
}
Стоит отметить, что вышеописанный код Canvas.ZIndex является прикрепленным свойством, доступным для UIElements в различных контейнерах, а не только при размещении на холсте (см. Контроль порядка отображения (ZOrder ) в Silverlight без использования Canvas control). Угадайте, что даже можно упростить статический метод SetTopmost и SetBottomMost для UIElement, адаптировав этот код.
Ответ 9
Вы также можете обновить решение Mehrdad Afshari, переписав метод расширения для более быстрого (и лучшего поиска):
static class EnumerableExtensions
{
public static T MaxElement<T, R>(this IEnumerable<T> container, Func<T, R> valuingFoo) where R : IComparable
{
var enumerator = container.GetEnumerator();
if (!enumerator.MoveNext())
throw new ArgumentException("Container is empty!");
var maxElem = enumerator.Current;
var maxVal = valuingFoo(maxElem);
while (enumerator.MoveNext())
{
var currVal = valuingFoo(enumerator.Current);
if (currVal.CompareTo(maxVal) > 0)
{
maxVal = currVal;
maxElem = enumerator.Current;
}
}
return maxElem;
}
}
А потом просто используйте его:
var maxObject = list.MaxElement(item => item.Height);
Это имя будет понятным для людей, использующих С++ (потому что там есть std:: max_element).