Итак, вы просите 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;