Difference between revisions of "for-in loop/fr"

From Lazarus wiki
Jump to navigationJump to search
Line 216: Line 216:
 
=== Traversée de types énumération et intervalle ===
 
=== Traversée de types énumération et intervalle ===
  
Dans Delphi, il n'est [http://qc.embarcadero.com/wc/qcmain.aspx?d=106212 pas possible de traverser nonplus les types énumérés] ou les intervalles, alors qu'en Free Pascal vous pouvez écrire ce qui suit:
+
Dans Delphi, il n'est [http://qc.embarcadero.com/wc/qcmain.aspx?d=106212 pas possible de traverser non plus les types énumérés] ou les intervalles, alors qu'en Free Pascal vous pouvez écrire ce qui suit:
  
 
<syntaxhighlight>
 
<syntaxhighlight>

Revision as of 10:57, 5 July 2014

English (en) français (fr) 日本語 (ja) русский (ru)

La construction de boucle "for-in" est pris en charge dans Delphi de Delphi 2005. Il a été mis en œuvre en FPC 2.4.2.

Implémentation Delphi et FPC

Une boucle for in respecte la syntaxe suivante:

Boucle sur une chaîne

procedure StringLoop(S: String);
var
  C: Char;
begin
  for C in S do
    DoSomething(C);
end;

Boucle sur un tableau

procedure ArrayLoop(A: Array of Byte);
var
  B: Byte;
begin
  for B in A do
    DoSomething(B);
end;

Boucle sur un ensemble

type
  TColor = (cRed, cGren, cBlue);
  TColors = set of TColor;
procedure SetLoop(Colors: TColors);
var
  Color: TColor;
begin
  for Color in Colors do
    DoSomething(Color);
end;

Traverser un conteneur

Pour travrser une classe conteneur, vous avez besoin d'ajouter un énumérateur. Un Enumérateur est une classe construite selon le modèle suivant :

TSomeEnumerator = class
public
  function MoveNext: Boolean;
  property Current: TSomeType;
end;

Il faut seulement 2 choses pour définir une classe Enumerateur: une méthode MoveNext qui demande à l'énumérateur d'avancer d'un pas et une propriété Current qui peut retourner tout type approprié.

Par la suite, vous devez ajouter la méthode magique GetEnumerator de la classe conteneur qui retourne un énumérateur de l'instance.

Par exemple:

type
  TEnumerableTree = class;

  TTreeEnumerator = class
  private
    FTree: TEnumerableTree;
    FCurrent: TNode;
  public
    constructor Create(ATree: TEnumerableTree); 
    function MoveNext: Boolean;
    property Current: TNode read FCurrent;
  end;

  TEnumerableTree = class
  public
    function GetEnumerator: TTreeEnumerator;
  end;

constructor TTreeEnumerator.Create(ATree: TEnumerableTree);
begin
  inherited Create;
  FTree := ATree;
  FCurrent := nil;
end;

function TTreeEnumerator.MoveNext: Boolean;
begin
  // pour obtenir le noeud suivant dans l'arbre
  if FCurrent = nil then
    FCurrent := FTree.GetFirstNode
  else
    FCurrent := FTree.GetNextNode(FCurrent);
  Result := FCurrent <> nil;
end;

function TEnumerableTree.GetEnumerator: TTreeEnumerator;
begin
  Result := TTreeEnumerator.Create(Self);
  // Important: l'objet créé est automatiquement libéré par le compilateur après la boucle.
end;

Ensuite, cela vous permet de d'exécuter le code suivant:

procedure TreeLoop(ATree: TEnumerableTree);
var
  ANode: TNode;
begin
  for ANode in ATree do
    DoSomething(ANode);
end;

Vos trouverez que plusieurs classes de bases (telles que TList, TStrings, TCollection, TComponent ...) ont des énumérateurs intégrés.

Il est aussi possible de rendre une classe énumérable si vous implémentez l'interface suivante dans votre classe conteneur enumérable:

  IEnumerable = interface(IInterface)
    function GetEnumerator: IEnumerator;
  end;

ooù IEnumerator est déclarée comme :

  IEnumerator = interface(IInterface)
    function GetCurrent: TObject;
    function MoveNext: Boolean;
    procedure Reset;
    property Current: TObject read GetCurrent;
  end;

Enumérateurs multiple pour une classe

Vous pouvez ajouter des énumérateur supplémentaires à des classes, des objets et des enregistrements.

Voici un exemple d'ajout d'énumérateur qui traverse un TEnumerableTree en ordre inverse:

type
  TEnumerableTree = class;

  TTreeEnumerator = class
  // ...pour la traversée dans l'ordre, voir au dessus...
  end;

  TTreeReverseEnumerator = class
  private
    FTree: TEnumerableTree;
    FCurrent: TNode;
  public
    constructor Create(ATree: TEnumerableTree); 
    function MoveNext: Boolean;
    property Current: TNode read FCurrent;
    function GetEnumerator: TTreeReverseEnumerator; // se retourne lui-même
  end;

  TEnumerableTree = class
  public
    function GetEnumerator: TTreeEnumerator;
    function GetReverseEnumerator: TTreeReverseEnumerator;
  end;

//...voir au dessus l'implémentation de TTreeEnumerator...

constructor TTreeReverseEnumerator.Create(ATree: TEnumerableTree);
begin
  inherited Create;
  FTree := ATree;
end;

function TTreeReverseEnumerator.MoveNext: Boolean;
begin
  // some logic to get the next node from a tree in reverse order
  if FCurrent = nil then
    FCurrent := FTree.GetLastNode
  else
    FCurrent := FTree.GetPrevNode(FCurrent);
  Result := FCurrent <> nil;
end;

function TTreeReverseEnumerator.GetEnumerator: TTreeReverseEnumerator;
begin
  Result := Self;
end;

function TEnumerableTree.GetReverseEnumerator: TTreeReverseEnumerator;
begin
  Result := TTreeReverseEnumerator.Create(Self);
  // Note: l'objet est automatiquement supprimé par le compilateur après la boucle.
end;

Après, vous êtes en mesure d'exécuter le code suivant:

procedure TreeLoop(ATree: TEnumerableTree);
var
  ANode: TNode;
begin
  for ANode in ATree.GetReverseEnumerator do
    DoSomething(ANode);
end;

Extensions FPC

Les exemples de code suivants illustrent les constructions implémentées uniquement dans FPC, elles ne sont pas supportées par Delphi.

Traversée de types énumération et intervalle

Dans Delphi, il n'est pas possible de traverser non plus les types énumérés ou les intervalles, alors qu'en Free Pascal vous pouvez écrire ce qui suit:

type
  TColor = (clRed, clBlue, clBlack);
  TRange = 'a'..'z';
var
  Color: TColor;
  ch: Char;
begin
  for Color in TColor do
    DoSomething(Color);
  for ch in TRange do
    DoSomethingOther(ch);
end.

Déclarer des énumérateurs

Il n'est pas non plus possible dans Delphi d'ajouter un énumérateur sans modifier la classe, ni d'ajouter un énumérateur d'un type non class/object/record/interface. FPC rend cela possible en utilisant la nouvelle syntaxe operator type Enumerator. Comme dans l'exemple suivant:

type
  TMyRecord = record F1: Integer; F2: array of TMyType; end;
  TMyArrayEnumerator = class
  private
    function GetCurrent: TMyType;
  public
    constructor Create(const A: TMyRecord);
    property Current: TMyType read GetCurrent;
    function MoveNext: Boolean;
  end;

  // C'est le nouveau opérateur intégré.
  operator Enumerator(const A: TMyRecord): TMyArrayEnumerator;
  begin
    Result := TMyArrayEnumerator.Create(A);
  end;

var
  A: MyRecord;
  V: TMyType
begin
  for V in A do
    DoSomething(V);
end.

Traversing UTF-8 strings

As a particularly useful example, the above extension allows very efficient traversal of UTF-8 strings:

uses
  LazUTF8;
interface
type
  { TUTF8StringAsStringEnumerator
    Traversing UTF8 codepoints as strings is useful when you want to know the
    exact encoding of the UTF8 character or if you like to use string
    constants in your code.
    For security reasons you should use the codepoints values (cardinals) instead.
    If speed matters, don't use enumerators. Instead use the PChar directly as 
    shown in the MoveNext method and read about UTF8. It has some interesting features. }

  TUTF8StringAsStringEnumerator = class
  private
    fCurrent: UTF8String;
    fCurrentPos, fEndPos: PChar;
    function GetCurrent: UTF8String;
  public
    constructor Create(const A: UTF8String);
    property Current: UTF8String read GetCurrent;
    function MoveNext: Boolean;
  end;

  operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;

var
  Form1: TForm1;

implementation

operator Enumerator(A: UTF8String): TUTF8StringAsStringEnumerator;
begin
  Result := TUTF8StringAsStringEnumerator.Create(A);
end;

{ TUTF8StringAsStringEnumerator }

function TUTF8StringAsStringEnumerator.GetCurrent: UTF8String;
begin
  Result:=fCurrent;
end;

constructor TUTF8StringAsStringEnumerator.Create(const A: UTF8String);
begin
  fCurrentPos:=PChar(A); // Note: if A='' then PChar(A) returns a pointer to a #0 string
  fEndPos:=fCurrentPos+length(A);
end;

function TUTF8StringAsStringEnumerator.MoveNext: Boolean;
var
  l: Integer;
begin
  if fCurrentPos<fEndPos then
  begin
    l:=UTF8CharacterLength(fCurrentPos);
    SetLength(fCurrent,l);
    Move(fCurrentPos^,fCurrent[1],l);
    inc(fCurrentPos,l);
    Result:=true;
  end else
    Result:=false;
end;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
var
  s, ch: UTF8String;
  i: SizeInt;
begin
  s:='mäßig';

  // using UTF8Length and UTF8Copy this way is slow, requiring O(n)^2
  for i:=1 to UTF8Length(s) do
    writeln('ch=',UTF8Copy(s,i,1));

  // using the above enumerator is shorter and quite fast, requiring O(n)
  for ch in s do
    writeln('ch=',ch);
end;

Using any identifiers instead of builtin MoveNext and Current

In Delphi you must use a function with the name 'MoveNext' and a property with the name 'Current' in enumerators. With FPC you can choose whatever names you wish. This is enabled by the use of the enumerator modifier, with the syntax 'enumerator MoveNext;' and 'enumerator Current;' modifiers. As in the following example:

type
  { TMyListEnumerator }

  TMyListEnumerator = object
  private
    FCurrent: Integer;
  public
    constructor Create;
    destructor Destroy;
    function StepNext: Boolean; enumerator MoveNext;
    property Value: Integer read FCurrent; enumerator Current;
  end;

  TMyList = class
  end;

{ TMyListEnumerator }

constructor TMyListEnumerator.Create;
begin
  FCurrent := 0;
end;

destructor TMyListEnumerator.Destroy;
begin
  inherited;
end;

function TMyListEnumerator.StepNext: Boolean;
begin
  inc(FCurrent);
  Result := FCurrent <= 3;
end;

operator enumerator (AList: TMyList): TMyListEnumerator;
begin
  Result.Create;
end;

var
  List: TMyList;
  i: integer;
begin
  List := TMyList.Create;
  for i in List do
    WriteLn(i);
  List.Free;
end.

Proposed extensions

Get enumerator Position if available

It is impossible to extract any information from the iterator except the current item. Sometimes other data, such as the current index, might be useful:

type
  TUTF8StringEnumerator = class
  private
    FByteIndex: Integer;
    FCharIndex: Integer;
  public
    constructor Create(const A: UTF8String);
    function Current: UTF8Char;
    function CurrentIndex: Integer;
    function MoveNext: Boolean;
  end;

  operator GetEnumerator(A: UTF8String): TUTF8StringEnumerator;
  begin
    Result := TUTF8String.Create(A);
  end;

var
  s: UTF8String;
  ch: UTF8Char;
  i: Integer;
begin

  // Inefficient, as discussed above
  for i := 1 to Length(s) do
    Writeln(i, ': ', ch[i]);

  // Ok, but ugly
  i := 1;
  for ch in s do begin
    Writeln(i, ': ', ch);
    Inc(i);
  end;

  // Proposed extension
  for ch in s index i do
    Writeln(i, ': ', ch);

  // Proposed extension for traversing backwards (equivalent to downto)
  for ch in reverse s do
    Writeln(i, ': ', ch);

  // With proposed index extension
  for ch in reverse s index i do
    Writeln(i, ': ', ch);
end.

Note that index could be designed to return an arbitrary type (i. e. not necessarily an integer). For example, in the case of tree traversal, the index might return an array of nodes describing the path from the tree root to the current node.

Reference