pat56350
-
5 sept. 2012 à 19:16
cs_yanb
Messages postés271Date d'inscriptionlundi 27 octobre 2003StatutMembreDernière intervention 7 juillet 2022
-
12 sept. 2012 à 11:46
Bonjour à tous,
J'aurais besoin de récupérer cycliquement la dernière ligne d'un fichier csv, puis de ranger ses différents champs dans des variables ou tableau de variables.
Nota : je n'ai à ma disposition qu'une version PRO de Delphi 3 (oui, je sais ça commence à dater)
En ce qui concerne la partie décomposition du csv pour rangement dans des variables, j'ai trouvé comment le faire simplement en VB, mais impossible de trouver une fonction équivalente en Delphi.
Merci d'avance pour votre aide,
Ex pour récupération d'un champ en VB :
[Dim fileContents As String
fileContents = My.Computer.FileSystem.ReadAllText("C:\RESULTAT2.csv")
Dim ligne As String = fileContents.Split(vbLf)(4)
Dim champ As String = ligne.Split(",")(2)
TextBox1.Text = champ
Return
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 10 sept. 2012 à 23:33
Bonsoir,
Voici voilà :
type
TArrayOfFields = array of string;
procedure GetFields(FileName: string; Index: Integer; FindText: string; out arrFields: TArrayOfFields);
var
i, j: Integer;
Fields: TStringList;
begin
with TStringList.Create do
try
LoadFromFile(FileName);
Fields := TStringList.Create;
try
for i := 0 to Count - 1 do
begin
//Fields.CommaText := CharReplace(Strings[i], ';', ',');
// si les champs sont séparés par des virgules, CharReplace devient superflu
Fields.CommaText := Strings[i];
if Fields[Index] = FindText then
begin
SetLength(arrFields, Fields.Count);
for j := 0 to Fields.Count - 1 do
arrFields[j] := Fields[j];
Break;
end;
end;
finally
Fields.Free;
end;
finally
Free;
end;
end;
J'ai testé ce code sur un fichier csv de 3,40 Mo, comprenant 36569 lignes de 19 champs chacune.
Le temps d'exécution paraît tout à fait acceptable dans ces conditions.
Evidemment, si l'index se trouve dans les premières lignes, c'est plus rapide que s'il se trouve à la fin.
On doit donc pouvoir améliorer avec une recherche dichotomique...
Les champs sont retournés sour forme d'un tableau dynamique de strings (array of strings) dont l'indice commence à zéro et dont la longueur et réglée automatiquement à l'intérieur de la procedure.
Si le champ "référence" est le premier, on passera en paramètre "index" la valeur "0".
Une TStringList commence toujours à l'index 0, et un tableau dynamique aussi.
On peut utiliser ce code ainsi, le Memo servant à afficher les champs récupérés :
procedure TForm1.btnSearchClick(Sender: TObject);
var
ArrayOfFields: TArrayOfFields;
Reference: string;
i: Integer;
begin
Reference := 'GT01258';
GetFields('TonFichier.csv', 0, Reference, ArrayOfFields);
Memo1.Clear;
for i := 0 to High(ArrayOfFields) do
Memo1.Lines.Add(ArrayOfFields[i]);
end;
Je ne rentre pas plus dans les détails, mais si quelque chose t'échappe, n'hésite pas à demander.
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 6 sept. 2012 à 23:04
Salut,
Si j'ai bien compris la question, et le code en VB qui s'y rapporte, il me semble que le plus simple est d'utiliser une TStringList pour charger le fichier csv, et de l'"éclater" en strings avec sa propriété "CommaText". Puis on récupère le "Strings" correspondant au champ.
Un bout de code valant mieux qu'un long discours embrumé, voici un exemple de récupération du 4ème champ de la dernière ligne d'un fichier csv :
procedure TForm1.Button1Click(Sender: TObject);
var
FileContents: TStringList;
Ligne: string;
SplittedLigne: TStringList;
Champ: string;
begin
{ création de la StringList: }
FileContents := TStringList.Create;
{ chargement du fichier dans la StringList: }
FileContents.LoadFromFile('C:\RESULTAT2.csv');
{ récupération de la dernière "strings", correspondant à la dernière ligne du fichier: }
Ligne := FileContents[FileContents.Count - 1];
{ remplacement éventuel des ";" par des "," afin d'utiliser CommaText: }
Ligne := StringReplace(Ligne, ';', ',', [rfReplaceAll]); ShowMessage(Ligne);
{ on n'a plus besoin de "FileContents", on le libère: }
FileContents.Free;
{ création d'une StringList qui va contenir les champs. On aurait
pu garder la StringList qui a servi précédemment, mais c'est pour
la clarté du code: }
SplittedLigne := TStringList.Create;
{ éclatement de la ligne des champs délimités par des virgules: }
SplittedLigne.CommaText := Ligne;
{ on récupère par exemple le 4ème champ, soit la String[3]: }
Champ := SplittedLigne[3];
{ on libère "SplittedLigne": }
SplittedLigne.Free;
{ affichage du champ dans un Edit: }
Edit1.Text := Champ;
end;
Attention au dépassement d'index, le 4ème champ correspond à String[3], le premier indice d'une TStringList étant toujours 0.
Voici le même code plus "condensé" :
procedure TForm1.Button1Click(Sender: TObject);
var
Champ: string;
begin
with TStringList.Create do
try
LoadFromFile('C:\RESULTAT2.csv');
CommaText := StringReplace(Strings[Count - 1], ';', ',', [rfReplaceAll]);
Champ := Strings[3];
Edit1.Text := Champ;
finally
Free;
end;
end;
Voilà. C'est tout de même plus beau que du VB, non ?
Repost de mon dernier message sans les caractères bizarres je l'espere
Bonjour Korgis,
Merci pour ce morceau de code qui va clairement m’etre precieux…
Il correspond exactement à ce que je souhaite faire.
Il me reste cependant un probleme à regler pour faire fonctionner celui-ci avec mon Delphi 3, car je n’arrive pas à déclarer correctement la fonction StringReplace.
J’ai trouve les deux lignes suivantes à inserer, mais apres diverses tentatives, il est clair que je ne sais pas où les mettre.
Eh oui, c’est ça un debutant en Delphi…
type TReplaceFlags = set of (rfReplaceAll, rfIgnoreCase);
function StringReplace(const S, OldStr, NewStr: string; Flags: TReplaceFlags): string;
Merci encore pour ton aide,
Vous n’avez pas trouvé la réponse que vous recherchez ?
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 7 sept. 2012 à 17:14
Salut,
Peut-être avec D3 faut-il rajouter SysUtils dans les uses ? C'est l'unité où se trouve StringReplace, à partir de D4 du moins...
uses
SysUtils;
Cette fonction permet de remplacer un caractère par un autre, voir l'aide en ligne de Delphi pour plus d'explications (mettre "StringReplace" en surbrillance dans l'éditeur, puis touche F1).
Bah, débutants, faux-débutants, experts, nous sommes tous des amateurs (*) ici
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 7 sept. 2012 à 22:19
Holala...
Je viens de tester StringReplace sur des fichiers csv de diverses tailles.
C'est d'une lenteur catastrophique, j'avais pas idée à ce point...
Du coup j'ai fabriqué viteuf ce petit truc pour voir :
function CharReplace(const S: string; OldChar, NewChar: Char): string;
var
i: Integer;
begin
Result := S;
for i := 1 to Length(Result) do
if Result[i] = OldChar then
Result[i] := NewChar;
end;
C'est tout con...
mais ça va tégévéesquement plus vite!
Bref, si le fichier csv dont tu veux changer les points-virgule en virgules dépasse une certaine taille, je te conseille vivement d'utiliser ma ch'tire fonction à la place de StringReplace.
Le remplacement des points-virgule par des virgules étant obligatoire si l'on veut utiliser la propriété CommaText qui va "éclater" la ligne sélectionnée en champs.
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 7 sept. 2012 à 22:54
Bon... c'est vrai qu'on n'a pas besoin de convertir tout le fichier, une ligne suffit.
Donc tu peux utiliser StringReplace si tu veux, ça ne fait finalement pas une grosse différence au niveau du temps de traitement.
Je viens par contre de m'apercevoir d'une erreur dans mon second code posté, qui fait qu'il est faux et ne donne pas le résultat escompté.
Quoi qu'il en soit, voici ce que tu peux faire, en gardant toujours le même objectif qui est d'extraire le quatrième champ dans la dernière ligne du fichier :
function CharReplace(const S: string; OldChar, NewChar: Char): string;
var
i: Integer;
begin
Result := S;
for i := 1 to Length(Result) do
if Result[i] = OldChar then
Result[i] := NewChar;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
Champ: string;
begin
with TStringList.Create do
try
LoadFromFile(ComboBox1.Text);
CommaText := CharReplace(Strings[Count - 1], ';', ',');
Champ := Strings[3];
Edit1.Text := Champ;
finally
Free;
end;
end;
Pour extraire le 1er champ de la 3ème ligne, on aurait écrit :
Merci pour ton aide, ton explication est très claire, et tout fonctionne parfaitement bien.
Je me permets de te poser une dernière question :
dans le cas ci dessus, je souhaitais récupérer les valeurs correspondant à la dernière ligne, mais comment devrais-je procéder pour récupérer les valeurs d'une ligne dont le premier champ correspondrait à une référence souhaitée par exemple ?
Est-ce possible ?
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 10 sept. 2012 à 15:10
Hello Pat,
"Est-ce possible ?"
Oui. Avec Delphi, tout devient possible.
Mais je me demande si les pistes que je t'ai suggérées sont les bonnes.
Car deux choses :
1) comme tu le sais certainement, Delphi est un outil formidable de gestion des bases de données,
2) je suis désolé de te décevoir, mais je suis une quiche en matière de BDD,
3) heureusement sur ce site, certains touchent plutôt leur bille dans ce domaine...
4) oui, je sais, j'avais dit deux choses...
Or, un fichier csv, c'est quand même un fichier de données, si je ne m'abuse...
Donc, pourrais-tu nous préciser 2 ou 3 points :
- ton fichier csv, quelle taille ?
- ton projet est-il "professionnel" (nécessitant un avis d'expert),
- heu... les séparateurs sont bien des ";" ?
- si tu veux préciser autre chose, y'a pas de problème...
En effet, selon la grosseur du fichier et l'exploitation que tu veux en faire, ils se pourrait que "CommaText", "SringReplace" et compagnie soient inadaptés car trop lents et pas prévus pour un usage axé base de données. Et que mes solutions soient plutôt du "bricolage".
Voilà.
oui, mon projet est professionnel...
l'extrait de fichier sur lequel je fais mes essais fait 2428 ko, le fichier original fait environs 7 à 8 fois cela je pense, car il y a plusieurs années d'historique supplémentaires.
La bonne nouvelle c'est que c'est vraiment un fichier csv séparé avec des "," donc pas de remplacement de caractères à effectuer.
Ton code me va très bien au niveau rapidité...
Concernant ma deuxième question, je te dois une petite explication :
j'ai un deuxième fichier qui lui est beaucoup moins gros, et dont Chacune des lignes commence par un champ "référence" sur lequel j'aimerais lancer ma recherche pour récupérer la ligne, puis les valeurs comme dans le fichier précédent.
Si c'est possible de faire ce genre de tri dans un csv, je suis bien sûr preneur pour le morceau de code...
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 11 sept. 2012 à 13:23
'jour,
On peut gagner en rapidité.
Il suffit de parcourir initialement le fichier et de stocker les références dans une tableau dynamique de chaînes (array of string).
Ensuite, il suffit de comparer la référence recherchée avec les références contenues dans ce tableau.
La position (indice) dans ce tableau nous donne la l'indice de la ligne du fichier où se trouvent les champs recherchés correspondant à cette référence.
Il n'y a plus qu'à aller chercher directement les champs dans cette ligne, sans les parcourir toutes à chaque fois.
C'est bien sûr beaucoup plus rapide, surtout pour les lignes en fin de fichier.
Le principe est simple et facilement adaptable au code ci-dessus.
J'ai déjà testé (le sujet m'intéresse). Si tu veux, je posterai un code de démonstration (qu'il me faudra auparavant mettre en forme).
cs_yanb
Messages postés271Date d'inscriptionlundi 27 octobre 2003StatutMembreDernière intervention 7 juillet 202214 11 sept. 2012 à 13:49
Salut,
@korgis : pour gagner du temps pourquoi pas tout simplement 'Pos'
type
TArrayOfFields = array of string;
procedure GetFields(FileName: string; Index: Integer; FindText: string; out arrFields: TArrayOfFields);
var
i, j: Integer;
Fields: TStringList;
begin
with TStringList.Create do
try
LoadFromFile(FileName);
Fields := TStringList.Create;
try
for i := 0 to Count - 1 do
begin
if Pos(FindText, Strings[i]) <> 0 then
begin
Fields.CommaText := Strings[i];
if Fields[Index] = FindText then
begin
SetLength(arrFields, Fields.Count);
for j := 0 to Fields.Count - 1 do
arrFields[j] := Fields[j];
Break;
end;
end;
end;
finally
Fields.Free;
end;
finally
Free;
end;
end;
ça fonctionne impeccable !
J'ai redécomposé les différents champs dans des variables et c'est exactement ce que je souhaitais. j'ai donc normalement tout en main pour construire mon application.
Merci sincèrement pour ton aide, c'est vraiment sympas.
Pat.
Merci à toi aussi Yanb pour ton morceau de code que j'ai aussi testé.
korgis
Messages postés420Date d'inscriptionsamedi 17 mai 2003StatutMembreDernière intervention 6 mai 201917 11 sept. 2012 à 21:53
Salut,
@yanb : oui, d'accord avec toi, on doit gagner un peu en utilisant Pos puis CommaText seulement si Pos <> 0.
Mais en traitant ligne par ligne à chaque requête on perd du temps, et il vaut mieux, comme je le disais plus haut, stocker les références dans un tableau dynamique qu'il sera plus rapide de parcourir.
J'ai testé, ça va effectivement beaucoup plus vite, surtout quand la référence recherchée n'est pas en début de fichier, et que ce fichier dépasse une certaine taille.
Voici un bout de code qui m'a servi à tester et que j'adapte un peu pour le rendre réutilisable et compréhensible hors contexte du test.
function CharReplace(const S: string; OldChar, NewChar: Char): string;
var
i: Integer;
begin
Result := S;
for i := 1 to Length(Result) do
if Result[i] = OldChar then
Result[i] := NewChar;
end;
type
TArrayOfFields = array of string;
TArrayOfReferences = array of string;
var
ArrayOfReferences: TArrayOfReferences;
procedure GetReferences(FileName: string; Index: Integer; out arrReferences: TArrayOfReferences);
var
i: Integer;
Fields: TStringList;
begin
with TStringList.Create do
try
LoadFromFile(FileName);
SetLength(arrReferences, Count);
Fields := TStringList.Create;
try
for i := 0 to Count - 1 do
begin
Fields.CommaText := CharReplace(Strings[i], ';', ',');
// si les champs sont séparés par des virgules, CharReplace devient superflu
//Fields.CommaText := Strings[i];
arrReferences[i] := Fields[Index];
end;
finally
Fields.Free;
end;
finally
Free;
end;
end;
procedure GetFields(const FileName: string; FindReference: string;
arrReferences: TArrayOfReferences; out arrFields: TArrayOfFields);
var
i, j: Integer;
Fields: TStringList;
begin
for i := 0 to High(arrReferences) do
begin
if arrReferences[i] = FindReference then
begin
with TStringList.Create do
try
LoadFromFile(FileName);
Fields := TStringList.Create;
try
Fields.CommaText := CharReplace(Strings[i], ';', ',');
// si les champs sont séparés par des virgules, CharReplace devient superflu
//Fields.CommaText := Strings[i];
SetLength(arrFields, Fields.Count);
for j := 0 to Fields.Count - 1 do
arrFields[j] := Fields[j];
finally
Fields.Free;
end;
finally
Free;
end;
Break;
end;
end;
end;
On peut appeler la procedure GetReferences dès le choix du fichier csv, afin de récupérer une fois pour toutes les références et les stocker dans un tableau dynamique de chaînes :
puis il suffira de parcourir ce tableau pour connaîntre l'index de ligne, puis aller récupérer les champs correspondant à cette ligne dans le fichier csv, ce que fait la procedure GetFields :
La différence est très sensible avec le code précédent, puisque sur un fichier de 3.40 Mo, 36569 lignes, 19 champs par ligne, les temps d'exécution de la requête sont approximativement de l'ordre de :
1ère méthode : 15 à 200 millisecondes selon l'emplacement dans le fichier,
2ème méthode : 15-16 millisecondes, quel que soit l'emplacement dans le fichier.
@pat56350 : je suis bien content que ça fonctionne, sinon, à quoi ça servirait que korgis il se décarcasse, lol
N'oublie pas de cliquer sur la (les) réponses qui te conviennent, ça ne coûte rien, ça ne rapporte rien non plus (hélas...), ça signale seulement à des gens qui seraient intéressés par le sujet que c'est résolu et qu'il peuvent venir jeter un petit coup d'oeil, ça n'engage à rien et ça peut rapporter gros, relol
cs_yanb
Messages postés271Date d'inscriptionlundi 27 octobre 2003StatutMembreDernière intervention 7 juillet 202214 12 sept. 2012 à 11:46
Salut,
@Korgis : Ah ben oui je suis d'accord avec toi que forcement c'est plus rapide avec un tableau dynamique référence charger au départ ! Mais*
On peut aussi pour info utiliser un TStringList à la place et utiliser IndexOf...
(Je sais plus si elle existe avec D3 par contre)
*Le truc c'est qu'il ne faut pas que la référence change dans ton système...
Après si la référence ne change que très rarement c'est la bonne méthode mais si elle change régulièrement la première méthode avec 'pos' en plus est beaucoup plus approprié...
Du coup j'ai quand même fait un essai pour ne pas dire de conneries avec la première et la deuxième méthode
Voilà ce que j’obtiens avec un fichier de 41Mo,20Colonnes,600000lignes avec systématiquement la dernière ligne de recherche, 20eme colonne a trouver, donc le plus défavorable...échantillon de 100 mesures...
-Méthode 1 moyenne : 1406ms
-Méthode 2 moyenne : 5485ms
Décomposition méthode 2 :
-Méthode chargement des références moyenne : 4563ms
-Méthode recherche ensuite avec tableau préchargé moyenne : 922ms
Donc si on ne charge pas les références trop souvent on va vite gagner du temps, mais si on charge régulièrement et sachant que si la référence recherchée dans la méthode 1 est proche du début du fichier, le temps va diminuer, il va falloir faire un choix judicieux
@+