Sie fragen also nach ArgMinoder ArgMax. C # hat keine integrierte API für diese.
Ich habe nach einem sauberen und effizienten (O (n) in der Zeit) Weg gesucht, um dies zu tun. Und ich glaube, ich habe einen gefunden:
Die allgemeine Form dieses Musters ist:
var min = data.Select(x => (key(x), x)).Min().Item2;
^ ^ ^
the sorting key | take the associated original item
Min by key(.)
Insbesondere anhand des Beispiels in der ursprünglichen Frage:
Für C # 7.0 und höher unterstützt das Wertetupel :
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
Für die C # -Version vor 7.0 kann stattdessen ein anonymer Typ verwendet werden:
var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;
Sie funktionieren, weil sowohl Wertetupel als auch anonymer Typ sinnvolle Standardvergleiche haben: Für (x1, y1) und (x2, y2) werden zuerst x1vs x2und dann y1vs verglichen y2. Aus diesem Grund .Minkann das integrierte Gerät für diese Typen verwendet werden.
Und da sowohl anonymer Typ als auch Wertetupel Werttypen sind, sollten beide sehr effizient sein.
HINWEIS
In meinen obigen ArgMinImplementierungen habe ich aus Gründen der Einfachheit und Klarheit angenommen DateOfBirth, dass der Typ verwendet wird DateTime. In der ursprünglichen Frage werden die Einträge mit dem Nullfeld ausgeschlossen DateOfBirth:
Null DateOfBirth-Werte werden auf DateTime.MaxValue gesetzt, um sie aus der Min-Betrachtung auszuschließen (vorausgesetzt, mindestens einer hat ein angegebenes DOB).
Dies kann mit einer Vorfilterung erreicht werden
people.Where(p => p.DateOfBirth.HasValue)
Es ist also unerheblich für die Frage der Implementierung ArgMinoder ArgMax.
ANMERKUNG 2
Der obige Ansatz hat die Einschränkung, dass die Min()Implementierung versucht, die Instanzen als Tie-Breaker zu vergleichen , wenn zwei Instanzen denselben Min-Wert haben . Wenn die Klasse der Instanzen jedoch nicht implementiert wird IComparable, wird ein Laufzeitfehler ausgegeben:
Mindestens ein Objekt muss IComparable implementieren
Zum Glück kann dies noch recht sauber behoben werden. Die Idee ist, jedem Eintrag, der als eindeutiger Krawattenbrecher dient, eine entfernte "ID" zuzuordnen. Wir können für jeden Eintrag eine inkrementelle ID verwenden. Verwenden Sie immer noch das Alter der Menschen als Beispiel:
var youngest = Enumerable.Range(0, int.MaxValue)
.Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;