Ich habe ein Personenobjekt mit einer Nullable DateOfBirth-Eigenschaft. Gibt es eine Möglichkeit, mit LINQ eine Liste von Personenobjekten für das Objekt mit dem frühesten / kleinsten DateOfBirth-Wert abzufragen?
Folgendes habe ich begonnen:
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
Null DateOfBirth-Werte werden auf DateTime.MaxValue gesetzt, um sie aus der Min-Betrachtung auszuschließen (vorausgesetzt, mindestens einer hat ein angegebenes DOB).
Für mich bedeutet das jedoch nur, firstBornDate auf einen DateTime-Wert zu setzen. Was ich erhalten möchte, ist das Person-Objekt, das dazu passt. Muss ich eine zweite Abfrage wie folgt schreiben:
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
Oder gibt es einen schlankeren Weg, dies zu tun?
a.Min(x => x.foo);
max("find a word of maximal length in this sentence".split(), key=len)
die Zeichenfolge 'Satz' zurück. In C # wird"find a word of maximal length in this sentence".Split().Max(word => word.Length)
berechnet, dass 8 die längste Länge eines Wortes ist, aber nicht, was das längste Wort ist .Antworten:
quelle
curMin == null
?curMin
könnte nur sein,null
wenn SieAggregate()
mit einem Samen verwenden, der istnull
.Leider gibt es dafür keine integrierte Methode, aber es ist einfach genug, sie selbst zu implementieren. Hier sind die Eingeweide davon:
Anwendungsbeispiel:
Beachten Sie, dass dies eine Ausnahme auslöst, wenn die Sequenz leer ist, und das erste Element mit dem Minimalwert zurückgibt, wenn es mehr als ein Element gibt.
Alternativ können Sie die Implementierung verwenden, die wir in MoreLINQ in MinBy.cs haben . (Es gibt
MaxBy
natürlich eine entsprechende .)Installation über die Paketmanager-Konsole:
quelle
HINWEIS: Der Vollständigkeit halber füge ich diese Antwort hinzu, da das OP die Datenquelle nicht erwähnt hat und wir keine Annahmen treffen sollten.
Diese Abfrage gibt die richtige Antwort, kann jedoch langsamer sein, da je nach Datenstruktur möglicherweise alle Elemente sortiert werden müssen :
People
People
UPDATE: Eigentlich sollte ich diese Lösung nicht "naiv" nennen, aber der Benutzer muss wissen, gegen was er abfragt. Die "Langsamkeit" dieser Lösung hängt von den zugrunde liegenden Daten ab. Wenn dies ein Array oder ist
List<T>
, hat LINQ to Objects keine andere Wahl, als zuerst die gesamte Sammlung zu sortieren, bevor das erste Element ausgewählt wird. In diesem Fall ist es langsamer als die andere vorgeschlagene Lösung. Wenn es sich jedoch um eine LINQ to SQL-Tabelle undDateOfBirth
eine indizierte Spalte handelt, verwendet SQL Server den Index, anstatt alle Zeilen zu sortieren. Andere benutzerdefinierteIEnumerable<T>
Implementierungen könnten ebenfalls Indizes verwenden (siehe i4o: Indexed LINQ oder die Objektdatenbank db4o ) und diese Lösung schneller alsAggregate()
oderMaxBy()
/ machenMinBy()
die die gesamte Sammlung einmal iterieren müssen. Tatsächlich hätte LINQ to Objects (theoretisch) SonderfälleOrderBy()
für sortierte Sammlungen wie machen könnenSortedList<T>
, aber meines Wissens nicht.quelle
Würde den Trick machen
quelle
Sie fragen also nach
ArgMin
oderArgMax
. 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:
Insbesondere anhand des Beispiels in der ursprünglichen Frage:
Für C # 7.0 und höher unterstützt das Wertetupel :
Für die C # -Version vor 7.0 kann stattdessen ein anonymer Typ verwendet werden:
Sie funktionieren, weil sowohl Wertetupel als auch anonymer Typ sinnvolle Standardvergleiche haben: Für (x1, y1) und (x2, y2) werden zuerst
x1
vsx2
und danny1
vs verglicheny2
. 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 angenommenDateOfBirth
, dass der Typ verwendet wirdDateTime
. In der ursprünglichen Frage werden die Einträge mit dem Nullfeld ausgeschlossenDateOfBirth
:Dies kann mit einer Vorfilterung erreicht werden
Es ist also unerheblich für die Frage der Implementierung
ArgMin
oderArgMax
.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 wirdIComparable
, wird ein Laufzeitfehler ausgegeben: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:
quelle
Lösung ohne zusätzliche Pakete:
Sie können es auch in eine Erweiterung einwickeln:
und in diesem Fall:
Übrigens ... O (n ^ 2) ist nicht die beste Lösung. Paul Betts gab Fatster-Lösung als meine. Aber meine Lösung ist immer noch LINQ und sie ist einfacher und kürzer als andere Lösungen hier.
quelle
quelle
Perfekt einfache Verwendung des Aggregats (entspricht dem Falten in anderen Sprachen):
Der einzige Nachteil ist, dass zweimal pro Sequenzelement auf die Eigenschaft zugegriffen wird, was teuer sein kann. Das ist schwer zu beheben.
quelle
Das Folgende ist die allgemeinere Lösung. Es macht im Wesentlichen dasselbe (in O (N) Reihenfolge), jedoch bei allen IEnumberable-Typen und kann mit Typen gemischt werden, deren Eigenschaftsauswahl null zurückgeben könnte.
Tests:
quelle
Nochmals BEARBEITEN:
Es tut uns leid. Abgesehen davon, dass ich das Nullable vermisst habe, habe ich mir die falsche Funktion angesehen.
Min <(Of <(TSource, TResult>)>) (IEnumerable <(Of <(TSource>)>), Func <(Of <(TSource, TResult>)>)) gibt den Ergebnistyp wie gesagt zurück.
Ich würde sagen, eine mögliche Lösung besteht darin, IComparable zu implementieren und Min <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>)) zu verwenden , das tatsächlich ein Element aus IEnumerable zurückgibt. Das hilft Ihnen natürlich nicht, wenn Sie das Element nicht ändern können. Ich finde das Design von MS hier etwas seltsam.
Natürlich können Sie bei Bedarf jederzeit eine for-Schleife ausführen oder die von Jon Skeet bereitgestellte MoreLINQ-Implementierung verwenden.
quelle
Eine andere Implementierung, die mit nullbaren Auswahlschlüsseln und für die Sammlung von Referenztypen arbeiten könnte, gibt null zurück, wenn keine geeigneten Elemente gefunden wurden. Dies kann beispielsweise bei der Verarbeitung von Datenbankergebnissen hilfreich sein.
Beispiel:
quelle
Ich habe selbst nach etwas Ähnlichem gesucht, vorzugsweise ohne eine Bibliothek zu benutzen oder die gesamte Liste zu sortieren. Meine Lösung endete ähnlich wie die Frage selbst, nur ein wenig vereinfacht.
quelle
var min = People.Min(...); var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == min...
Andernfalls wird die min wiederholt, bis die gesuchte gefunden wird.