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

StackOverflow https://stackoverflow.com/questions/1101841

  •  12-09-2019
  •  | 
  •  

Question

    

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

         

J'ai une liste d'objets qui ont deux propriétés int. 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 la propriété Height.

Je peux gérer pour obtenir la valeur la plus élevée de la valeur Height mais pas l'objet lui-même.

Puis-je faire avec LINQ? Comment?

Était-ce utile?

La solution

Nous avons une méthode d'extension href="https://github.com/morelinq/MoreLINQ/blob/master/MoreLinq/MaxBy.cs" pour faire exactement cela en MoreLINQ . Vous pouvez regarder la mise en œuvre là-bas, mais au fond, ce qui est un cas d'itérer les données, se souvenant de l'élément maximum que nous avons vu jusqu'à présent et la valeur maximale qu'il produit sous la projection.

Dans votre cas, vous feriez quelque chose comme:

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

est meilleure (OMI) que l'une des solutions présentées ici autre que la deuxième solution de Mehrdad (qui est essentiellement le même que MaxBy):

  • Il est O (n) contrairement à la réponse acceptée précédente qui trouve la valeur maximale à chaque itération (ce qui en O ( n ^ 2))
  • La solution de commande est O (n log n)
  • En prenant la valeur de Max, puis de trouver le premier élément avec cette valeur est O (n), mais une itération au cours de la séquence à deux reprises. Lorsque cela est possible, vous devez utiliser LINQ dans un mode de passage unique.
  • Il est beaucoup plus simple à lire et à comprendre que la version globale, et évalue que la projection une fois par élément

Autres conseils

Il faudrait pour cela une sorte (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 a l'avantage de la séquence de l'énumération list juste une fois. Bien qu'il n'importe si list est un List<T> qui ne change pas dans le temps, il pourrait la matière pour les objets IEnumerable<T> arbitraires. Rien ne garantit que la séquence ne change pas dans les différents dénombrements si des méthodes qui le font plusieurs fois peuvent être dangereux (et inefficace, selon la nature de la séquence). Cependant, il est encore moins de solution 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 être en mesure de le faire en une seule passe sans tri et d'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);

Faire une commande, puis en sélectionnant le premier élément gaspille beaucoup de temps de commander les articles après la première. Vous ne se soucient pas de l'ordre de ceux-ci.

vous pouvez plutôt utiliser la fonction d'agrégation pour sélectionner le meilleur élément en fonction de ce que vous cherchez.

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 pas ??? :

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

ou plusieurs optimiser:

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

mmm?

Les réponses à ce jour sont super! Mais je vois un besoin d'une solution avec les contraintes suivantes:

  1. Plaine, LINQ concise;
  2. O (n) complexes;
  3. Ne pas évaluer la propriété plus d'une fois par élément.

Ici, il 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;
}

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

Je crois que le tri par la colonne que vous voulez obtenir le MAX puis saisir le premier devrait fonctionner. Cependant, s'il y a plusieurs objets avec la même valeur MAX, une seule SAISIR:

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

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

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

Ce 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.

Sur la base de la réponse initiale de Cameron, voici ce que je viens d'ajouter à ma version améliorée de FloatingWindowHost (de la copie de la bibliothèque SilverFlow 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);
    }

A noter en ce qui concerne le code ci-dessus que Canvas.ZIndex est une propriété attachée disponible pour UIElements dans divers récipients, non seulement utilisé lorsqu'il est hébergé dans une toile (voir contrôle de l'ordre de rendu (ZOrder) dans Silverlight sans utiliser la toile ). Devinez on pourrait même faire une méthode d'extension statique SetTopmost et SetBottomMost pour UIElement facilement en adaptant ce code.

Vous pouvez également mettre à jour la solution de Mehrdad Afshari en réécrivant la méthode extention plus rapidement (et plus beau) un:

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 juste utiliser:

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

Ce nom sera clair pour les personnes qui utilisent C ++ (car il y a std :: max_element là-bas).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top