Sie fragen also nach ArgMin
oder 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 x1
vs x2
und dann y1
vs verglichen y2
. Aus diesem Grund .Min
kann 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 ArgMin
Implementierungen 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 ArgMin
oder 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;