Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Löschen in einer "for in" Schleife (https://www.delphipraxis.net/178055-loeschen-einer-schleife.html)

dominikkv 14. Dez 2013 13:45

Löschen in einer "for in" Schleife
 
Hallo,

ich stehe öfters mal vor dem Problem, dass ich eine Liste habe, und Einträge daraus löschen möchte:
Delphi-Quellcode:
var
  Eintrag: TMeinEintrag;
  Liste: TObjectList<TMeinEintrag>;

// ...

  for Eintrag in Liste do
     begin
      if Bedingung(Eintrag) then
         begin
          Liste.Remove(Eintrag);
         end;
     end;
Das ganze kann aber zu komischen Effekten führen - zum Beispiel wird bei mir jetzt gerade ein Eintrag übersprungen. Vielleicht kommt der Iterator nicht damit zurecht, wenn der aktuelle Eintrag aus der Liste gelöscht wird.

- Könnt ihr bestätigen, dass obiges Beispiel zu Fehlern führen kann?
- Wie löscht ihr bestimmte Einträge aus einer Liste? Eine "for I := Count - 1 downto 0"-Schleife möchte ich vermeiden.

Grüße
Dominik

himitsu 14. Dez 2013 13:52

AW: Löschen in einer "for in" Schleife
 
Das problem ist, daß der Standard-Enumerator nur den Index und das Ende kennt.
Er zählt einfach nur blind hoch und weiß nicht, daß du etwas löschst.

siehe TObjectList.GetEnumerator (das ist der Enumerator, welchen For-In nimmt, wenn du selbst Keinen explizit übergibst)


Die einzigen Lösungen sind da:
- kein For-In, sondern eine While- oder eine For-Downto-Schleife nutzen
- einen eigenen Enumerator nutzen
- - entweder muß der auch rückwärts laufen
- - oder er listet erst alles auf, merkt sich das und geht dann seine Liste durch
- wenn deine TList eine ToArray-Funktion hast, dann läßt du die Liste in ein Array umwandeln und läufst dann mit dem Standard-Enumerator der Arrays über die Kopie, wobei das Array von deinem Löschen auch nichts weiß und demanch alles von seiner Kopier dir presentiert.

[add]
die generiesche TObjectList<T> geht auf TList<T> und die hat ein ToArray
Delphi-Quellcode:
for Eintrag in Liste.ToArray do


Du kannst Emba auch fragen, ob die das Löschen in dem Standardenumerator berücksichtigen.
(dürfte mit zwei/drei Zeilen Quellcode möglich sein)

jaenicke 14. Dez 2013 13:58

AW: Löschen in einer "for in" Schleife
 
Die einfachste Lösung ist die zuletzt genannte:
Delphi-Quellcode:
  for Eintrag in Liste.ToArray do
     begin
      if Bedingung(Eintrag) then
         begin
          Liste.Remove(Eintrag);
         end;
     end;
Zitat:

Zitat von dominikkv (Beitrag 1239746)
- Wie löscht ihr bestimmte Einträge aus einer Liste? Eine "for I := Count - 1 downto 0"-Schleife möchte ich vermeiden.

Was spricht denn dagegen? Das wäre ja die schnellste Variante was den erzeugten Code angeht.

Furtbichler 14. Dez 2013 14:53

AW: Löschen in einer "for in" Schleife
 
Zitat:

Zitat von himitsu (Beitrag 1239747)
Das problem ist, daß der Standard-Enumerator nur den Index und das Ende kennt.

Den Index? Ich dachte immer, ein Eumerator kennt gerade *keinen* Index, sondern nur ein 'Start','Next' und 'Bin ich am Ende?'. Aber, klar: Der spezielle Listen-Enumerator arbeitet natürlich mit einem Index. Aber allgemein gesehen darf ein Enumerator keinen Index kennen. Ein gutes Beispiel ist ein Dataset oder ein Dateiverzeichnis: Dort gibt es wirklich nur die drei o.g. Befehle zum Navigieren.

Da eine For-In-Enumeration in der Umsetzung allgemeingültig sein muss, würde ich mich nie darauf verlassen, wie ein Iterator umgesetzt ist. D.h.: Löschen in Iterator: Nee.

Und deshalb sollte die Remove-Implementierung in der Liste sofort (fail-fast) eine Exception werfen, wenn ein Enumerator aktiv ist. So wie ich das hier lese, scheint das bei Delphi (mal wieder) nur halbherzig, d.h. gar nicht umgesetzt worden zu sein. Oder irre ich mich?

Perlsau 14. Dez 2013 15:40

AW: Löschen in einer "for in" Schleife
 
Zitat:

Zitat von Furtbichler (Beitrag 1239761)
Ein gutes Beispiel ist ein Dataset oder ein Dateiverzeichnis: Dort gibt es wirklich nur die drei o.g. Befehle zum Navigieren.

Theoretisch könnte man in einem Dataset auch mit RecNo navigieren:
Delphi-Quellcode:
Dataset.Last;
for i := 1 to Dataset.RecordCount do
begin
  DataSet.RecNo := i;
  ...
end;
Ist aber kein guter Programmierstil und birgt etliche Fallen. Deshalb: In DataSets immer nur mit Next, Prior, First und Last navigieren und beim Durchiterieren auf DataSet.Eof prüfen.

dominikkv 14. Dez 2013 18:13

AW: Löschen in einer "for in" Schleife
 
Hey danke für eure Antworten :thumb:

Das mit dem .ToArray gefällt mir gut (und es klappt sogar :-D). Das erstellt zwar eine Kopie der Liste, aber das ist in diesem Fall vertretbar.

Zitat:

Zitat von jaenicke (Beitrag 1239749)
Zitat:

Zitat von dominikkv (Beitrag 1239746)
- Wie löscht ihr bestimmte Einträge aus einer Liste? Eine "for I := Count - 1 downto 0"-Schleife möchte ich vermeiden.

Was spricht denn dagegen? Das wäre ja die schnellste Variante was den erzeugten Code angeht.

Ja das ist in der Ausführung die schnellste Variante, allerdings ist Schnelligkeit nicht immer ausschlaggebend. In meinem Fall habe ich so um die 100 Einträge in der Liste, da lege ich mehr Wert auf Lesbarkeit des Codes, außerdem spare ich somit eine Variable :cyclops:

Mikkey 14. Dez 2013 19:42

AW: Löschen in einer "for in" Schleife
 
Wenn Du unbedingt das for .. in - Konstrukt verwenden willst, legst Du halt eine Löschliste an, in die Du alle zu löschenden Kandidaten übernimmst und nach vollständigem Duchlauf diese Kandidaten gezielt löschst.

In C# führt Dein Vorgehen regelmäßig zu einer Exception. Dort ist der Umweg über den Index i.A. nicht möglich, deshalb ist der obige ein häufig beschrittener Weg.

Furtbichler 14. Dez 2013 20:29

AW: Löschen in einer "for in" Schleife
 
Zitat:

Zitat von Mikkey (Beitrag 1239798)
... Dort ist der Umweg über den Index i.A. nicht möglich, deshalb ist der obige ein häufig beschrittener Weg.

Na ja, wenn du noch unbedingt mit .NET 2 hantieren musst, ja. Aber mit .NET 3.5 oder höher geht das aber noch einfacher
Code:
foreach (var item in myEnumerable.Where(ItemIsNotValidAnymore))
{
  myEnumerable.Remove(item);
}
...
bool ItemIsNotValidAnymore(MyEnumerableElement item)
{
 ...
}
Da braucht man noch nicht einmal mehr Kommentare.

jaenicke 14. Dez 2013 20:34

AW: Löschen in einer "for in" Schleife
 
Zitat:

Zitat von dominikkv (Beitrag 1239784)
außerdem spare ich somit eine Variable :cyclops:

Die Variable ist ja trotzdem da, nur halt in der zusätzlichen Enumerationsklasse. ;-)

Furtbichler 14. Dez 2013 21:21

AW: Löschen in einer "for in" Schleife
 
Zitat:

Zitat von dominikkv (Beitrag 1239784)
Ja das ist in der Ausführung die schnellste Variante, allerdings ist Schnelligkeit nicht immer ausschlaggebend. In meinem Fall habe ich so um die 100 Einträge in der Liste, da lege ich mehr Wert auf Lesbarkeit des Codes, außerdem spare ich somit eine Variable :cyclops:

Ich frage mich wirklich, was daran schlecht lesbar ist:
Delphi-Quellcode:
for i:=Liste.Count-1 downto 0 do
  if Bedingung(Eintrag[i]) Then
    Liste.Delete(i);
Oder findest Du die for-Schleife etwa 'old-school'? Den Umweg über ein Array zu gehen ist ja auch nicht gerade direkt, kurz und knapp, oder? Wenn Du das lesbar machen willst, dann benutze Refactoring;
Delphi-Quellcode:
...
LoescheBestimmteElementeAusDer(Liste);
...
Beim Lesen des Codes kann man ja wohl nicht anders, als zu lesen, was dort passiert. Da muss man noch nicht einmal großartig in die Routine reinspringen, weil das ja im Kontext der o.g. Routine sekundär sein dürfte.
Delphi-Quellcode:
Procedure LoescheBestimmteElementeAusDer(Liste : TListe);
var
  i : Integer;
Begin
  for i:=Liste.Count-1 downto 0 do
    if Bedingung(Eintrag[i]) Then
      Liste.Delete(i);
End;


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:29 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz