2010-04-29 13 views
37

Tengo un class A { public float Score; ... } y un IEnumerable<A> items y me gustaría encontrar el A que tiene puntaje mínimo.Cómo utilizar linq para encontrar el mínimo

El uso de items.Min(x => x.Score) da la puntuación mínima y no la instancia con puntaje mínimo.

¿Cómo puedo obtener la instancia iterando solo una vez a través de mis datos?

Editar: Siempre hay tres soluciones principales:

  • escribir un método de extensión (propuesto por Svish). Pros: Fácil de usar y evalúa el puntaje solo una vez por artículo. Contras: Necesita un método de extensión. (Escogí esta solución para mi aplicación).

  • Usando Agregado (propuesto por Daniel Renshaw). Pros: utiliza un método integrado LINQ. Contras: levemente ofuscado para el ojo no entrenado y llama al evaluador más de una vez.

  • Implementando IComparable (propuesto por cyberzed). Pros: Puede usar Linq.Min directamente. Contras: Corregido en un comparador: no se puede elegir libremente el comparador cuando se realiza el cálculo mínimo.

+2

No del todo. Saber el índice no ayuda directamente con IEnumerable. En algunos casos, uno debe iterar nuevamente usando el índice para encontrar la instancia deseada. – Danvil

Respuesta

14

Eche un vistazo al método de extensión MinBy en MoreLINQ (creado por Jon Skeet, ahora mantenido principalmente por Atif Aziz).

+0

¿No se pudo reemplazar todo el cuerpo de ese método con esto? return source.Aggregate ((c, d) => comparer.Compare (selector (c), selector (d)) <0? c: d); –

+0

@Daniel: Tendría que preguntarle a Jon Skeet sobre eso: p Algunos de ellos es un error de verificación, que por supuesto desea tener en una biblioteca como esa. – Svish

+0

Veo la pequeña diferencia, que en MinBy el selector se llama solo una vez por instancia, mientras que con Agregado se llama con más frecuencia. – Danvil

19

Trate items.OrderBy(s => s.Score).FirstOrDefault();

+8

Sucinto, pero tenga cuidado de ordenar; es una operación "lenta". Recomendaría la evaluación comparativa si la cantidad de elementos es grande para garantizar que el rendimiento sea aceptable. –

+3

¡Esto no se repite solo una vez! Es O (n log n) y no O (n). – Danvil

+0

Comprueba la respuesta de Svish. Estaba a punto de escribir un método de extensión que hizo exactamente lo que Jon Skeet ya hizo. –

4

La forma más rápida tal como la veo se implementa IComparable para su clase A (si es posible, por supuesto)

class A : IComparable<A> 

Es una aplicación simple en el que escribir el CompareTo(A other) tienen un vistazo a IEnumerable(of T).Min on MSDN para una guía de referencia

+0

Esta es una buena idea. Pero, ¿y si tuviera Score1 y Score2 y me gustaría cambiar el puntaje que uso para encontrar el mínimo? – Danvil

+0

Hmm bueno, depende ... de alguna manera no me importaría que el objeto supiera qué define su base de comparación, pero puedo ver el concepto de por qué le gustaría al revés. Diría que el modo de Jon Skeet sería obvio con MinBy – cyberzed

2

Jamie Penney obtuvo mi voto. También puede decir

items.OrderBy(s => s.Score).Take(1); 

Lo cual tiene el mismo efecto. USA Tomar (5) para tomar la más baja 5, etc.

+3

¡La ordenación no itera solo * una vez *! – Danvil

+2

RE: "¡La ordenación no se repite una sola vez!" Eso es verdad. Pero responde la pregunta que me trajo a esta página: "Cómo usar linq para encontrar el mínimo". Y, sí, uno pagará un precio cuando ordene una gran colección de artículos. – DavidRR

5

Esto se puede solucionar con un poco de iteración simple:

float minScore = float.MaxValue; 
A minItem = null; 

foreach(A item in items) 
{ 
    if(item.Score < minScore) 
     minItem = item; 
} 

return minItem; 

No es una consulta LINQ agradable, pero sí evitar una operación de clasificación y sólo itera la lista una vez, según los requisitos de la pregunta.

+0

Por supuesto. Y me gustaría saber si este patrón se puede expresar con linq ... – Danvil

+0

La respuesta es que ningún LINQ no ofrece una operación 'MinItem'. Puede envolver mi respuesta en un método de extensión si desea tener algo igualmente breve. Alternativamente, puede hacerlo con LINQ si realiza dos pasadas de la lista. –

+0

solo una nota de que la línea minItem = artículo; debe ser { minItem = item; minScore = item.Score; } – mosheb

57

uso agregado:

items.Aggregate((c, d) => c.Score < d.Score ? c : d) 

Como se sugirió, exacta misma línea con los nombres más amigables:

items.Aggregate((minItem, nextItem) => minItem.Score < nextItem.Score ? minItem : nextItem) 
+3

Interesante respuesta. Sin embargo, es bastante opaco: si no supiera lo que estaba haciendo, estaría bastante confundido en cuanto a la intención de ese código. –

+0

Jamie: Estoy de acuerdo, cumple con los requisitos de Danvil, pero puede ser difícil de analizar para aquellos que no están familiarizados con los lenguajes funcionales como Hakell o F #. –

+1

@Jamie - no cuando la línea comienza con 'minItem ='. Es un poco problemático porque vuelves a implementar 'Min', pero creo que es un punto menor. – Kobi

3

Un pequeño pliegue debe hacer:

var minItem = items.Aggregate((acc, c) => acc.Score < c.Score? acc : c); 

Ah ... demasiado lento.

Cuestiones relacionadas