Итак, вы запрашиваете ArgMin
или ArgMax
. C # не имеет встроенного API для них.
Я искал чистый и эффективный (O (n) вовремя) способ сделать это. И я думаю, что нашел один:
Общая форма этого шаблона:
var min = data.Select(x => (key(x), x)).Min().Item2;
^ ^ ^
the sorting key | take the associated original item
Min by key(.)
Специально, используя пример из оригинального вопроса:
Для C # 7.0 и выше, который поддерживает значение кортежа :
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
Для версии C # до 7.0 вместо нее можно использовать анонимный тип :
var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;
Они работают, потому что и кортеж значений, и анонимный тип имеют разумные сравнения по умолчанию: для (x1, y1) и (x2, y2) сначала сравниваются x1
против x2
, затем y1
против y2
. Вот почему встроенный .Min
может использоваться на этих типах.
И так как анонимный тип и кортеж значений являются типами значений, они оба должны быть очень эффективными.
ПРИМЕЧАНИЕ
В моих приведенных выше реализациях ArgMin
я предполагал, что DateOfBirth
принимает тип DateTime
для простоты и ясности. Исходный вопрос просит исключить эти записи с нулевым DateOfBirth
полем:
Нулевым значениям DateOfBirth присвоено значение DateTime.MaxValue, чтобы исключить их из минимального рассмотрения (при условии, что хотя бы у одного указан указанный DOB).
Это может быть достигнуто с предварительной фильтрацией
people.Where(p => p.DateOfBirth.HasValue)
Так что вопрос внедрения ArgMin
или ArgMax
.
несущественен.
ПРИМЕЧАНИЕ 2
Приведенный выше подход имеет одну оговорку: когда два экземпляра имеют одинаковое минимальное значение, реализация Min()
попытается сравнить экземпляры как прерыватели связей. Однако, если класс экземпляров не реализует IComparable
, будет выдана ошибка времени выполнения:
Как минимум один объект должен реализовывать IComparable
К счастью, это можно исправить довольно чисто. Идея состоит в том, чтобы связать отдаленный «идентификатор» с каждой записью, которая служит однозначным нарушителем связей. Мы можем использовать инкрементный идентификатор для каждой записи. Все еще используя возраст людей в качестве примера:
var youngest = Enumerable.Range(0, int.MaxValue)
.Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;