LINQ: Cómo realizar .Max () en una propiedad de todos los objetos en una colección y devolver el objeto con el valor máximo

Tengo una lista de objetos que tienen dos propiedades int. La lista es el resultado de otra consulta linq. El objeto:

public class DimensionPair { public int Height { get; set; } public int Width { get; set; } } 

Quiero encontrar y devolver el objeto en la lista que tiene el mayor valor de propiedad de Height .

Puedo lograr el valor más alto del valor de Height pero no el objeto en sí.

¿Puedo hacer esto con Linq? ¿Cómo?

Tenemos un método de extensión para hacer exactamente esto en MoreLINQ . Puede ver la implementación allí, pero básicamente se trata de iterar a través de los datos, recordando el elemento máximo que hemos visto hasta ahora y el valor máximo que produjo bajo la proyección.

En tu caso, harías algo como:

 var item = items.MaxBy(x => x.Height); 

Esto es mejor (IMO) que cualquiera de las soluciones presentadas aquí aparte de la segunda solución de Mehrdad (que es básicamente la misma que MaxBy ):

  • Es O (n) a diferencia de la respuesta aceptada anterior que encuentra el valor máximo en cada iteración (por lo que es O (n ^ 2))
  • La solución de pedido es O (n log n)
  • Tomando el valor Max y luego encontrando el primer elemento con ese valor es O (n), pero itera sobre la secuencia dos veces. Donde sea posible, debe usar LINQ de una sola pasada.
  • Es mucho más fácil de leer y comprender que la versión global, y solo evalúa la proyección una vez por elemento

Esto requeriría una clasificación (O (n log n)) pero es muy simple y flexible. Otra ventaja es poder usarlo con LINQ to SQL:

 var maxObject = list.OrderByDescending(item => item.Height).First(); 

Tenga en cuenta que esto tiene la ventaja de enumerar la secuencia de la list solo una vez. Si bien es posible que no importe si la list es una List que no cambia mientras tanto, podría importar IEnumerable arbitrarios IEnumerable . Nada garantiza que la secuencia no cambie en diferentes enumeraciones, por lo que los métodos que lo hacen varias veces pueden ser peligrosos (e ineficientes, dependiendo de la naturaleza de la secuencia). Sin embargo, sigue siendo una solución menos que ideal para grandes secuencias. Te sugiero que escribas tu propia extensión MaxObject manualmente si tienes un gran conjunto de elementos para poder hacerlo en una sola pasada sin ordenar ni nada de otro tipo (O (n)):

 static class EnumerableExtensions { public static T MaxObject(this IEnumerable source, Func selector) where U : IComparable { 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; } } 

y úsala con:

 var maxObject = list.MaxObject(item => item.Height); 

Hacer un pedido y luego seleccionar el primer artículo está perdiendo mucho tiempo ordenando los artículos después del primero. No te importa el orden de esos.

En su lugar, puede usar la función de agregado para seleccionar el mejor artículo en función de lo que está buscando.

 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); 

¿Y por qué no intentas con esto? :

 var itemsMax = items.Where(x => x.Height == items.Max(y => y.Height)); 

O más optimiza:

 var itemMaxHeight = items.Max(y => y.Height); var itemsMax = items.Where(x => x.Height == itemMaxHeight); 

mmm?

¡Las respuestas hasta ahora son geniales! Pero veo la necesidad de una solución con las siguientes limitaciones:

  1. LINQ claro y conciso;
  2. O (n) complejidad;
  3. No evalúe la propiedad más de una vez por elemento.

Aquí está:

 public static T MaxBy(this IEnumerable en, Func evaluate) where R : IComparable { return en.Select(t => new Tuple(t, evaluate(t))) .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) > 0 ? next : max).Item1; } public static T MinBy(this IEnumerable en, Func evaluate) where R : IComparable { return en.Select(t => new Tuple(t, evaluate(t))) .Aggregate((max, next) => next.Item2.CompareTo(max.Item2) < 0 ? next : max).Item1; } 

Uso:

 IEnumerable> list = new[] { new Tuple("other", 2), new Tuple("max", 4), new Tuple("min", 1), new Tuple("other", 3), }; Tuple min = list.MinBy(x => x.Item2); // "min", 1 Tuple max = list.MaxBy(x => x.Item2); // "max", 4 

Creo que la ordenación por la columna de la que desea obtener el MAX y luego la del primero debería funcionar. Sin embargo, si hay varios objetos con el mismo valor MAX, solo se agarrará uno:

 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 arr = new List(); arr.Add(v1); arr.Add(v2); arr.Add(v3); test max = arr.OrderByDescending(t => t.Id).First(); } class test { public int Id { get; set; } } 

En NHibernate (con NHibernate.Linq) puede hacerlo de la siguiente manera:

 return session.Query() .Single(a => a.Filter == filter && a.Id == session.Query() .Where(a2 => a2.Filter == filter) .Max(a2 => a2.Id)); 

Que generará SQL de la siguiente manera:

 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') 

Lo cual me parece bastante eficiente.

Con base en la respuesta inicial de Cameron, esto es lo que acabo de agregar en mi versión mejorada de FloatingWindowHost de la biblioteca SilverFlow (copiando de FloatingWindowHost.cs en http://clipflair.codeplex.com código fuente)

  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); } 

Cabe destacar que el Canvas.ZIndex es una propiedad adjunta disponible para UIElements en varios contenedores, no solo utilizado cuando se aloja en un Canvas (consulte Controlar el orden de representación (ZOrder) en Silverlight sin usar el control Canvas ). Adivina uno podría incluso hacer un método de extensión estático SetTopmost y SetBottomMost para UIElement fácilmente adaptando este código.

También puede actualizar la solución de Mehrdad Afshari reescribiendo el método de extensión a uno más rápido (y mejor):

 static class EnumerableExtensions { public static T MaxElement(this IEnumerable container, Func 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; } } 

Y luego solo úsalo:

 var maxObject = list.MaxElement(item => item.Height); 

Ese nombre será claro para las personas que usen C ++ (porque allí hay std :: max_element).