3

I am trying to learn about Generics in Delphi but have a very basic problem with TList.

I have successfully created a list integers and filled it with 1000 odd numbers. I want to change every number on the list that is divisible by 3 to 0. I thought that I could do something like this.

For I in Mylist Do
Begin
  If (I mod 3)= 0 Then
    I:=0;
End;

This clearly does not work so I would appreciate someone explaining what will.

LU RD
  • 34,438
  • 5
  • 88
  • 296
user3861642
  • 31
  • 1
  • 2
  • 1
    This is one area where the Delphi language is really weak. Other languages have powerful iterators that allow you to mutate the collections. – David Heffernan Jul 21 '14 at 17:35
  • 2
    I wonder how much work it would take for Embarcadero to let an enumerator's `Current` property be read/write instead of read-only. – Remy Lebeau Jul 21 '14 at 18:12
  • Possible duplicates http://stackoverflow.com/q/1105519/960757, http://stackoverflow.com/q/2246087/960757. – TLama Jul 21 '14 at 18:34
  • See also how to [Increment an integer variable in a loop](http://stackoverflow.com/q/16326975/576719). – LU RD Jul 21 '14 at 18:40
  • I'm thinking of D here: `foreach (ref int value; collection) value++;` – David Heffernan Jul 21 '14 at 19:35

3 Answers3

9

You are using a for..in loop, which uses a read-only enumerator. This code:

For I in Mylist Do
Begin
  If (I mod 3) = 0 Then
    I := 0;
End;

Is actually doing this:

Enum := Mylist.GetEnumerator;
while Enum.MoveNext do
Begin
  I := Enum.Current;
  If (I mod 3) = 0 Then
    I := 0;
End;

That is why you cannot modify the list contents in a for..in loop. You have to use an older-style for loop instead, using the TList<T>.Items[] property to access the values:

For I := 0 to Mylist.Count-1 Do
Begin
  If (Mylist[I] mod 3) = 0 Then
    Mylist[I] := 0;
End;

Update: to then remove the zeros, you can do this:

For I := Mylist.Count-1 downto 0 Do
Begin
  If Mylist[I] = 0 Then
    Mylist.Delete(I);
End;

Or, do it in the initial loop so you don't need a second loop:

For I := Mylist.Count-1 downto 0 Do
Begin
  If (Mylist[I] mod 3) = 0 Then
    Mylist.Delete(I);
End;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Many thanks - that was very helpful and I have modified my code successfully. However, this raises a further question. How would I go about removing all items from the list that are now zero? (and, of course, effectively shorten the list). – user3861642 Jul 22 '14 at 13:27
  • You would have to loop through the list (backwards) a second time looking for zeros. I updated my answer with examples. – Remy Lebeau Jul 22 '14 at 19:53
4

Your code won't work because you're trying to modify the loop control variable (I) in the loop, which isn't allowed. It tells you that in the compiler error:

[dcc32 Error] Project1.dpr(23): E2081 Assignment to FOR-Loop variable 'i'

If you want to modify the list, you need to iterate through the list the old-fashioned way (by index) instead.

for i := 0 to List.Count - 1 do
  if (List[i] mod 3) = 0 then
    List[i] := 0;
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • 1
    Yes, but I would add: "If you want to modify the list, you need to iterate through the list the old-fashioned way (by index) rather than by enumerator (note that there are no iterators in Delphi's collections)." – Rudy Velthuis Jul 21 '14 at 18:23
  • @Rudy There are iterators. They are just badly named as enumerators. – David Heffernan Jul 21 '14 at 18:41
  • @Rudy: I agree that I should have clarified with the "modify the list" addition. The [documentation](http://docwiki.embarcadero.com/RADStudio/XE6/en/Declarations_and_Statements#Iteration_Over_Containers_Using_For_statements) refers to `forin` as iteration and not enumeration, though. I've rephrased the sentence somewhat. – Ken White Jul 21 '14 at 19:21
  • @DavidHeffernan: Iterators generally allow you to modify the collection underneath. Enumerators generally don't. Delphi's collections have enumerators, and the for-in loop builds on those. You can't modify the collection using these enumerators. – Rudy Velthuis Jul 21 '14 at 19:38
  • FWIW: http://stackoverflow.com/questions/948194/difference-between-java-enumeration-and-iterator. That is just one example. – Rudy Velthuis Jul 21 '14 at 19:40
  • @RudyVelthuis You are talking about language specific terminology, in the case you link to Java. A for in loop does not need to iterate over every single item. Ergo enumeration is the wrong term. The fact that lots of other people have made this mistake in terminology does not make it not be a mistake. – David Heffernan Jul 21 '14 at 19:42
  • Here is [`one for C#`](http://stackoverflow.com/q/716238/960757). I also don't see any mistake. They just designed the language and call *that thing* enumerator. – TLama Jul 21 '14 at 19:50
  • @Rudy It is a mistake, in my opinion, to call these things enumerators because they do not enumerate. Enumeration would result in every member of the collection being yielded. The thing is that every language designer has a subtly different view on the terminology. So given that there are so many different meanings, I don't think you are in any position to tell Ken that his use of terminology is wrong. Likewise I cannot tell you that either. There is no single right and wrong naming here. – David Heffernan Jul 21 '14 at 19:50
  • @TLama C++ has no enumerators. It has iterators. That can be read only or read/write, amongst other things. So the original claimed distinction is clearly not so. And before you say, we are not talking about C++, we aren't talking about Delphi either because it has no read/write iterators/enumerators. – David Heffernan Jul 21 '14 at 19:52
  • I don't see why enumeration should result in every member. – Rudy Velthuis Jul 21 '14 at 19:54
  • @Rudy I hope you don't feel this way when enumerating teeth! ;-) – David Heffernan Jul 21 '14 at 19:59
  • @David: That's an enumeration, not an enumerator. – Rudy Velthuis Jul 21 '14 at 19:59
  • @Rudy and what does an enumerator do then? – David Heffernan Jul 21 '14 at 20:01
  • @David: if I enumerate teeth, I am examining them. That is not a modification. I then write down the numbers of the teeth that need modification or removal. ;-) – Rudy Velthuis Jul 21 '14 at 20:01
  • An enumerator, well, enumerates. An enumeration is the result of the completion of this action. – Rudy Velthuis Jul 21 '14 at 20:03
  • @Rudy This really makes my point then. The end result of a delphi enumerator's work need not be an enumeration. – David Heffernan Jul 21 '14 at 20:14
  • Ok, then an enumerator does not have to result in an enumeration. An enumeration can be constructed using an enumerator, though. – Rudy Velthuis Jul 21 '14 at 20:17
  • @Rudy Anyway, all I'm saying is that I think Ken was fine to use the term iterator. – David Heffernan Jul 21 '14 at 20:28
  • @Rudy: As I mentioned before, the Delphi Language Guide says that `forin` does an iteration. (See the link from my prior comment.) – Ken White Jul 21 '14 at 20:59
  • @Ken: the Delphi Language Guide sometimes uses its own terms and may contain errors. The fact that the RTL uses GetEnumerator, etc. seems to tell me it enumerates. – Rudy Velthuis Jul 21 '14 at 21:04
  • 1
    @Rudy In your head, iterate means that the items can be modified. Enumerate means that the items cannot be modified. You are welcome to use those meanings if you wish. But don't kid yourself that these meanings are universally accepted. – David Heffernan Jul 22 '14 at 06:38
0
uses
  System.Generics.Collections;

procedure TForm1.BitBtn1Click(Sender: TObject);
var
  _List: TList<integer>;
  i: integer;
begin
  _List := TList<integer>.Create;
  try

    // Add few odd numbers
    for i := 0 to 100 do
    begin
      if (i mod 2) <> 0 then
        _List.Add(i);
    end;

    // Replace numbers with 0 that divides by 3
    for i := 0 to _List.Count - 1 do
    begin
      if (_List[i] mod 3) = 0 then
        _List[i] := 0;
    end;

    // Show new list
    for i := 0 to _List.Count - 1 do
      OutputDebugstring(PWideChar(IntToStr(_List[i])));

  finally
    FreeAndNil(_List);
  end;
end;

You do not change iteration itself (e.g. i), you want to change value (e.g. _List[i])

Edijs Kolesnikovičs
  • 1,627
  • 3
  • 18
  • 34
  • _List is a local variable, which is not accessible from anywhere else. Why do you FreeAndNil it? – Rudy Velthuis Jul 22 '14 at 07:01
  • 1
    I didn't say it was wrong. I am just asking why you do it. The variable is at the very end of its scope and not accessible from anywhere else, so I wondered why you use `FreeAndNil` instead of `.Free`. – Rudy Velthuis Jul 22 '14 at 11:33