c# - LINQ: Cómo realizar.Max() en una propiedad de todos los objetos de una colección y devolver el objeto con el valor máximo
linq max (9)
Esta pregunta ya tiene una respuesta aquí:
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 obtener el valor más alto del valor de Height
pero no el objeto en sí.
¿Puedo hacer esto con Linq? ¿Cómo?
¡Las respuestas hasta ahora son geniales! Pero veo la necesidad de una solución con las siguientes restricciones:
- LINQ simple y conciso;
- O (n) complejidad;
- 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
¿Y por qué no 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?
Basado en la respuesta inicial de Cameron, esto es lo que acabo de agregar a mi versión mejorada del FloatingWindowHost de la biblioteca SilverFlow (copia desde el código fuente de FloatingWindowHost.cs en 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);
}
Vale la pena señalar con respecto al código anterior que Canvas.ZIndex es una propiedad adjunta disponible para UIElements en varios contenedores, que no se usa solo cuando se aloja en un Canvas (consulte Control del 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.
Creo que la clasificación por la columna de la que desea obtener el MÁXIMO y luego tomar la primera debería funcionar. Sin embargo, si hay varios objetos con el mismo valor MAX, solo se capturará 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; }
}
En NHibernate (con NHibernate.Linq) puede 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 que me parece bastante eficiente.
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<T>
que no cambia mientras tanto, podría ser importante para objetos IEnumerable<T>
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, dependiendo de la naturaleza de la secuencia). Sin embargo, sigue siendo una solución menos que ideal para secuencias grandes. Le sugiero que escriba su propia extensión de MaxObject
manualmente si tiene un gran conjunto de elementos para poder hacerlo en una sola pasada sin ordenar y 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);
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 agregación 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);
También puede actualizar la solución de Mehrdad Afshari reescribiendo el método de extensión a uno más rápido (y con mejor aspecto):
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 quedará claro para las personas que usan C ++ (porque hay std :: max_element allí).
Tenemos un método de extensión para hacer exactamente esto en MoreLINQ . Puede ver la implementación allí, pero básicamente es un caso de iteración 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 la misma que MaxBy
):
- Es O (n) a diferencia de la respuesta aceptada anterior que encuentra el valor máximo en cada iteración (haciéndola 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 se repite dos veces en la secuencia. Siempre que sea posible, debe usar LINQ de una sola pasada. - Es mucho más sencillo de leer y entender que la versión agregada, y solo evalúa la proyección una vez por elemento