Więc prosisz o ArgMin
lub ArgMax
. C # nie ma dla nich wbudowanego API.
Szukałem czystego i wydajnego (O (n) na czas) sposobu, aby to zrobić. I myślę, że znalazłem jeden:
Ogólna forma tego wzoru to:
var min = data.Select(x => (key(x), x)).Min().Item2;
^ ^ ^
the sorting key | take the associated original item
Min by key(.)
Szczególnie, korzystając z przykładu z oryginalnego pytania:
W wersji C # 7.0 i nowszej obsługującej krotkę wartości :
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
W wersji C # wcześniejszej niż 7.0 można użyć zamiast tego typu anonimowego :
var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;
Pracują ponieważ zarówno krotka wartość i typ anonimowy mieć sensowne domyślne porównywarki: dla (x1, y1) i (x2, y2), najpierw porównuje x1
vs x2
, następnie y1
vs y2
. Dlatego wbudowanego .Min
można używać na tych typach.
A ponieważ zarówno anonimowy typ, jak i krotka wartości są typami wartości, oba powinny być bardzo wydajne.
UWAGA
W moich powyższych ArgMin
implementacjach przyjąłem DateOfBirth
typ DateTime
dla uproszczenia i przejrzystości. Pierwotne pytanie dotyczy wykluczenia tych wpisów o pustym DateOfBirth
polu:
Wartości Null DateOfBirth są ustawiane na DateTime.MaxValue, aby wykluczyć je z uwzględnienia wartości minimalnej (zakładając, że co najmniej jedna ma określoną DOB).
Można to osiągnąć dzięki filtrowaniu wstępnemu
people.Where(p => p.DateOfBirth.HasValue)
Jest to więc nieistotne dla kwestii wdrożenia ArgMin
lub ArgMax
.
UWAGA 2
Powyższe podejście ma zastrzeżenie, że jeśli istnieją dwa wystąpienia, które mają tę samą wartość minimalną, wówczas Min()
implementacja spróbuje porównać wystąpienia jako rozstrzygające. Jeśli jednak klasa instancji nie zostanie zaimplementowana IComparable
, zostanie wygenerowany błąd środowiska wykonawczego:
Co najmniej jeden obiekt musi implementować IComparable
Na szczęście można to nadal naprawić dość czysto. Chodzi o to, aby skojarzyć odrębny „identyfikator” z każdym wpisem, który służy jako jednoznaczny element rozstrzygający. Możemy użyć przyrostowego identyfikatora dla każdego wpisu. Nadal wykorzystując wiek osób jako przykład:
var youngest = Enumerable.Range(0, int.MaxValue)
.Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;