LINQ: Comment effectuer.Max () sur une propriété de tous les objets d'une collection et retourner l'objet avec une valeur maximale [dupliquer]

cette question a déjà une réponse ici:

  • comment utiliser LINQ pour sélectionner un objet avec une valeur de propriété minimale ou maximale 12 réponses

j'ai une liste d'objets qui ont deux int propriétés. La liste est la sortie 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 retourner l'objet dans la liste qui a la plus grande valeur de propriété Height .

je peux arriver à obtenir la valeur la plus élevée de la valeur Height mais pas l'objet lui-même.

je peux faire ça avec Linq? Comment?

215
demandé sur Thaoden 2009-07-09 08:29:53

9 réponses

nous avons une méthode d'extension pour faire exactement cela dans MoreLINQ . Vous pouvez regarder l'implémentation là-bas, mais en gros, c'est un cas d'itération à travers les données, en se rappelant l'élément maximum que nous avons vu jusqu'à présent et la valeur maximale qu'il a produit sous la projection.

Dans votre cas, vous feriez quelque chose comme:

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

c'est mieux (IMO) que n'importe laquelle des solutions présentées ici, autre que la deuxième solution de Mehrdad (qui est essentiellement la même que MaxBy ):

  • C'est O(n) à la différence de la précédent accepté de répondre qui trouve le maximum de valeur à chaque itération (O(n^2))
  • la solution de commande est O (N log n)
  • en prenant la valeur Max et en trouvant le premier élément avec cette valeur est O(n), mais itère sur la séquence deux fois. Dans la mesure du possible, vous devez utiliser LINQ en un seul passage.
  • c'est beaucoup plus simple à lire et à comprendre que la version agrégée, et n'évalue la projection qu'une fois par élément
217
répondu Jon Skeet 2017-05-23 11:47:28

cela nécessiterait une sorte (O (N log n)) mais est très simple et flexible. Un autre avantage est de pouvoir L'utiliser avec LINQ pour SQL:

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

notez que cela a l'avantage d'énumérer la séquence list une seule fois. Bien qu'il ne soit pas important que list soit un List<T> qui ne change pas entre-temps, il pourrait être important pour les objets arbitraires IEnumerable<T> . Rien ne garantit que la séquence ne changement dans différentes énumérations de sorte que les méthodes qui le font plusieurs fois peut être dangereux (et inefficace, selon la nature de la séquence). Cependant, c'est toujours une solution loin d'être idéale pour les grandes séquences. Je suggère d'écrire votre propre extension MaxObject manuellement si vous avez un grand ensemble d'articles pour pouvoir le faire en un seul passage sans tri et autres choses que ce soit (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);
160
répondu Mehrdad Afshari 2009-07-09 09:21:54

passer une commande et ensuite sélectionner le premier article est perdre beaucoup de temps à commander les articles après le premier. Vous n'avez pas de soins sur l'ordre de ceux-ci.

à la place, vous pouvez utiliser la fonction agrégat pour sélectionner le meilleur article basé sur 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);
113
répondu Cameron MacFarland 2016-08-29 13:36:10

et pourquoi ne pas essayer 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 ?

30
répondu Dragouf 2012-03-26 12:29:38

Les réponses sont très! Mais je vois le besoin d'une solution avec les contraintes suivantes:

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

le voici:

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

Utilisation:

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
répondu meustrus 2015-08-07 15:30:54

je crois que le tri par la colonne vous voulez obtenir le MAX de et puis 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; }
}
4
répondu regex 2009-07-09 04:36:55

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

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

qui va générer SQL comme suit:

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

ce qui me semble assez efficace.

2
répondu Tod Thomson 2013-05-01 02:41:49

basé sur la réponse initiale de Cameron, voici ce que je viens d'ajouter à ma version améliorée de FloatingWindowHost (copie à partir de FloatingWindowHost.cs à http://clipflair.codeplex.com code source)

    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 au-dessus de cette toile.ZIndex est une propriété attachée disponible pour UIElements dans divers conteneurs, non seulement utilisé lors de l'hébergement dans une toile (voir de contrôle commande de rendu (ZOrder) en Silverlight sans utiliser la commande de toile ). Devinez on pourrait même faire un SetTopmost et Setbottomost méthode d'extension statique pour UIElement facilement en adaptant ce code.

1
répondu George Birbilis 2017-05-23 12:02:47

vous pouvez également mettre à jour la solution de Mehrdad Afshari en réécrivant la méthode d'extension à une plus rapide (et plus esthétique):

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

et puis il suffit de l'utiliser:

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

ce nom sera clair pour les personnes utilisant C++ (parce qu'il y a std::max_element là-dedans).

1
répondu Maciej Oziębły 2014-12-27 22:53:05