Tengo un objeto Person con una propiedad Nullable DateOfBirth. ¿Hay alguna manera de usar LINQ para consultar una lista de objetos Person para el que tiene el valor DateOfBirth más antiguo / más pequeño?
Esto es con lo que comencé:
var firstBornDate = People.Min(p => p.DateOfBirth.GetValueOrDefault(DateTime.MaxValue));
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).
Pero todo lo que hace por mí es establecer firstBornDate en un valor DateTime. Lo que me gustaría obtener es el objeto Persona que coincida con eso. ¿Necesito escribir una segunda consulta así:
var firstBorn = People.Single(p=> (p.DateOfBirth ?? DateTime.MaxValue) == firstBornDate);
¿O hay una forma más ágil de hacerlo?
a.Min(x => x.foo);
max("find a word of maximal length in this sentence".split(), key=len)
devuelve la cadena 'oración'. En C #"find a word of maximal length in this sentence".Split().Max(word => word.Length)
calcula que 8 es la longitud más larga de cualquier palabra, pero no te dice lo que la palabra más larga es .Respuestas:
fuente
curMin == null
?curMin
solo podría sernull
si estuvieras usandoAggregate()
una semilla que esnull
.Desafortunadamente no hay un método incorporado para hacer esto, pero es bastante fácil de implementar por ti mismo. Aquí están las entrañas:
Ejemplo de uso:
Tenga en cuenta que esto arrojará una excepción si la secuencia está vacía, y devolverá el primer elemento con el valor mínimo si hay más de uno.
Alternativamente, puede usar la implementación que tenemos en MoreLINQ , en MinBy.cs . (Hay un correspondiente
MaxBy
, por supuesto).Instalar a través de la consola del administrador de paquetes:
fuente
NOTA: Incluyo esta respuesta para completar ya que el OP no mencionó cuál es la fuente de datos y no debemos hacer suposiciones.
Esta consulta da la respuesta correcta, pero podría ser más lenta ya que podría tener que ordenar todos los elementos
People
, según la estructura de datosPeople
:ACTUALIZACIÓN: En realidad, no debería llamar a esta solución "ingenua", pero el usuario necesita saber contra qué está consultando. La "lentitud" de esta solución depende de los datos subyacentes. Si se trata de una matriz o
List<T>
, entonces LINQ to Objects no tiene más remedio que ordenar la colección completa primero antes de seleccionar el primer elemento. En este caso será más lento que la otra solución sugerida. Sin embargo, si esta es una tabla LINQ to SQL yDateOfBirth
es una columna indexada, SQL Server usará el índice en lugar de ordenar todas las filas. Otros personalizadosIEnumerable<T>
implementaciones también pueden hacer uso de índices (ver i4o: indexado LINQ , o la base de datos de objetos db4o ) y hacer de esta solución más rápida queAggregate()
oMaxBy()
/MinBy()
que necesitan iterar toda la colección una vez. De hecho, LINQ to Objects podría (en teoría) haber hecho casos especialesOrderBy()
para colecciones ordenadas comoSortedList<T>
, pero, por lo que sé, no lo hace.fuente
Haría el truco
fuente
Entonces estás pidiendo
ArgMin
oArgMax
. 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:
Especialmente, usando el ejemplo en la pregunta original:
Para C # 7.0 y superior que admite la tupla de valor :
Para la versión C # anterior a la 7.0, se puede usar el tipo anónimo en su lugar:
Trabajan porque ambos tuple de valor y tipo anónimo tienen comparadores sensibles predeterminados: para (x1, y1) y (x2, y2), primero se compara
x1
frentex2
, entoncesy1
vsy2
. 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 tipoDateTime
de letra por simplicidad y claridad. La pregunta original pide excluir esas entradas conDateOfBirth
campo nulo :Se puede lograr con un prefiltrado
Por lo tanto, no tiene importancia la cuestión de implementar
ArgMin
oArgMax
.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 implementaIComparable
, se generará un error de tiempo de ejecución: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:
fuente
Solución sin paquetes adicionales:
También puedes envolverlo en extensión:
y en este caso:
Por cierto ... O (n ^ 2) no es la mejor solución. Paul Betts dio una solución más rica que la mía. Pero mi sigue siendo la solución LINQ y es más simple y más corta que otras soluciones aquí.
fuente
fuente
Uso perfectamente simple de agregado (equivalente a doblar en otros idiomas):
El único inconveniente es que se accede a la propiedad dos veces por elemento de secuencia, lo que podría ser costoso. Eso es difícil de arreglar.
fuente
La siguiente es la solución más genérica. Básicamente hace lo mismo (en orden O (N)) pero en cualquier tipo IEnumberable y puede mezclarse con tipos cuyos selectores de propiedades podrían devolver nulo.
Pruebas:
fuente
EDITAR de nuevo:
Lo siento. Además de perder el anulable, estaba mirando la función incorrecta,
Min <(Of <(TSource, TResult>)>) (IEnumerable <(Of <(TSource>)>), Func <(Of <(TSource, TResult>)>)) devuelve el tipo de resultado como usted dijo.
Diría que una posible solución es implementar IComparable y usar Min <(Of <(TSource>)>) (IEnumerable <(Of <(TSource>)>)) , que realmente devuelve un elemento de IEnumerable. Por supuesto, eso no te ayuda si no puedes modificar el elemento. Encuentro el diseño de MS un poco extraño aquí.
Por supuesto, siempre puede hacer un bucle for si lo necesita, o usar la implementación de MoreLINQ que dio Jon Skeet.
fuente
Otra implementación, que podría funcionar con teclas de selección anulables, y para la recopilación del tipo de referencia devuelve nulo si no se encuentran elementos adecuados. Esto podría ser útil luego de procesar los resultados de la base de datos, por ejemplo.
Ejemplo:
fuente
Estaba buscando algo similar yo mismo, preferiblemente sin usar una biblioteca o ordenar toda la lista. Mi solución terminó similar a la pregunta en sí, simplemente simplificada un poco.
fuente
var min = People.Min(...); var firstBorn = People.FirstOrDefault(p => p.DateOfBirth == min...
De lo contrario, obtendrá el min repetidamente hasta que encuentre el que está buscando.