Sie fragen also nach ArgMin
o ArgMax
. C# hat keine eingebaute API für diese.
Ich habe nach einem sauberen und effizienten (O(n) in time) Weg gesucht, 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(.)
Vor allem, wenn man das Beispiel aus der ursprünglichen Frage verwendet:
Für C# 7.0 und höher, das Folgendes unterstützt Wertetupel :
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
Für C# Versionen vor 7.0, anonymer Typ kann stattdessen verwendet werden:
var youngest = people.Select(p => new {age = p.DateOfBirth, ppl = p}).Min().ppl;
Sie funktionieren, weil sowohl Wertetupel als auch anonymer Typ sinnvolle Standardkomparatoren haben: Für (x1, y1) und (x2, y2) wird zuerst verglichen x1
gegen x2
entonces y1
gegen y2
. Deshalb ist die eingebaute .Min
kann für diese Typen verwendet werden.
Und da sowohl anonymous type als auch value tuple Werttypen sind, sollten sie beide sehr effizient sein.
ANMERKUNG
In meinem obigen ArgMin
Implementierungen habe ich angenommen DateOfBirth
zum Mitschreiben DateTime
der Einfachheit und Klarheit halber. In der ursprünglichen Frage wird darum gebeten, die Einträge mit Null auszuschließen DateOfBirth
Feld:
NulldateOfBirth-Werte werden auf DateTime.MaxValue gesetzt, um sie von der Min-Betrachtung auszuschließen (vorausgesetzt, mindestens einer hat ein bestimmtes Geburtsdatum).
Dies kann mit einer Vorfilterung erreicht werden
people.Where(p => p.DateOfBirth.HasValue)
Es ist also unerheblich für die Frage der Umsetzung ArgMin
o ArgMax
.
ANMERKUNG 2
Der obige Ansatz hat den Nachteil, dass, wenn es zwei Instanzen gibt, die denselben Min-Wert haben, die Min()
Implementierung wird versuchen, die Instanzen zu vergleichen, um ein Unentschieden zu erreichen. Wenn die Klasse der Instanzen jedoch nicht IComparable
dann wird ein Laufzeitfehler ausgelöst:
Mindestens ein Objekt muss IComparable implementieren
Zum Glück lässt sich das noch recht sauber beheben. Die Idee besteht darin, jedem Eintrag eine entfernte "ID" zuzuordnen, die als eindeutiger Tie-Breaker dient. Wir können eine inkrementelle ID für jeden Eintrag verwenden. Wir verwenden 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;