LINQ: comment effectuer .Max () sur une propriété de tous les objects d’une collection et renvoyer l’object avec la valeur maximale

J’ai une liste d’objects ayant deux propriétés int. La liste est la sortie d’une autre requête linq. L’object:

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

Je souhaite rechercher et renvoyer l’object dans la liste qui possède la plus grande valeur de propriété Height .

Je peux réussir à obtenir la valeur la plus élevée de la valeur Height mais pas l’object lui-même.

Puis-je le faire avec Linq? Comment?

Nous avons une méthode d’extension pour faire exactement cela dans MoreLINQ . Vous pouvez regarder l’implémentation là-bas, mais en gros, il s’agit de parcourir les données, en se rappelant de l’élément maximal que nous avons vu jusqu’à présent et de la valeur maximale produite par la projection.

Dans votre cas, vous feriez quelque chose comme:

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

C’est mieux (IMO) que toutes les solutions présentées ici autres que la deuxième solution de Mehrdad (qui est fondamentalement la même que MaxBy ):

  • C’est O (n) contrairement à la précédente réponse acceptée qui trouve la valeur maximale à chaque itération (ce qui en fait O (n ^ 2))
  • La solution de commande est O (n log n)
  • Prendre la valeur Max puis trouver le premier élément avec cette valeur est O (n), mais itère deux fois sur la séquence. Dans la mesure du possible, vous devez utiliser LINQ en un seul passage.
  • Il est beaucoup plus simple de lire et de comprendre que la version agrégée et n’évalue la projection qu’une fois par élément

Cela nécessiterait un sorting (O (n log n)) mais est très simple et flexible. Un autre avantage est de pouvoir l’utiliser avec LINQ to SQL:

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

Notez que cela présente l’avantage d’énumérer la séquence une seule fois. Même si cela n’a pas d’importance si list est une List qui ne change pas entre-temps, cela peut être important pour des objects arbitraires IEnumerable . Rien ne garantit que la séquence ne change pas dans les différentes énumérations, de sorte que les méthodes qui le font plusieurs fois peuvent être dangereuses (et inefficaces, selon la nature de la séquence). Cependant, cela rest une solution moins qu’idéale pour les grandes séquences. Je suggère d’écrire votre propre extension MaxObject manuellement si vous avez un grand ensemble d’éléments pour pouvoir le faire en un seul passage sans sorting ni autre chose (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; } } 

et l’utiliser avec:

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

Faire une commande puis sélectionner le premier article perd beaucoup de temps à commander les articles après le premier. Vous ne vous souciez pas de l’ordre de ceux-ci.

Au lieu de cela, vous pouvez utiliser la fonction d’agrégation pour sélectionner le meilleur article en fonction de ce que vous recherchez.

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

Et pourquoi ne pas essayer avec ça ??? :

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

OU plus optimisé:

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

mmm?

Les réponses sont excellentes! Mais je vois un besoin de solution avec les contraintes suivantes:

  1. Plain, concis LINQ;
  2. O (n) complexité;
  3. Ne pas évaluer la propriété plus d’une fois par élément.

C’est ici:

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

Usage:

 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 

Je pense que le sorting par la colonne que vous voulez utiliser pour obtenir le MAX, puis le premier devrait fonctionner. Cependant, s’il existe plusieurs objects avec la même valeur MAX, un seul sera saisi:

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

Dans NHibernate (avec NHibernate.Linq), vous pouvez le faire comme suit:

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

Qui générera SQL comme suit:

 select * from TableName foo where foo.Filter = 'Filter On Ssortingng' and foo.Id = (select cast(max(bar.RowVersion) as INT) from TableName bar where bar.Name = 'Filter On Ssortingng') 

Ce qui me semble plutôt efficace.

Sur la base de la réponse initiale de Cameron, voici ce que je viens d’append à ma version améliorée de FloatingWindowHost de la bibliothèque SilverFlow (copie à partir de FloatingWindowHost.cs sur le code source 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); } 

À noter concernant le code ci-dessus, que Canvas.ZIndex est une propriété attachée disponible pour UIElements dans divers conteneurs, et pas seulement lors de son hébergement dans un canevas (voir Contrôle de rendu (ZOrder) dans Silverlight sans utiliser le contrôle Canvas ). Devinez, on pourrait même faire une méthode d’extension statique SetTopmost et SetBottomMost pour UIElement en adaptant ce code.

Vous pouvez également mettre à niveau la solution de Mehrdad Afshari en réécrivant la méthode d’extention pour la rendre plus rapide (et plus simple):

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

Et puis, utilisez-le simplement:

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

Ce nom sera clair pour les utilisateurs de C ++ (car il y a std :: max_element).