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

Resuelto theringostarrs asked hace 15 años • 9 respuestas

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 de la lista que tiene el Heightvalor de propiedad más grande.

Puedo lograr obtener el valor más alto del Heightvalor pero no el objeto en sí.

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

theringostarrs avatar Jul 09 '09 11:07 theringostarrs
Aceptado

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 (en mi opinión) que cualquiera de las soluciones presentadas aquí, excepto 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 (lo que lo convierte en O(n^2))
  • La solución de pedido es O(n log n)
  • Tomar el Maxvalor y luego encontrar el primer elemento con ese valor es O(n), pero itera sobre la secuencia dos veces. Siempre que sea posible, debe utilizar LINQ en un solo paso.
  • Es mucho más sencillo de leer y comprender que la versión agregada y solo evalúa la proyección una vez por elemento.
Jon Skeet avatar Jul 09 '2009 05:07 Jon Skeet

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 listsecuencia sólo una vez. Si bien puede que no importe si listno List<T>cambia mientras tanto, podría importar para IEnumerable<T>objetos arbitrarios. 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, según la naturaleza de la secuencia). Sin embargo, sigue siendo una solución poco ideal para secuencias grandes. Sugiero escribir su propia MaxObjectextensión manualmente si tiene un gran conjunto de elementos para poder hacerlo de una sola vez sin ordenar ni otras cosas (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;
    }
}

y usarlo con:

var maxObject = list.MaxObject(item => item.Height);
Mehrdad Afshari avatar Jul 09 '2009 04:07 Mehrdad Afshari

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

En su lugar, puede utilizar la función agregada para seleccionar el mejor elemento según 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);
Cameron MacFarland avatar Jul 09 '2009 05:07 Cameron MacFarland

¿Y por qué no pruebas con esto??? :

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

O optimizar más:

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

mmm ?

Dragouf avatar Jun 13 '2011 12:06 Dragouf

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

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

Aquí lo tienes:

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

Uso:

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
meustrus avatar Aug 06 '2015 19:08 meustrus