tags:

views:

223

answers:

3

Alright, so you have a TObjectList instance. You want to loop through the items in it and delete some of the objects from the list. You can't do this:

for I := 0 to ObjectList.Count - 1 do
  if TMyClass(ObjectList[I]).ShouldRemove then
    ObjectList.Delete(I);

...because once you delete the first object the index counter I will be all wrong and the loop won't work any more.

So here is my solution:

Again:
  for I := 0 to ObjectList.Count - 1 do
    if TMyClass(ObjectList[I]).ShouldRemove then
    begin
      ObjectList.Delete(I);
      goto Again;
    end;

This is the best solution I've found to this so far. If anyone has a neater solution I'd love to see it.

+27  A: 

Try this instead:

 for I := ObjectList.Count - 1 downto 0 do
   if TMyClass(ObjectList[I]).ShouldRemove then
     ObjectList.Delete(I);

That looks like a particularly bad use of goto, jumping out of the for loop like that. I assume it works (since you're using it), but it would give me the willies.

Larry Lustig
Woah. That works, I'm not 100% sure why but I just tested it and it does. Very nice.
David
Alright, I see why it works now. Because you are counting down it doesn't matter that the list shrinks. The subsequent indexes will all be valid. I'm going to go change my stupid code now.
David
Keep in mind Larry's solution is a well known standard solution to this particular problem (index-based deletion), applicable in a whole lot more situations than just "Delphi" and/or "TObjectList". It's an important tool to have in your belt.
Paul-Jan
And basically, it shows that the use of Goto is still not needed for this problem. :-) In my 20+ years of Pascal programming experience I've learned that if you consider the use of goto, you're missing a much better solution. I've never, ever needed to use this silly command.
Workshop Alex
A: 

The only valid use of Goto I've seen is the one supplied in Delphi's help.

for I := 0 to something do
begin
  for J := 0 to something do
  begin
    For K := 0 to something do
    begin
      if SomeCondition then
        Goto NestedBreak
    end;
  end;
end;
NestedBreak:

Though Goto could be avoided in that exemple by moving the loop in a local function and using EXIT, for exemple. If a subfunction is not acceptable, you can still do that:

for I := 0 to something do
begin
  for J := 0 to something do
  begin
    For K := 0 to something do
    begin
      if SomeCondition then
      begin
        GottaBreak := True
        Break;
      end; 
    end;
    if GottaBreak then Break;
  end;
  if GottaBreak then Break;
end;

This is just sligthly less optimal.

I have yet to see a single other situation where a Goto would be the best solution.(Or any good at all).

Goto in itself is NOT bad. It's a flow control command just like EXIT, BREAK or CONTINUE. Except that those other are restricted to specific situations and are managed by the compiler correctly. (With that being said, some programmer I spoke with consider those as being as harmful as Goto, a view I don't share) Goto being unrestricted, the things you can do with it can have very negative impacts. Anyway, I think I went a bit beyond the scope of the question already. ^_^

Ken Bourassa
Ken, you could just wrap a try...except around the loops and use Abort; instead of the goto. I think I would prefer that.
David
Exceptions have a much... MUCH bigger performance hit than the "if GottaBreak then" solution. Try...Except is bad for performance. It shouldn't be used for "normal" flow.
Ken Bourassa
Today's processors are fast enough that you don't have to worry about the performance impact of raising a single exception to escape from a loop.
David
Today's processor have so much performance, lets waste some! Seriously, here's some reading for you. http://www.stevetrefethen.com/blog/BDS2006slowcompiletimesandthecostofexceptions.aspx
Ken Bourassa
@David, using Abort in that way is just a Goto in disguise and also disguise the true purpose. As for the performance impact: let's not waste some if there are better and much clearer solutions to be found. Using an exception for (local) flow control is not the clearest way of coding and considered bad practice by many.
Marjan Venema
Ken: jumping out a bunch (even 2) of nested IFs is also faster. (at least in D2006). Similar case though. I have one or two in my innerloop of my image processing (blob finding) routines. FPC recently implemented interprocedural goto btw (ISO feature).
Marco van de Voort
It's a good example, although you could replace the for-loop with a while-loop and check for two conditions. I really hate the use of the break command too. It just makes developers lazy, since there are nicer alternatives to jump out of a loop. (Besides, a while-loop provides more control over the loop, plus it maintains the value of the loop variable when you end the loop.)
Workshop Alex
@Ken, thank you, that was a very interesting read. I didn't realize exceptions could cause that much of a performance hit. I will avoid using them in normal program flow from now on.
David
@Marjan, after reading Ken's link I now agree with you. I'll avoid using exceptions like this in the future. PS. Any relation to Wietse Venema?
David
@David: not a relation I know of...
Marjan Venema
+7  A: 

You can also use

  I := 0;
  while I < ObjectList.Count do begin
    if TMyClass(ObjectList[I]).ShouldRemove then ObjectList.Delete(I)
    else Inc(I);
  end;
Serg
That's a nice solution as well! Thanks.
David
Yep! Good one. But how about doing a reverse loop? `For I := Prec(ObjectList.count) downto 0 do if TMyClass(ObjectList[I]).ShouldRemove then ObjectList.Delete(I)` ? Which has been suggested already. :-)
Workshop Alex