Salut à tous
Je vous expose une petite recherche personnelle.
Pour reprendre le paragraphe Extension proposée du Wiki FPC ajouté par Gilles, je suggère une solution employant un énumérateur (dit aussi itérateur) générique. L'idée m'est venue en voulant décomposer une chaîne contenant des sous-chaînes séparées par des ';' (une ligne de fichier csv pour tout dire) en vue de proposer une alternative à l'emploi d'une TStringList.
La solution étant basée sur la généricité, l'énumérateur générique est récupérable pour d'autres usages, par exemple pour parcourir une liste chaînée en simulant un indice qui ne serait pas disponible directement dans l'interface de la liste elle-même. Le code est relativement simple mais appelle quelques explications.
La documentation standard sur les génériques est plutôt réduite et les énumérateurs n'y apparaissent même pas, seuls quelques paragraphes sont disponibles dans le Wiki. Ce qui est dommage car cela n'encourage pas l'emploi de ces mécanismes modernes et fort utiles. En outre, l'implémentation que je propose met en jeu aussi une type interne.
Un énumérateur (ou itérateur) permet d'accéder à une suite d'éléments depuis un structure conteneur (par exemple les éléments d'un tableau) via la boucle for..in.
Cependant si nous voulons un indice sur ces données, il faut déclarer et incrémenter une variable entière supplémentaire car la construction for..in n'expose pas un tel indice. Ma solution consiste à embarquer dans l'itérateur la gestion de l'indice et d'en permettre l'accès. Bon montrons le code ce sera plus parlant.
Déclaration du type générique:
Spécialisation et dérivation du type générique pour notre exemple.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 generic TGIndicedEnumerator<TContainer, TElement> = class public type TIndicedValue = record Value: TElement; Ind: Integer; end; protected FCntnr: TContainer; FCurrent: TIndicedValue; public constructor Create(aCntnr: TContainer); virtual; function MoveNext: Boolean; virtual; abstract; property Current: TIndicedValue read FCurrent; function GetEnumerator: TGIndicedEnumerator; end;
L'implémentation des types est la suivante :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10 TCsvAnsiStringFieldEnumerator = class(specialize TGIndicedEnumerator<AnsiString, AnsiString>) private FCurrPos: Integer; FSeparator: Char; public // On introduit ici la notion de séparateur. constructor Create(aString: AnsiString; Separator: AnsiChar); overload; function MoveNext: Boolean; override; end;
Le type générique reprend les méthodes et proriétés nécessaires à la mise en oeuvre de l'énumérateur (MoveNext, Current), j'ai ajouté GetEnumerator car l'énumérateur servira aussi de conteneur en quelque sorte.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 { TGIndicedEnumerator } constructor TGIndicedEnumerator.Create(aCntnr: TContainer); begin FCntnr := aCntnr; end; function TGIndicedEnumerator.GetEnumerator: TGIndicedEnumerator; begin Result := Self; end; { TCsvAnsiStringFieldEnumerator } constructor TCsvAnsiStringFieldEnumerator.Create(aString: AnsiString; Separator: AnsiChar); begin inherited Create(aString); FCurrPos := 0; FSeparator := Separator; FCurrent.Ind := -1; end; function TCsvAnsiStringFieldEnumerator.MoveNext: Boolean; begin if FCurrPos > Length(FCntnr) then exit(False); // Premier élément Inc(FCurrent.ind); FCurrent.Value := ''; Inc(FCurrPos); while (FCurrPos <= Length(FCntnr)) and (FCntnr[FCurrPos] <> FSeparator) do begin FCurrent.Value += FCntnr[FCurrPos]; Inc(FCurrPos); end; Result := True; end;
Les marques substitutives TContainer et TElement ne sont pas encore des types, les types effectifs seront précisés lors de la spécialisation (mot-clé specialize).
Le type TIndicedValue qui définit la paire (Valeur, Indice) est interne à la classe générique afin de bénéficier des mêmes identificateurs de types génériques et aussi pour être sûr que les types sont identiques après spécialisation.
En fait, le plus surprenant (pour moi) a été que la méthode GetEnumerator: TGIndicedEnumerator; change correctement de type de retour après la spécialisation (i.e. ce dernier devient TCsvAnsiStringFieldEnumerator), je pensais que cette opération ne s'intéressait qu'aux marques substitutives, c'est une excellente nouvelle sinon ma conception initiale aurait été remise en question, il aurait fallu créer cette méthode dans les types dérivés, accroissant le code à écrire. L'ajout de cette méthode est nécessaire car l'itérateur est obtenu directement depuis l'objet itérateur lui-même.
Le code de décomposition d'une chaîne devient très simple, l'indice permet de savoir quelle composante de la chaîne on manipule (la première d'incide 0, la suivante d'indice 1...). Il se ramène à une simple boucle.
Le type de la variable IndStr est TIndicedValue mais qualifié par le type spécialisé, l'ensemble constitue donc aussi un type particulier à part entière.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 #!/usr/bin/instantfpc // pour emploi avec instantfpc, // remplacer la première ligne par "program TestCsv;" si vous n'utilisez pas InstantFPC. {$mode objfpc}{$H+} uses SysUtils, IndicedEnumerators; var IndStr: TCsvAnsiStringFieldEnumerator.TIndicedValue; begin for IndStr in TCsvAnsiStringFieldEnumerator.Create(ParamStr(1),';') do WriteLn(Format('%d : "%s"', [IndStr.Ind, IndStr.Value])); end.
Exemple d'appel en ligne de commande (nom du fichier source pascal avec des droit d'exécution sous Linux + un paramètre)
Laissez un commentaire si cela est difficile à faire sous Windows, on verra ce qu'on peut faire.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2./TestStringFieldEnums.pas "petit;programme;pascal;pour;faire;un;test"
Voilà, j'espère vous avoir exposé quelque chose d'utile, intéressant et compréhensible.
Qu'en pensez-vous ?
NB : pour les administrateurs : les mots-clés generic et specialize ne sont pas mis en évidence comme les autres mots-clés, pouvez-vous corriger cela dans le site ?
Partager