352 votes

Comment exécuter .Max() sur une propriété de tous les objets d'une collection et retourner l'objet ayant la valeur maximale ?

J'ai une liste d'objets qui ont deux propriétés int. La liste est le résultat d'une autre requête linq. L'objet :

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

Je veux trouver et renvoyer l'objet de la liste qui a la plus grande taille. Height valeur de la propriété.

Je parviens à obtenir la valeur la plus élevée de l'élément Height mais pas l'objet lui-même.

Puis-je faire cela avec Linq ? Comment ?

7 votes

Var maxDimension = dimensions.OrderByDesc(x=>x.Height).FirstOrDefault() ;

0 votes

Quelle fonction simple et utile. Une fonction MaxBy devrait faire partie de la bibliothèque standard. Nous devrions faire une demande de fonctionnalité à Microsoft github.com/dotnet/corefx

348voto

Jon Skeet Points 692016

Nous avons un méthode d'extension pour faire exactement cela dans PlusLINQ . Vous pouvez consulter l'implémentation à cet endroit, mais il s'agit essentiellement d'itérer à travers les données, en se souvenant de l'élément maximal que nous avons vu jusqu'à présent et de la valeur maximale qu'il a produite dans le cadre de 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, à l'exception de la deuxième solution de Mehrdad (qui est fondamentalement la même que MaxBy ) :

  • C'est O(n) contrairement à la réponse précédente acceptée qui trouve la valeur maximale à chaque itération (ce qui fait O(n^2))
  • La solution de commande est O(n log n)
  • Prendre le Max et ensuite 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 devriez utiliser LINQ en un seul passage.
  • Elle est beaucoup plus simple à lire et à comprendre que la version agrégée, et n'évalue la projection qu'une fois par élément.

48 votes

Les extensions Linq ont déjà un Max que vous pouvez également utiliser avec une expression lambda. var item = items.Max(x => x.Height);

114 votes

@sgissinger : Vous n'avez pas compris - cela donnera la hauteur maximale dans la collection, pas l'article. avec la hauteur maximale.

27 votes

Pour information, il s'agit de la version agrégée : items.Aggregate((i, j) => i.Height > j.Height ? i : j)

246voto

Mehrdad Afshari Points 204872

Cela nécessiterait une sorte (O(n journal 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 cette méthode présente l'avantage d'énumérer les éléments suivants list séquence juste une fois. Même si cela n'a pas d'importance si list es un List<T> que cela ne change pas entre-temps, cela pourrait avoir de l'importance pour l'arbitraire. IEnumerable<T> objets. Rien ne garantit que la séquence ne change pas dans les différentes énumérations, donc les méthodes qui le font plusieurs fois peuvent être dangereuses (et inefficaces, selon la nature de la séquence). Cependant, c'est toujours une solution moins qu'idéale pour les grandes séquences. Je suggère d'écrire votre propre MaxObject extension manuelle si vous avez un grand ensemble d'éléments pour pouvoir le faire en une seule passe sans tri et autres trucs du genre (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;
    }
}

et l'utiliser avec :

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

2 votes

Rats, je n'ai pas vu ça avant de poster. C'est en gros ce que nous faisons dans MoreLINQ, sauf que nous itérons directement en utilisant GetEnumerator plutôt que d'avoir un drapeau "premier". Je pense que cela rend le code un peu plus simple. Nous autorisons également l'utilisation de Comparer<U> et permettons que l'un d'entre eux soit passé, plutôt que de demander à U d'implémenter IComparable, mais c'est une question secondaire.

1 votes

La dernière partie est beaucoup de complexité inutile pour un problème simple.

6 votes

Il s'agit d'une solution pour un problème plus générique. Fondamentalement, vous déclarez une telle méthode d'extension à s'abstenir la complexité à chaque fois que vous en avez besoin.

175voto

Cameron MacFarland Points 27240

Effectuer un classement puis sélectionner le premier élément, c'est perdre beaucoup de temps à classer les éléments 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 élément 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);

2 votes

Est-ce O(n) alors ? Et comment gère-t-il une liste vide ?

15 votes

Ceci devrait être la réponse acceptée. Toute autre méthode fera itérer les objets plus d'une fois ou utilisera une bibliothèque inutile. Voir aussi : stackoverflow.com/questions/3188693/

0 votes

MaxHeightAndWidth ne fonctionnera pas de manière cohérente en fonction de l'ordre de la liste. Par exemple, si vous avez seulement {1, 3} et {3, 1}, il retournera le premier élément de la liste, quel que soit l'ordre. Elle n'est donc pas vraiment utile.

40voto

Dragouf Points 1663

Et pourquoi tu n'essaies pas avec ça ? :

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

OU plus optimiser :

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

mmm ?

22 votes

Parce que c'est O(n^2). Pour chaque élément de la liste, il parcourt tous les éléments et trouve celui qui a la plus grande hauteur, puis vérifie si l'élément actuel a la même hauteur, et le renvoie. Cela convient pour les petites listes, mais au-delà de 100 éléments, vous remarquerez une différence.

2 votes

Vous avez raison, vous devez donc avant extraire items.Max(y => y.Height) de l'expression lambda et le garder dans une variable. var itemMaxHeight = items.Max(y => y.Height) ; var item = items.Where(x => x.Height == itemMaxHeight) ; Je l'ai écrit rapidement parce que je pensais que cela pouvait être une façon simple de le faire. Mais pour optimiser, c'est mieux de le faire, c'est vrai.

1 votes

La version plus optimisée est maintenant O(2n) ce qui est mieux, mais nécessite maintenant une variable temporaire et 2 lignes, et n'est toujours pas O(n) comme certaines des autres réponses. haussement d'épaules

2voto

regex Points 2247

Je pense que le fait de trier par la colonne dont vous voulez obtenir le MAX et de saisir le premier devrait fonctionner. Cependant, s'il y a plusieurs objets 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<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; }
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X