2012-12-31 23 views
6

Ich habe eine CSV-Datei mit 30 000 Zeilen. Ich muss viele Werte basierend auf vielen Bedingungen auswählen, also habe ich mich für viele Schleifen und "if's" entschieden, linq zu verwenden. Ich habe Klasse geschrieben, um CSV zu lesen. Es implementiert IEnumerable zur Verwendung mit linq. Das ist mein Enumerator:Kann die Suchmethode in LINQ geändert werden?

class CSVEnumerator : IEnumerator 
{ 

    private CSVReader _csv; 

    private int _index; 

    public CSVEnumerator(CSVReader csv) 
    { 
     _csv = csv; 
     _index = -1; 
    } 

    public void Reset(){_index = -1;} 


    public object Current 
    { 
     get 
     { 
      return new CSVRow(_index,_csv); 
     } 
    } 


    public bool MoveNext() 
    { 
     return ++_index < _csv.TotalRows; 
    } 

} 

Es funktioniert, aber es ist langsam. Nehmen wir an, ich möchte den Maximalwert in Spalte A im Bereich 100, 150 Zeilen auswählen.

max = (from CSVRow r in csv where r.ID > 100 && r.ID < 150 select r).Max(y=>y["A"]); 

Dies funktioniert, aber Linq sucht max-Wert in 30 000 Zeilen anstelle von 48. Wie gesagt, ich Schleife verwenden könnte, aber nur in diesem Beispielfall sind die Bedingungen „brutal“ :)

Gibt es eine Möglichkeit, die linq-Sammlungssuche zu überschreiben. Etwas wie: schaue in die Abfrage, die auf meinem Enumerator verwendet wird, schau, ob irgendwelche linq-Bedingungen in "wo" den "Zeilen-ID-Filter" enthalten und gib darauf basierend weitere Daten an.

Ich möchte nicht einen Teil der Daten in ein anderes Array/Sammlung kopieren und Problem ist nicht in meinem CSV-Reader. Der Zugriff auf jede Zeile nach ID ist schnell, das einzige Problem ist, wenn Sie auf alle 30 000 von ihnen zugreifen. Jede Hilfe geschätzt :-)

+1

BTW, sollten Sie 'IEnumerable ' implementieren. – SLaks

+0

Wie ist das implementiert -> 'neue CSVRow (_index, _csv)'. Direktzugriff oder sequenzieller Zugriff? – Tilak

+1

Sind Sie sicher, dass linq to objects Max auf alle Ihre Datensätze und nicht nur auf diese 48 anwendet? Seltsam, sollte es Betreiber nacheinander anwenden. Meine Vermutung ist, dass Ihr Enumerator nur langsam ist. –

Antwort

2

Wenn Sie in der Lage sein möchten, LINQ effizient zu verwenden, müssen Sie expression trees verwenden, in einer ähnlichen (aber viel einfacheren) Weise, als was verschiedene LINQ-Provider für SQL-Datenbanken tun. Obwohl machbar, denke ich, es wäre eine Menge Code für so eine einfache Aufgabe.

Aus diesem Grund denke ich, eine bessere Lösung wäre, eine separate Methode zu verwenden, um die Zeilen auszuwählen, die Sie möchten (und dann möglicherweise LINQ verwenden, um mit dem Ergebnis zu arbeiten).

Außerdem können viele Vorgänge, die Sammlungen zurückgeben (einschließlich Ihres ursprünglichen Codes und meiner Änderung), vereinfacht werden, indem Sie iterator methods verwenden.

So könnte Ihr Code wie folgt aussehen:

public static IEnumerable<CSVRow> GetRows(
    this CSVReader reader, int idGreaterThan, int idLessThan) 
{ 
    for (int i = idGreaterThan + 1; i < idLessThan; i++) 
    { 
     yield return new CSVRow(reader, i); 
    } 
} 

Hier ist es eine Erweiterungsmethode für CSVReader, aber eine andere Lösung (z tatsächliche Methode für diese Klasse) könnte besser geeignet für Sie sein.

Ihr Beispiel würde dann etwas wie folgt aussehen:

max = csvReader.GetRows(100, 150).Max(y => y["A"]); 

(Auch finde ich es seltsam, dass, wenn Sie Grenzen 100 und 150 haben, Sie wollen tatsächlich Zeilen zwischen 101 und 149. Aber ich Sie gehe davon aus habe einen Grund dafür, also habe ich das selbe gemacht.)

+0

Dies ist genau das, was ich brauche, danke :-) 100 und 150 war nur Beispiel, um Problem zu zeigen, wenn ich echte Bedingung post, würde ich erklären müssen, woher Variablen kommen, welche Datentypen sind etc ... –

1

Soweit LINQ betroffen ist, ist r.ID einfach ein Wert, der gefiltert wird und so alle 30k Linien für die Verwendung in der Max-Operation berücksichtigt werden. Wenn dies ein Zeilenindex ist, was hier der Fall zu sein scheint, können Sie Skip and Take verwenden, um zu vermeiden, dass alle 30k-Zeilen verglichen werden.

max = csv.Skip(100).Take(50).Max(y => y["A"]); 
+0

Aber das iteriert immer noch 150 Zeilen, anstatt 50. Wenn also der Bereich 29000-29050 wäre, würden Sie 29050 Zeilen durchlaufen, was sehr ineffektiv ist. – svick

+0

@svik: Woher weiß Ihr CSVReader, in welcher Zeile er ist, wenn Sie die ersten 29000 überspringen? Sie müssen immer noch alle vorlesen, damit Sie die richtigen Zeilen auswählen. Ich denke, die CSVReader-Implementierung ist ineffizient. Es sollte die bereits gelesenen Zeilen zwischenspeichern, dann werden fast alle Abfragen schnell sein. –

+0

@AloisKraus Ich habe keine Ahnung, wie 'CSVReader' implementiert ist, aber die Frage besagt, dass es jede einzelne Zeile effizient abruft. – svick

0

@DougM ist direkt über die Reihenfolge der Bewertung, aber in diesem Fall, was ich tun würde, ist Hit bei der Initialisierung eines einmaligen nehmen und erzeugen Lookups für alle „index“ Felder: Grundsätzlich berechnet pre Karte (Wörterbuch) von Zeilenindex zu Zeile. Dies ist jedoch nur sinnvoll, wenn Sie viele wiederholte Abfragen für ein bestimmtes Indexfeld haben.

Verwandte Themen