it-swarm.com.de

Durchführen von .Max () für eine Eigenschaft aller Objekte in einer Auflistung und Zurückgeben des Objekts mit maximalem Wert

Ich habe eine Liste von Objekten, die zwei int-Eigenschaften haben. Die Liste ist die Ausgabe einer anderen Linq-Abfrage. Das Objekt:

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

Ich möchte das Objekt in der Liste finden und zurückgeben, das den größten Eigenschaftswert Height hat.

Ich kann es schaffen, den höchsten Wert des Height -Werts zu erhalten, aber nicht das Objekt selbst.

Kann ich das mit Linq machen? Wie?

257
theringostarrs

Wir haben eine Erweiterungsmethode , um genau dies in MoreLINQ zu tun. Sie können sich die Implementierung dort ansehen, aber im Grunde geht es darum, die Daten zu durchlaufen und sich an das maximale Element zu erinnern, das wir bisher gesehen haben, und an den maximalen Wert, den es unter der Projektion erzeugt hat.

In Ihrem Fall würden Sie etwas tun wie:

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

Dies ist besser (IMO) als jede der hier vorgestellten Lösungen, abgesehen von der zweiten Lösung von Mehrdad (die im Grunde die gleiche ist wie MaxBy):

  • Es ist O(n) im Gegensatz zu vorherige akzeptierte Antwort , das bei jeder Iteration den Maximalwert findet (macht es zu O (n ^ 2))
  • Die Bestelllösung ist O (n log n)
  • Wenn Sie den Max -Wert nehmen und dann das erste Element mit diesem Wert finden, ist O (n), aber die Sequenz wird zweimal durchlaufen. Wenn möglich, sollten Sie LINQ in einem Durchgang verwenden.
  • Es ist viel einfacher zu lesen und zu verstehen als die aggregierte Version und wertet die Projektion nur einmal pro Element aus
260
Jon Skeet

Dies würde eine Sortierung (O (n log n)) erfordern, ist jedoch sehr einfach und flexibel. Ein weiterer Vorteil ist die Verwendung mit LINQ to SQL:

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

Beachten Sie, dass dies den Vorteil hat, die Sequenz list nur einmal aufzulisten. Während es möglicherweise egal ist, ob list ein List<T> Ist, der sich in der Zwischenzeit nicht ändert, könnte es für beliebige IEnumerable<T> Objekte von Bedeutung sein. Nichts garantiert, dass sich die Sequenz nicht in verschiedenen Aufzählungen ändert, sodass mehrfach ausgeführte Methoden gefährlich (und je nach Art der Sequenz ineffizient) sein können. Es ist jedoch immer noch keine ideale Lösung für große Sequenzen. Ich empfehle Ihnen, Ihre eigene MaxObject -Erweiterung manuell zu schreiben, wenn Sie über eine große Anzahl von Elementen verfügen, um dies in einem Durchgang ohne Sortieren und andere Dinge tun zu können (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;
    }
}

und benutze es mit:

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

Wenn Sie eine Bestellung aufgeben und dann den ersten Artikel auswählen, verschwenden Sie viel Zeit damit, die Artikel nach dem ersten zu bestellen. Die Reihenfolge ist dir egal.

Stattdessen können Sie die Aggregatfunktion verwenden, um den besten Artikel basierend auf Ihrer Suche auszuwählen.

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

Und warum versuchst du es nicht damit ??? :

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

ODER mehr optimieren:

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

mmm?

32
Dragouf

Die bisherigen Antworten sind großartig! Ich sehe jedoch eine Notwendigkeit für eine Lösung mit den folgenden Einschränkungen:

  1. Schlichtes, prägnantes LINQ;
  2. O (n) Komplexität;
  3. Bewerten Sie die Eigenschaft nur einmal pro Element.

Hier ist es:

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

Verwendungszweck:

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
20
meustrus

Ich glaube, dass es funktionieren sollte, nach der Spalte zu sortieren, von der Sie das Maximum erhalten möchten, und dann das erste zu greifen. Wenn es jedoch mehrere Objekte mit demselben MAX-Wert gibt, wird nur eines erfasst:

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

In NHibernate (mit NHibernate.Linq) können Sie dies folgendermaßen tun:

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

Was wird SQL wie folgt erzeugen:

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

Was mir ziemlich effizient erscheint.

2
Tod Thomson

Sie können die Lösung von Mehrdad Afshari auch aktualisieren, indem Sie die Erweiterungsmethode auf eine schnellere (und besser aussehende) Methode umschreiben:

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

Und dann benutze es einfach:

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

Dieser Name ist für Leute, die C++ verwenden, klar (da sich dort std :: max_element befindet).

1

Basierend auf der ersten Antwort von Cameron habe ich in meiner erweiterten Version von FloatingWindowHost der SilverFlow-Bibliothek Folgendes hinzugefügt (Kopieren von FloatingWindowHost.cs unter http: //clipflair.codeplex.com Quellcode)

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

In Bezug auf den obigen Code ist zu beachten, dass Canvas.ZIndex eine angehängte Eigenschaft ist, die für UIElements in verschiedenen Containern verfügbar ist und nicht nur beim Hosten in einem Canvas verwendet wird (siehe Steuern der Renderreihenfolge (ZOrder) in Silverlight ohne Verwendung des Canvas-Steuerelements ). Vermutlich könnte man sogar eine statische Erweiterungsmethode für SetTopmost und SetBottomMost für UIElement erstellen, indem man diesen Code einfach anpasst.

1
George Birbilis