Entonces estás pidiendo ArgMin
o ArgMax
. C # no tiene una API integrada para esos.
He estado buscando una manera limpia y eficiente (O (n) a tiempo) de hacer esto. Y creo que encontré uno:
La forma general de este patrón es:
var min = data.Select(x => (key(x), x)).Min().Item2;
^ ^ ^
the sorting key | take the associated original item
Min by key(.)
Especialmente, usando el ejemplo en la pregunta original:
Para C # 7.0 y superior que admite la tupla de valor :
var youngest = people.Select(p => (p.DateOfBirth, p)).Min().Item2;
Para la versión C # anterior a la 7.0, se puede usar el tipo anónimo en su lugar:
var youngest = people.Select(p => new { ppl = p; age = p.DateOfBirth }).Min().ppl;
Trabajan porque ambos tuple de valor y tipo anónimo tienen comparadores sensibles predeterminados: para (x1, y1) y (x2, y2), primero se compara x1
frente x2
, entonces y1
vs y2
. Es por eso que el incorporado .Min
se puede usar en esos tipos.
Y dado que tanto el tipo anónimo como la tupla de valor son tipos de valor, ambos deberían ser muy eficientes.
NOTA
En mis ArgMin
implementaciones anteriores , asumí DateOfBirth
que tomaba el tipo DateTime
de letra por simplicidad y claridad. La pregunta original pide excluir esas entradas con DateOfBirth
campo nulo :
Los valores nulos de DateOfBirth se establecen en DateTime.MaxValue para descartarlos de la consideración mínima (suponiendo que al menos uno tenga un DOB especificado).
Se puede lograr con un prefiltrado
people.Where(p => p.DateOfBirth.HasValue)
Por lo tanto, no tiene importancia la cuestión de implementar ArgMin
o ArgMax
.
NOTA 2
El enfoque anterior tiene la advertencia de que cuando hay dos instancias que tienen el mismo valor mínimo, la Min()
implementación intentará comparar las instancias como un desempate. Sin embargo, si la clase de las instancias no se implementa IComparable
, se generará un error de tiempo de ejecución:
Al menos un objeto debe implementar IComparable
Afortunadamente, esto todavía se puede arreglar de manera bastante limpia. La idea es asociar una "ID" distante con cada entrada que sirva como un desempate inequívoco. Podemos usar una identificación incremental para cada entrada. Todavía usando la edad de las personas como ejemplo:
var youngest = Enumerable.Range(0, int.MaxValue)
.Zip(people, (idx, ppl) => (ppl.DateOfBirth, idx, ppl)).Min().Item3;