Extraction sur fichier csv [Résolu]

- - Dernière réponse : cs_yanb
Messages postés
260
Date d'inscription
lundi 27 octobre 2003
Dernière intervention
4 mars 2016
- 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
Afficher la suite 

Votre réponse

15 réponses

Meilleure réponse
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
3
Merci
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.

Bon coding.

Dire « Merci » 3

Quelques mots de remerciements seront grandement appréciés. Ajouter un commentaire

Codes Sources 95 internautes nous ont dit merci ce mois-ci

Commenter la réponse de korgis
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
0
Merci
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 ?
Commenter la réponse de korgis
0
Merci
Bonjour Korgis,

Merci pour ce morceau de code qui va clairement mâ���ªtre pr�©cieuxâ�Š
Il correspond exactement �  ce que je souhaite faire.
Il me reste cependant un probl�šme �  r�©gler pour faire fonctionner celui-ci avec mon Delphi 3, car je nâ��arrive pas �  d�©clarer correctement la fonction �«� StringReplace� �» .
Jâ��ai trouv�© les deux lignes suivantes �  ins�©rer, mais apr�šs diverses tentatives, il est clair que je ne sais pas o�¹ les mettre.
Eh oui, câ��est �§a un d�©butant en Delphiâ�Š

type TReplaceFlags = set of (rfReplaceAll, rfIgnoreCase);

function StringReplace(const S, OldStr, NewStr: string; Flags: TReplaceFlags): string;


Merci encore pour ton aide,
Commenter la réponse de pat56350
0
Merci
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,
Commenter la réponse de pat56350
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
0
Merci
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

(*) amateur : qui a du goût pour, qui aime...
Commenter la réponse de korgis
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
0
Merci
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.
Commenter la réponse de korgis
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
0
Merci
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 :

CommaText := CharReplace(Strings[2], ';', ','); // 3ème ligne
Champ := Strings[0];  // 1er champ


le 1er champ de la 1ère ligne :

CommaText := CharReplace(Strings[0], ';', ','); // 1ère ligne
Champ := Strings[0];  // 1er champ


Voilà, j'espère que ça n'est pas trop confus et que je ne t'ai pas trop embrouillé...
Commenter la réponse de korgis
0
Merci
Bonsoir Korgis,

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 ?

Merci encore à toi de me consacrer ce temps...

Pat.
Commenter la réponse de pat56350
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
0
Merci
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à.

à +
Commenter la réponse de korgis
0
Merci
Bonsoir Korgis,

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...

Cordialement,

Pat
Commenter la réponse de pat56350
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
0
Merci
'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).
Commenter la réponse de korgis
Messages postés
260
Date d'inscription
lundi 27 octobre 2003
Dernière intervention
4 mars 2016
0
Merci
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;

@+
Commenter la réponse de cs_yanb
0
Merci
Merci Korgis,

ç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é.
Commenter la réponse de pat56350
Messages postés
423
Date d'inscription
samedi 17 mai 2003
Dernière intervention
4 août 2018
0
Merci
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 :

GetReferences('MonFichier.csv', Index_De_Colonne_Reference, ArrayOfReferences);


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 :

GetFields('MonFichier.csv', Reference, ArrayOfReferences, ArrayOfFields);


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

Bonne continuation...
Commenter la réponse de korgis
Messages postés
260
Date d'inscription
lundi 27 octobre 2003
Dernière intervention
4 mars 2016
0
Merci
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
@+
Commenter la réponse de cs_yanb

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.