2010-04-29 11 views
37

Ich habe eine class A { public float Score; ... } und eine IEnumerable<A> items und möchte die A finden, die minimale Punktzahl hat.Wie man linq verwendet, um das Minimum zu finden

Die Verwendung von items.Min(x => x.Score) gibt die minimale Punktzahl und nicht die Instanz mit minimaler Punktzahl.

Wie kann ich die Instanz durch Iterieren nur einmal durch meine Daten bekommen?

bearbeiten: So lange gibt es drei Hauptlösungen:

  • eine Erweiterungsmethode Schreiben (von Svish vorgeschlagen). Pros: Einfach zu bedienen und bewertet Score nur einmal pro Artikel. Nachteile: Benötigt eine Erweiterungsmethode. (Ich wählte diese Lösung für meine Anwendung.)

  • Verwendung von Aggregat (vorgeschlagen von Daniel Renshaw). Vorteile: Verwendet eine integrierte LINQ-Methode. Nachteile: Leicht zum ungeübten Auge verschleiert und ruft Evaluator mehr als einmal.

  • Implementierung IComparable (von Cyberzed vorgeschlagen). Pros: Kann Linq.Min direkt verwenden. Nachteile: Fixed zu einem Vergleicher - kann Comparer nicht frei wählen, wenn die minimale Berechnung durchgeführt wird.

+2

Nicht ganz. Den Index zu kennen hilft nicht direkt für IEnumerable. In einigen Fällen muss man erneut mit dem Index iterieren, um die gewünschte Instanz zu finden. – Danvil

Antwort

14

Werfen Sie einen Blick auf die MinBy Erweiterung Methode in MoreLINQ (erstellt von Jon Skeet, jetzt hauptsächlich von Atif Aziz gepflegt).

+0

Konnte nicht der ganze Körper dieser Methode damit ersetzt werden? return source.Aggregate ((c, d) => comparer.Compare (Selektor (c), Selektor (d)) <0? c: d); –

+0

@Daniel: Sie müssten Jon Skeet fragen: p Einiges davon ist eine Fehlerüberprüfung, die Sie natürlich in einer solchen Bibliothek haben möchten. – Svish

+0

Ich sehe den kleinen Unterschied, dass in MinBy der Selektor nur einmal pro Instanz aufgerufen wird, während mit Aggregate es öfter aufgerufen wird. – Danvil

19

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

+8

Prägnant, aber Vorsicht beim Sortieren; Es ist eine "langsame" Operation. Ich würde Benchmarks empfehlen, wenn die Anzahl der Elemente groß ist, um sicherzustellen, dass die Leistung akzeptabel ist. –

+3

Dies iteriert nicht nur einmal! Es ist O (n log n) und nicht O (n). – Danvil

+0

Überprüfen Sie die Antwort von Svish. Ich wollte gerade eine Erweiterungsmethode schreiben, die genau das getan hat, was Jon Skeet bereits getan hat. –

4

Der schnelle Weg versuchen, wie ich sehen würde es IComparable für Ihre Klasse A (natürlich wenn möglich)

class A : IComparable<A> 

Es ist eine einfache Implementierung, wo Sie die CompareTo(A other) schreiben umsetzen müssen ein Blick auf IEnumerable(of T).Min on MSDN für ein Referenzhandbuch

+0

Dies ist eine gute Idee. Aber was wäre, wenn ich Score1 und Score2 hätte und gerne ändern würde, welche Punktzahl ich für das Minimum verwende? – Danvil

+0

Hmm, es kommt darauf an ... irgendwie würde es mir nichts ausmachen, wenn das Objekt weiß, was seine Vergleichsbasis ausmacht, aber ich kann das Konzept sehen, warum du es andersherum haben möchtest. Ich würde sagen, dass der Jon Skeet Weg wäre offensichtlich mit MinBy – cyberzed

2

Jamie Penney bekam meine Stimme. Sie können auch

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

sagen, die den gleichen Effekt hat. Gebrauch Nehmen Sie (5) nimmt die niedrigste 5 usw.

+3

Sortieren wird nicht nur * einmal * iterieren! – Danvil

+2

RE: "Sortieren iteriert nicht nur einmal!" Das ist wahr. Aber es beantwortet die Frage, die mich auf diese Seite brachte: "Wie man linq benutzt, um das Minimum zu finden." Und, ja, man wird einen Preis bezahlen, wenn man eine große Sammlung von Gegenständen bestellt. – DavidRR

5

Dies kann mit einer kleinen einfachen Iteration gelöst werden:

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

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

return minItem; 

Es ist keine schöne LINQ-Abfrage, aber es einen Sortiervorgang nicht zu vermeiden und nur Iterierten die Liste einmal, je nach den Anforderungen der Frage.

+0

Natürlich. Und ich würde gerne wissen, ob dieses Muster mit linq ausgedrückt werden kann ... – Danvil

+0

Die Antwort ist, keine LINQ bietet keine "MinItem" -Operation. Sie können meine Antwort in eine Erweiterungsmethode verpacken, wenn Sie etwas ebenso Prägnantes haben möchten. Alternativ können Sie es mit LINQ tun, wenn Sie zwei Durchgänge der Liste machen. –

+0

nur ein Hinweis, dass die Zeile minItem = item; sollte sein { minItem = item; minScore = Element.Score; } – mosheb

57

Verwendung Aggregate:

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

Wie vorgeschlagen, genaue gleiche Linie mit freundlichen Namen:

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

Interessante Antwort. Es ist jedoch ziemlich undurchsichtig - Wenn ich nicht wüsste, was es tut, wäre ich ziemlich verwirrt hinsichtlich der Absicht dieses Codes. –

+0

Jamie: Ich stimme zu, es erfüllt die Anforderungen von Danvil, aber es kann schwierig für diejenigen zu parsen sein, die mit funktionalen Sprachen wie Hakell oder F # nicht vertraut sind. –

+1

@Jamie - nicht wenn die Zeile mit 'minItem =' beginnt. Es ist ein bisschen problematisch, weil Sie 'Min' neu implementieren, aber ich denke, das ist ein kleiner Punkt. – Kobi

3

Eine kleine Falte tun soll:

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

Ah ... zu langsam.

Verwandte Themen