LINQ: Cómo actuar.Max() en una propiedad de todos los objetos de una colección y devuelve el objeto con el valor máximo [duplicate]


Esta pregunta ya tiene una respuesta aquí:

Tengo una lista de objetos que tienen dos propiedades int. La lista es la salida 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 Height valor de la propiedad.

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

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

Author: Thaoden, 2009-07-09

9 answers

Tenemos un método de extensión para hacer exactamente esto en MoreLINQ. Puedes mirar la implementación allí, pero básicamente es un caso 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í que no sea la segunda solución de Mehrdad (que es básicamente lo mismo que MaxBy):

  • Es O (n) a diferencia de la respuesta aceptada anterior que encuentra el valor máximo en cada iteración(haciéndolo O (n^2))
  • La solución de pedido es O (n log n)
  • Tomar el valor Max y luego encontrar el primer elemento con ese valor es O(n), pero itera sobre la secuencia dos veces. Siempre que sea posible, debe usar LINQ de una sola pasada.
  • Es mucho más simple de leer y entender que la versión agregada, y solo evalúa la proyección una vez por elemento
 217
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:28

Esto requeriría una ordenació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 list solo una vez. Si bien puede que no importe si list es un List<T> que no cambia mientras tanto, podría importar para objetos arbitrarios IEnumerable<T>. Nada garantiza que la secuencia no cambie en diferentes enumeraciones por lo que los métodos que lo están haciendo varias veces puede ser peligroso (e ineficiente, dependiendo de la naturaleza de la secuencia). Sin embargo, sigue siendo una solución menos que ideal para secuencias grandes. Sugiero escribir su propia extensión MaxObject manualmente si tiene un gran conjunto de elementos para poder hacerlo en una sola pasada sin ordenar y otras cosas en absoluto(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 úsalo con:

var maxObject = list.MaxObject(item => item.Height);
 160
Author: Mehrdad Afshari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-09 09:21:54

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 usar la función de agregado para seleccionar el mejor elemento 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);
 113
Author: Cameron MacFarland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-29 13:36:10

¿Y por qué no lo intentas con esto ??? :

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

O más optimizar:

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

Mmm ?

 30
Author: Dragouf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-03-26 12:29:38

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

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

Aquí está:

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
 15
Author: meustrus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-07 15:30:54

Creo que ordenar por la columna de la que desea obtener el MÁXIMO y luego agarrar la primera debería funcionar. Sin embargo, si hay varios objetos con el mismo valor MÁXIMO, solo se tomará 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<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; }
}
 4
Author: regex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-07-09 04:36:55

En NHibernate (con NHibernate.Linq) usted podría hacerlo de la siguiente manera:

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

Que generará SQL como sigue:

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.

 2
Author: Tod Thomson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-01 02:41:49

Basado en la respuesta inicial de Cameron, esto es lo que acabo de agregar en mi versión mejorada de FloatingWindowHost de la biblioteca de SilverFlow (copiando desde FloatingWindowHost.cs at 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);
    }

Vale la pena señalar con respecto al código por encima de ese lienzo.zIndex es una propiedad adjunta disponible para UIElements en varios contenedores, no solo se usa cuando se aloja en un Lienzo (ver Controlando el orden de renderizado (ZOrder) en Silverlight sin usar el control Canvas ). Supongo que uno podría incluso hacer un método de extensión estática SetTopmost y SetBottomMost para UIElement fácilmente adaptando este código.

 1
Author: George Birbilis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:47

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

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

Y luego solo úsalo:

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

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

 1
Author: Maciej Oziębły,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-27 22:53:05