2010-04-29 4 views
37

J'ai un class A { public float Score; ... } et un IEnumerable<A> items et je voudrais trouver le A qui a un score minimal.Comment utiliser linq pour trouver le minimum

L'utilisation de items.Min(x => x.Score) donne le score minimal et non l'instance avec un score minimal.

Comment puis-je obtenir l'instance en itérant seulement une fois à travers mes données?

Modifier: Tant il y a trois solutions principales:

  • Rédaction d'une méthode d'extension (proposée par Svish). Avantages: Facile à utiliser et évalue Score une seule fois par article. Contre: Nécessite une méthode d'extension. (J'ai choisi cette solution pour mon application.)

  • Utilisation de l'agrégat (proposé par Daniel Renshaw). Avantages: Utilise une méthode LINQ intégrée. Contre: Légèrement obfusqué à l'œil non formé et appelle l'évaluateur plus d'une fois.

  • Implémentation d'IComparable (proposée par cyberzed). Avantages: Peut utiliser Linq.Min directement. Contre: Fixé à un comparateur - impossible de choisir librement le comparateur lors de l'exécution du calcul minimum.

+2

Pas tout à fait. Connaître l'index n'aide pas directement pour IEnumerable. Dans certains cas, il faut recommencer en utilisant l'index pour trouver l'instance désirée. – Danvil

Répondre

14

Jetez un oeil à la méthode d'extension MinBy en MoreLINQ (créée par Jon Skeet, maintenant principalement maintenu par Atif Aziz).

+0

L'ensemble du corps de cette méthode ne pourrait-il pas être remplacé par ceci? return source.Aggregate ((c, d) => comparaison.Compare (sélecteur (c), sélecteur (d)) <0? c: d); –

+0

@ Daniel: Il faudrait demander à Jon Skeet à ce sujet: p Certaines d'entre elles est la vérification des erreurs cependant, que bien sûr vous voulez avoir dans une bibliothèque comme ça. – Svish

+0

Je vois la légère différence, que dans MinBy le sélecteur est appelé seulement une fois par instance, alors qu'avec Aggregate il est appelé plus souvent. – Danvil

19

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

+8

Succinct, mais attention au tri; c'est une opération "lente". Je recommanderais l'analyse comparative si le nombre d'éléments est important pour s'assurer que la performance est acceptable. –

+3

Cela ne pas itérer une seule fois! C'est O (n log n) et non O (n). – Danvil

+0

Vérifiez la réponse de Svish. J'étais sur le point d'écrire une méthode d'extension qui a fait exactement ce que Jon Skeet a déjà fait. –

4

La manière rapide que je le vois mettrait en œuvre IComparable pour votre classe A (si possible ofcourse)

class A : IComparable<A> 

Il est une implémentation simple où vous écrivez le CompareTo(A other) ont un regard sur IEnumerable(of T).Min on MSDN pour un guide de référence

+0

C'est une bonne idée. Mais que se passerait-il si j'avais Score1 et Score2 et que je voudrais changer le score que j'utilise pour trouver le minimum? – Danvil

+0

Hmm bien ça dépend ... Je ne me dérangerait pas d'avoir en quelque sorte l'objet de savoir ce qui la définit sa base de comparaison, mais je peux voir le concept de la raison pour laquelle vous voudriez l'inverse. Je dirais que la manière de Jon Skeet serait évidente avec MinBy – cyberzed

2

Jamie Penney a obtenu mon vote. Vous pouvez également dire

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

Qui a le même effet. Utilisez Take (5) pour prendre le plus bas 5 etc.

+3

Le tri ne se répète pas seulement * une fois *! – Danvil

+2

RE: "Le tri n'interrompt pas une seule fois!" C'est vrai. Mais cela répond à la question qui m'a amené à cette page: "Comment utiliser linq pour trouver le minimum." Et, oui, on paiera un prix en commandant une grande collection d'articles. – DavidRR

5

Ceci peut être résolu avec une petite itération simple:

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

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

return minItem; 

Ce n'est pas une belle requête LINQ, mais il n'évite une opération de tri et seulement itère la liste une fois, selon les exigences de la question.

+0

Bien sûr. Et je voudrais savoir si ce modèle peut être exprimé avec linq ... – Danvil

+0

La réponse est, pas LINQ n'offre pas une opération 'MinItem'. Vous pouvez envelopper ma réponse dans une méthode d'extension si vous souhaitez avoir quelque chose de tout aussi succinct. Alternativement, vous pouvez le faire avec LINQ si vous faites deux passages de la liste. –

+0

juste une note que la ligne minItem = item; devrait être { minItem = item; minScore = item.Score; } – mosheb

57

utilisation globale:

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

Comme suggéré, exacte même ligne avec des noms plus sympathiques:

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

de réponse intéressante. C'est assez opaque cependant - Si je ne savais pas déjà ce qu'il faisait, je serais assez confus quant à l'intention de ce code. –

+0

Jamie: Je suis d'accord, il répond aux exigences de Danvil mais il peut être difficile d'analyser pour ceux qui ne sont pas familiers avec les langages fonctionnels comme Hakell ou F #. –

+1

@Jamie - pas quand la ligne commence par 'minItem ='. C'est un peu problématique parce que vous ré-implémentez 'Min', mais je pense que c'est un point mineur. – Kobi

3

Un petit pli doit faire:

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

Ah ... trop lent.

Questions connexes