Entwickler-Ecke
Delphi Language (Object-Pascal) / CLX - Merkwürdiges For-Konstrukt
delphi10 - Sa 16.10.10 01:29
Titel: Merkwürdiges For-Konstrukt
Hallo
Warum erzeugt dieses For-Konstrukt
Delphi-Quelltext
1: 2: 3:
| For x := 1 to length(ErrorText) do if ErrorText[x] = '&' then delete(ErrorText,Pos('&',ErrorText),1); |
keine Fehlermeldung wenn im Text ein oder mehrere & vorkommen? Durch die Änderung der to-Varible würde ich einen Fehler erwarten, läuft aber einwandfrei.
Gruss Delphi10
Gerd Kayser - Sa 16.10.10 01:35
delphi10 hat folgendes geschrieben : |
Warum erzeugt dieses For-Konstrukt
Delphi-Quelltext 1: 2: 3:
| For x := 1 to length(ErrorText) do if ErrorText[x] = '&' then delete(ErrorText,Pos('&',ErrorText),1); |
keine Fehlermeldung wenn im Text ein oder mehrere & vorkommen? Durch die Änderung der to-Varible würde ich einen Fehler erwarten, läuft aber einwandfrei.
Gruss Delphi10 |
Siehe z. B. hier:
http://www.delphipraxis.net/90482-schleife-rueckwaerts.html
elundril - Sa 16.10.10 01:56
In dem Fall würde ich aber ne FOR-IN-Schleife verwenden, wenn du schon das RAD-Studio 2010 hast. Nur so zur Sicherheit. Weil da wird dann jedes Element genau einmal ausgewählt (Falls es überhaupt mit Strings geht, aber eigentlich sollte es).
Gerd Kayser - Sa 16.10.10 02:02
elundril hat folgendes geschrieben : |
In dem Fall würde ich aber ne FOR-IN-Schleife verwenden, wenn du schon das RAD-Studio 2010 hast. Nur so zur Sicherheit. Weil da wird dann jedes Element genau einmal ausgewählt (Falls es überhaupt mit Strings geht, aber eigentlich sollte es). |
Ich würde mich beim Programmieren nicht auf irgendwelche Schalterstellungen in der IDE verlassen. Richtig wäre, die Schleife runter zählen zu lassen, und das auch entsprechend zu coden.
delphi10 - Sa 16.10.10 02:03
elundril hat folgendes geschrieben : |
In dem Fall würde ich aber ne FOR-IN-Schleife verwenden, wenn du schon das RAD-Studio 2010 hast. Nur so zur Sicherheit. Weil da wird dann jedes Element genau einmal ausgewählt (Falls es überhaupt mit Strings geht, aber eigentlich sollte es). |
Das stammt noch aus einem alten Projekt mit Studio 2006 das ich gerade aufmöbele. Trotzdem Danke.
Der Hinweis von @Gerd Kayser hat meinem organischen Speicher wieder reorganisiert. Man(n) wird älter.
jaenicke - Sa 16.10.10 07:18
Zu deiner Ausgangsfrage: Wenn du die Bereichsüberprüfung nicht angeschaltet hast, merkt dein Programm ganz einfach nicht, dass es ggf. bereits hinter dem Ende des Strings liest. ;-)
Solange hinter dem Speicherbereich des Strings also keine nicht zugreifbaren Speicherbereiche liegen, kommt auch keine Zugriffsverletzung. Sollte da zufällig ein Zeichen gefunden werden, weil da diese Bytewerte liegen, passiert auch kein Schreibzugriff, da Delete nichts tut, da der Zugriff hinter dem Ende des Strings liegt.
Wenn du einen String versucht hättest, bei dem zwei der gesuchten Zeichen hintereinander liegen, hätte es nicht funktioniert, da das zweite Zeichen nach dem Delete aufgerückt ist und nicht mehr geprüft wird.
elundril hat folgendes geschrieben : |
In dem Fall würde ich aber ne FOR-IN-Schleife verwenden, wenn du schon das RAD-Studio 2010 hast. Nur so zur Sicherheit. Weil da wird dann jedes Element genau einmal ausgewählt (Falls es überhaupt mit Strings geht, aber eigentlich sollte es). |
Ja, es geht mit Strings. In diesem Fall aber nicht, denn es soll ja gerade der String verändert werden. Und das geht nicht, wenn for...in benutzt wird. Denn das funktioniert wie jede andere for-Schleife: Die Randbedingungen werden vorher ausgelesen.
Tranx - Sa 16.10.10 08:12
Wahrscheinlich wäre hier ein While-Konstrukt besser. Möglicherweise ist der jedoch nicht so schnell, da jedesmal ein Pos(sub,s) abgefragt wird, aber er sollte alle Zeiche(folgen) erfassen:
Delphi-Quelltext
1: 2:
| while Pos('&',Errortext)>0 do Delete(ErrorText,Pos('&',ErrorText),1); |
Dies gilt natürlich nur, wenn nicht ausdrücklich eine FO-Schleife gefordert ist.
jaenicke - Sa 16.10.10 08:15
Das ist hier aber deutlich ungünstiger (noch verstärkt dadurch, dass du das Ergebnis von Pos nicht einmal zwischenspeicherst).
Der bereits genannte Weg mit der umgedrehten for-Schleife ist schon der richtige. Das funktioniert auch, ist aber deutlich schneller.
Tranx - Sa 16.10.10 08:19
Sicher, für das Löschen einzelner Zeichen ist mein Konstrukt sicher langsamer als eine For-Schleife. Doch was ist mit dem Löschen einer Zeichenfolge? Z.B. statt '&' '###' oder ...? Dann wird ein For-Konstrukt deutlich komplizierter, oder? Außerdem, wenn das Zeichen in einem String als Platzhalter für andere Texte steht? Dann ist das in einer umgekehrten FOR-Schleife ebenfalls nicht so einfach machbar.
Flexibilität ist leider oft mit Geschwindigkeitsverlust verbunden.
jaenicke - Sa 16.10.10 08:30
Erstens gibt es dann noch StringReplace, das wahrscheinlich immer noch besser optimiert ist, zweitens ist das nicht so schwer. Mal so runtergeschrieben, sollte funktionieren:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
| function DeleteFromString(const AFrom, ADelString: string): string; var i, CurPos: Integer; begin Result := AFrom; if (Length(AFrom) > 0) and (Length(ADelString) > 0) then begin CurPos := Length(ADelString); for i := Length(Result) downto 1 do if ADelString[CurPos] = AFrom[i] then if CurPos = 1 then begin Delete(Result, i, Length(ADelString)); CurPos := Length(ADelString); end else Dec(CurPos) else CurPos := Length(ADelString); end; end; |
Mit Boyer-Moore könnte man noch ein bisschen mehr Geschwindigkeit herausholen. ;-)
Gerd Kayser - Sa 16.10.10 11:24
jaenicke hat folgendes geschrieben : |
Zu deiner Ausgangsfrage: Wenn du die Bereichsüberprüfung nicht angeschaltet hast, merkt dein Programm ganz einfach nicht, dass es ggf. bereits hinter dem Ende des Strings liest. ;-) |
Nö, das ist nicht die eigentliche Ursache. Wenn die Delphi-Optimierung eingeschaltet ist, wird aus einer to-Scheife eine downto-Schleife. Bei einer downto-Schleife tritt der Effekt nicht auf, d. h. ein Lesen hinter dem String passiert nicht. Interessant wird es erst, wenn die Optimierung abgeschaltet wird, um vielleicht an einer anderen Stelle zu debuggen. Man sollte sich also nie auf die Compilerschalterchen verlassen und sich angewöhnen, gleich richtig eine downto-Schleife zu verwenden.
jaenicke - Sa 16.10.10 11:27
Die Optimierung passiert nur im Hintergrund. Diese hat aber keinen Effekt auf die tatsächlich angesprochenen Indizes des Strings...
Dass die Schleife in Wirklichkeit rückwärts läuft, hat also keinen Effekt. Wäre ja auch schlimm sonst.
Gerd Kayser - Sa 16.10.10 12:34
jaenicke hat folgendes geschrieben : |
Die Optimierung passiert nur im Hintergrund. Diese hat aber keinen Effekt auf die tatsächlich angesprochenen Indizes des Strings...
Dass die Schleife in Wirklichkeit rückwärts läuft, hat also keinen Effekt. Wäre ja auch schlimm sonst. |
Ich habe gerade versucht, dieses Verhalten nachzustellen. Leider ohne Erfolg. Entweder war ich da auf dem Holzweg oder meine Erinnerung trügt mich. Ich meine vor etwa einem Jahr eine Schleife programmiert zu haben, die sich anders als erwartet verhielt, und demzufolge Murks produzierte.
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 TMainform.Button1Click(Sender: TObject); var EinString : string; Schleife : integer; begin EinString := 'Hallo Welt'; for Schleife := 1 to Length(EinString) do if EinString[Schleife] = 'l' then Delete(EinString,Pos('l',EinString),1); ShowMessage(EinString); end;
procedure TMainform.Button2Click(Sender: TObject); var EinString : string; Schleife : integer; begin EinString := 'Hallo Welt'; for Schleife := Length(EinString) downto 1 do if EinString[Schleife] = 'l' then Delete(EinString,Pos('l',EinString),1); ShowMessage(EinString); end; |
Wie dem auch sei: Beide Prozeduren liefern jedenfalls unterschiedliche Ergebnisse.
Prozedur 1: Hao Welt
Prozedur 2: Hao Wet
jaenicke - Sa 16.10.10 12:50
Naja, ist ja logisch, du prüfst an der Stelle der Schleifenvariablen und löschst an der ersten Position des Zeichens... Dass es dann einen Unterschied zwischen den Laufrichtungen gibt, ist klar. ;-)
delphi10 - Sa 16.10.10 12:51
jaenicke hat folgendes geschrieben : |
Erstens gibt es dann noch StringReplace, das wahrscheinlich immer noch besser optimiert ist, zweitens ist das nicht so schwer. Mal so runtergeschrieben, sollte funktionieren: Delphi-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21:
| function DeleteFromString(const AFrom, ADelString: string): string; var i, CurPos: Integer; begin Result := AFrom; if (Length(AFrom) > 0) and (Length(ADelString) > 0) then begin CurPos := Length(ADelString); for i := Length(Result) downto 1 do if ADelString[CurPos] = AFrom[i] then if CurPos = 1 then begin Delete(Result, i, Length(ADelString)); CurPos := Length(ADelString); end else Dec(CurPos) else CurPos := Length(ADelString); end; end; | Mit Boyer-Moore könnte man noch ein bisschen mehr Geschwindigkeit herausholen. ;-) |
Ich hab das mal eben implantiert. Läuft einwandfrei, wobei mir eine Optimierung nicht so wichtig ist. Hier werden Systemmeldungen für einen Mailbody zusammengestellt - da ist dann sowieso alles zu spät. Dummerweise verträgt Outlook im automatisch generierten Bodytext keine '&', daher der Aufwand.
Gruß Delphi10
edit: Die For-Schleife erwischt in der Tat nur einzelne '&', wenn mehrere hintereinander stehen versagt die Methode.
Tilman - Sa 16.10.10 16:51
1. Der ganz Obenstehende Code funktioniert bei mir
nicht, voraussetzend dass er alle & in einem text löschen soll: aus "salbader&&&blubl&&ub&" wird "salbaderblubl&ub&"
2. Ich mache das immer so, wenn ich z.B. aus Listen lösche, und funktioniert einwandfrei:
Delphi-Quelltext
1: 2: 3:
| For x := length(ErrorText) downto 1 do if ErrorText[x] = '&' then delete(ErrorText,Pos('&',ErrorText),1); |
Hier wird zwar die From-Klausel geändert, aber das aktuelle Element ist stets gültig, und ich hatte noch nie Probleme damit.
jaenicke - Sa 16.10.10 17:23
Tilman hat folgendes geschrieben : |
1. Der ganz Obenstehende Code funktioniert bei mir nicht |
Das hatte ich ja schon geschrieben, ich hatte es zwar nicht ausprobiert, aber rein logisch war das klar.
Tilman hat folgendes geschrieben : |
2. Ich mache das immer so, wenn ich z.B. aus Listen lösche, und funktioniert einwandfrei: |
Das ist doch auch Unfug (außer die richtige Schleifenrichtung, aber die ist ja nun hinreichend schon erwähnt worden). Wie ich schon weiter oben geschrieben habe: Wozu prüfst du, ob das aktuelle Zeichen das passende ist, wenn du dann ein ganz anderes (das erste nämlich wegen Pos) löschst?!?
Tilman - Sa 16.10.10 17:28
Jo jaenicke da hast du recht, hatte mir den Code nicht genau genug angesehen, muss natürlich
Delphi-Quelltext
1: 2: 3:
| For x := length(ErrorText) downto 1 do if ErrorText[x] = '&' then delete(ErrorText,x,1); |
lauten
Martok - Sa 16.10.10 17:49
Was hindert euch jetzt eigentlich daran, die Perfomance zu ignorieren und
Delphi-Quelltext
1:
| StringReplace(ErrorText, '&','',[rfReplaceAll]) |
zu verwenden?
Duct Tape Programming: funktionierender (und selbstdokumentierender) Code zuerst, optimieren kann man immer noch, wenn sich Probleme zeigen. Und das dürfte bei einer Funktion die Mails verschickt wohl nicht passieren...
jaenicke - Sa 16.10.10 18:29
Ja, die Funktion hatte ich ja auch schon erwähnt. Die eigene Funktion war vor allem ne Demo, dass es durchaus einfach ist. ;-)
delphi10 - Sa 16.10.10 21:20
Martok hat folgendes geschrieben : |
Duct Tape Programming: funktionierender (und selbstdokumentierender) Code zuerst, optimieren kann man immer noch, wenn sich Probleme zeigen. Und das dürfte bei einer Funktion die Mails verschickt wohl nicht passieren... |
es sei denn, dass eine Folge von n'&' 100% ausgeschlossen ist. Optimieren kann auch heißen, dass nicht mögliche Zustände auch nicht geprüft oder sonstwie behandelt werden müssen. Einsetzten von X anstelle von
ist dagegen natürlich ok. Aber wie das so ist beim schnellen hinschreiben, sowas merkt man einfach nicht, das guckt sich weg..
alzaimar - So 17.10.10 05:17
Das das Delete die eigentliche Spass(=Performance)bremse ist, scheint noch nicht aufgefallen zu sein.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| Var i,j : Integer;
Begin j := 0; for i:=1 to length (aString) do if aString[i]<>'&' then begin inc(j); aString[i] := aString[j]; end;
SetLength (aString,j); End; |
Da wird dann auch kein Boyer-Moore mehr benötigt. Der wäre bei Suchstring, der nur aus einem Zeichen besteht, sowieso langsamer als eine einfache Suche.
jaenicke - So 17.10.10 08:17
Doch schon, ich habe es hier auch dann ausprobiert, aber wirklich schneller war es dann nicht, egal ob mit Strings oder PChars.
Da hatte ich dann aber schon mit mehreren Zeichen getestet, für ein Zeichen sollte deins natürlich schneller 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!