Entwickler-Ecke
Sonstiges (Delphi) - Viele if-Anweisungen kürzen -> Case-Anweisung (wie richtig?)
probare - Mi 04.12.13 15:38
Titel: Viele if-Anweisungen kürzen -> Case-Anweisung (wie richtig?)
Hi,
ich lese eine Textdatei in eine Listbox (ListBoxRead) ein.
Bei einem Klick auf einen Button (btnRead) möchte ich nun jede einzelne Zeile der Liste auf einen bestimmten String prüfen und speziell behandeln.
Folgend erst einmal ein Auszug aus dem Quellcode -
wie er bisher war:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| procedure TfrmMain.btnReadClick(Sender: TObject);
var i : Integer; ...
begin ... for i := 0 to ListBoxRead.Items.Count -1 do begin
if ListBoxRead.Items[i] := '## 123' begin ... end;
if ListBoxRead.Items[i] := '## 234' begin ... end; ... end; end; |
Das funktioniert für kleine Textdateien zwar wunderbar, aber für größere ist der Rechenaufwand aufgrund der ganzen if-Anweisungen (sind natürlich nicht nur 2, sondern in diesem Fall eine ganze Menge) ziemlich hoch.
Verbesserung des Rechenaufwands kann hier die case-Anweisung schaffen, wenn ich mich richtig informiert habe.
Leider bekomme ich das in diesem Fall nicht so ganz auf die Reihe und dementsprechend Fehlermeldungen.
Eventuell kann mir hier ja jemand helfen.
Ich poste kurz meine Gehversuche:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14:
| for i := 0 to ListBoxRead.Items.Count -1 do begin ... case (ListBoxRead.Items[i]) of '## 123' : begin ... end;
'## 234' : begin ... end; end; |
Wie funktioniert es richtig?
Moderiert von
Martok: Quote- durch Delphi-Tags ersetzt
baumina - Mi 04.12.13 15:54
Case geht mit Strings nicht. Aber wenn du innerhalb deiner if's ein continue einbaust, muss er sobald er das richtige if gefunden hat nicht alle anderen if#s noch prüfen, sondern springt in deiner for-Schleife gleich weiter.
Delphi-Quelltext
1: 2: 3: 4: 5:
| if ListBoxRead.Items[i] := '## 123' begin ... continue; end; |
probare - Mi 04.12.13 15:57
baumina hat folgendes geschrieben : |
Case geht mit Strings nicht. Aber wenn du innerhalb deiner if's ein continue einbaust, muss er sobald er das richtige if gefunden hat nicht alle anderen if#s noch prüfen, sondern springt in deiner for-Schleife gleich weiter.
Delphi-Quelltext 1: 2: 3: 4: 5:
| if ListBoxRead.Items[i] := '## 123' begin ... continue; end; | |
Herzlichen Dank, hilft mir schonmal sehr.
Delete - Mi 04.12.13 17:26
... und wenn in deinen Strings ausschließlich "Zahlenwerte" stehen, wandelst du die Strings in einen Integer um, was dich in die Lage versetzt, doch noch einen Case-Block einzusetzen.
WasWeißDennIch - Mi 04.12.13 17:33
Mit AnsiIndexText ist man ja auch in der Lage, case zu verwenden:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27:
| uses StrUtils;
const POSSIBLEVALUES: array[0..4] of string = ( '## 123', '## 234', '## 345', '## 456', '## 567');
procedure TFormTest.ButtonTestClick(Sender: TObject); begin case AnsiIndexText('## 345', POSSIBLEVALUES) of -1: ShowMessage('Nicht enthalten'); 0: ShowMessage('Erster'); 1: ShowMessage('Zweiter'); 2: ShowMessage('Dritter'); 3: ShowMessage('Vierter'); 4: ShowMessage('Fünfter'); end; end; |
probare - Mi 04.12.13 17:42
Perlsau hat folgendes geschrieben : |
... und wenn in deinen Strings ausschließlich "Zahlenwerte" stehen, wandelst du die Strings in einen Integer um, was dich in die Lage versetzt, doch noch einen Case-Block einzusetzen. |
Stehen leider nicht nur Zahlenwerte drin.
Der jeweils 3. String ist ein Buchstabe, der jeweils 5. ist eine Zahl und das auch nur Blöckeweise - also Block wird mit '##X 123' geöffnet und mit '##Y 123' geschlossen.
Habe das Ganze jetzt erst einmal so gelöst, dass ich per if-Abfrage das 1. und das 3. Zeichen auslese, damit ich nur mit geöffneten Blöcken arbeite (stoppt sobald '##Y' ) auftaucht.
In der Bearbeitung verwende ich eine weitere Variable, so dass ich die Blockeinträge auslesen kann.
Ist der Block beendet, bin ich wieder in der for-Schleife.
Da ich ja nun weiß, dass die 2. Zeile für mich vermutlich uninteressant ist, arbeite ich mit einer 3. Variablen, die den Lesezähler künstlich hochschraubt.
Somit wird nicht jede Zeile der Textdatei durch alle if-Abfragen gejagd, sondern nur Blockanfang + Blockende, was die Rechenzeit wesentlich verbessert hat.
Das das hier sich natürlich etwas unverständlich liest, das Ganze einmal als Quellcode:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| ... s := 0; for i := 0 to ListBoxRead.Items.Count -1 do begin k := i+s; if k >= ListBoxRead.Items.Count then break; if ListBoxRead.Items[k][1] = '#' then if ListBoxRead.Items[k][3] = 'H' then begin s := 0; if ListBoxRead.Items[k] ='##X 2' then begin while ListBoxRead.Items[k+s] <> '##Y 2' do begin StringList2.Add(ListBoxRead.Items[k+s]); s := s+1; end; StringList2.Add('##Y 2'); continue; end; ... end; ... |
@WasWeißDennIch: Danke, werde mich damit mal näher beschäftigen müssen.
jaenicke - Mi 04.12.13 18:17
Eine TStringList statt einer Listbox kann auch noch ein wenig mehr Geschwindigkeit bringen. Eine Listbox ist nur zur Anzeige da, nicht zur Verarbeitung. Du kannst es dem Benutzer natürlich dort zusätzlich anzeigen, aber verarbeiten würde ich es in einer TStringList.
Dazu kommt dann das Einlesen, das geht mit einer MMF sehr schnell, wenn die Datei größer ist.
Zum Vergleich:
Ich habe eine 350 MiB große Registrydatei in wenigen Sekunden komplett geparst und in einem Baum angezeigt. Und da muss ich jeden Eintrag entsprechend zerlegen und prüfen...
Xion - Mi 04.12.13 19:00
Statt continue würde es wesentlich naheliegender auch ein else tun:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| if ... then begin end else if ... then begin end else if ... |
Wenn du die Zweige dann noch nach "Häufigkeit" sortierst, dann bringt das auch nochmal was.
Für eine etwas ausgefeiltere Lösung wäre wohl eine Hashtabelle/-funktion geeignet. Du übersetzt deinen String in einen (weitestgehend eindeutigen) Integer und benutzt den dann für case (oder als Funktionspointer...). Das wird wohl die effizienteste Möglichkeit sein.
//Edit: ich nehme an, dass genau dies durch AnsiIndexText, wie oben erwähnt, realisiert wird
Tranx - Mi 04.12.13 19:20
Wenn Deine Strings in einer Stringlist stehen, kannst Du auch folgendes abfragen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| var StrgLst : TStringList; s : string; begin StrgLst := TStringList.Create; s := '## 123'; n = StrgLst.IndexOf(s); case n of
0 : ..; 1 : ..; else ..; end; end; |
probare - Mi 04.12.13 20:41
Tranx hat folgendes geschrieben : |
Wenn Deine Strings in einer Stringlist stehen, kannst Du auch folgendes abfragen:
Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23:
| var StrgLst : TStringList; s : string; begin StrgLst := TStringList.Create; s := '## 123'; n = StrgLst.IndexOf(s); case n of
0 : ..; 1 : ..; else ..; end; end; | |
Das habe ich auch schon in Betracht gezogen. Da s aber ~600 verschiedene Varianten annehmen kann, hätte es denselben Effekt einer if-Abfrage, wenn ich mich nicht irre.
jaenicke hat folgendes geschrieben : |
Eine TStringList statt einer Listbox kann auch noch ein wenig mehr Geschwindigkeit bringen. Eine Listbox ist nur zur Anzeige da, nicht zur Verarbeitung. Du kannst es dem Benutzer natürlich dort zusätzlich anzeigen, aber verarbeiten würde ich es in einer TStringList.
Dazu kommt dann das Einlesen, das geht mit einer MMF sehr schnell, wenn die Datei größer ist.
Zum Vergleich:
Ich habe eine 350 MiB große Registrydatei in wenigen Sekunden komplett geparst und in einem Baum angezeigt. Und da muss ich jeden Eintrag entsprechend zerlegen und prüfen... |
Im Moment speicher ich jeden Block in seine zugehörige TStringList (es gibt hier ca 600 verschiedene), um diese einerseits zu sortieren, andererseits nur eine Ausgabe brauche.
Das Programm braucht zur Zeit ca 12 Sekunden für das Sortieren einer 1,6 mb großen Datei. Dabei ist die Prozessorauslastung recht hoch, was ich zu Minimieren versuche, soweit es denn geht.
OlafSt - Mi 04.12.13 23:29
Die Prozessorauslastung kann egal sein - solange sie angenehm kurz ist ;)
Wie wäre es mit einem anderen Ansatz ?
Zuerst: Keine Listbox nehmen, die sind dafür nicht gedacht, der Overhead für die ganze visuelle Aufbereitung ist gewaltig. Eine TStringList ist wirklich besser geeignet, zur Visualisierung reicht ein Progressbar.
Als nächstes würde ich die Datei gar nicht mit LoadFromFile in die TSL einsaugen. Diese Art Listen haben das Problem, das sie mit wachsender Zeilenzahl immer langsamer werden, bis es irgendwann eine Minute dauert, um eine Zeile anzuhängen... Stattdessen würde ich die Datei zeilenweise lesen und dort schon mal schauen, was überhaupt relevant ist. Das Relevante geht in die StringList, die dann genauer untersucht wird. Beim Einfügen in die StringList kann man sich dann überlegen, ob man wirklich "###X 123" einfügen muß - oder man das gleich übersetzt in "04123", das "##Y 123" in "05123" etcpp... Die restlichen Ideen zur Verarbeitung überlasse ich Dir ;)
GuaAck - Mi 04.12.13 23:34
Hallo Jaenicke,
das MMF liest sich gut, aber was bedeutet MMF? Weder in den Hilfen für Delphi und Windows noch mit Google habe ich was sinnvolles finden können.
Gruß
GuaAck
probare - Mi 04.12.13 23:56
OlafSt hat folgendes geschrieben : |
Die Prozessorauslastung kann egal sein - solange sie angenehm kurz ist ;)
Wie wäre es mit einem anderen Ansatz ?
Zuerst: Keine Listbox nehmen, die sind dafür nicht gedacht, der Overhead für die ganze visuelle Aufbereitung ist gewaltig. Eine TStringList ist wirklich besser geeignet, zur Visualisierung reicht ein Progressbar.
Als nächstes würde ich die Datei gar nicht mit LoadFromFile in die TSL einsaugen. Diese Art Listen haben das Problem, das sie mit wachsender Zeilenzahl immer langsamer werden, bis es irgendwann eine Minute dauert, um eine Zeile anzuhängen... Stattdessen würde ich die Datei zeilenweise lesen und dort schon mal schauen, was überhaupt relevant ist. Das Relevante geht in die StringList, die dann genauer untersucht wird. Beim Einfügen in die StringList kann man sich dann überlegen, ob man wirklich "###X 123" einfügen muß - oder man das gleich übersetzt in "04123", das "##Y 123" in "05123" etcpp... Die restlichen Ideen zur Verarbeitung überlasse ich Dir ;) |
Der Ansatz gefällt mir.
Werde mir morgen mal näher dazu Gedanken machen.
mandras - Do 05.12.13 12:35
Du kannst für das Suchen eine StringList mit sorted=true einsetzen,
dann erfolgt bei indexof oder find eine binäre suche die recht flott ist.
probare - Do 05.12.13 23:21
Noch nicht, sind auch schon eine ganze Menge Ideen und Ansätze auf einmal, was ich so auf Anhieb nicht erwartet hätte. Werde am Wochenende mal einige Varianten ausprobieren und testen.
Herzlichen Dank
probare - Fr 06.12.13 14:22
mandras hat folgendes geschrieben : |
Du kannst für das Suchen eine StringList mit sorted=true einsetzen,
dann erfolgt bei indexof oder find eine binäre suche die recht flott ist. |
wie gesagt, stehen in der textdatei blöckeweise informationen.
setze ich nun sorted = true, werden die blöcke durcheinander geworfen.
trotzdem danke, die information könnte mir in anderen projekten evtll noch behilflich sein.
Entwickler-Ecke.de based on phpBB
Copyright 2002 - 2011 by Tino Teuber, Copyright 2011 - 2025 by Christian Stelzmann Alle Rechte vorbehalten.
Alle Beiträge stammen von dritten Personen und dürfen geltendes Recht nicht verletzen.
Entwickler-Ecke und die zugehörigen Webseiten distanzieren sich ausdrücklich von Fremdinhalten jeglicher Art!