Remplacer un caractère dans une chaîne

Résolu
Fred_Ca_Pulse Messages postés 20 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 19 juin 2008 - 16 août 2006 à 09:44
Fred_Ca_Pulse Messages postés 20 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 19 juin 2008 - 17 août 2006 à 11:05
Bonjour à tous

Pour otimiser mon code, j'ai voulu transformer en procédure la fonction de remplacement que j'utilise depuis longtemps déjà :

function CaractereRemplacer( CSource, CDestination : Char; const SChaine : String ) : String;
var
   ILong : Longint;
   Source, Dest : PChar;
begin
     ILong := Length( SChaine );
     SetLength( Result, ILong );
     Source := Pointer( SChaine );
     Dest := Pointer( Result );
     while ILong <> 0 do begin
           if Source^ = CSource
              then Dest^ := CDestination
              else Dest^ := Source^;
           Inc( Source );
           Inc( Dest );
           Dec( ILong );
     end;
end;


Son équivalent en procédure me semblait assez évident :

procedure CaractereRemplacerP( CSource, CDestination : Char; var SChaine : String );
var
   ILong : Longint;
   Source : PChar;
begin
     ILong := Length( SChaine );
     Source := Pointer( SChaine );
     while ILong <> 0 do begin
           if Source^ = CSource
              then Source^ := CDestination;
           Inc( Source );
           Dec( ILong );
     end;
end;

Mais, bien sûr , çà ne marche pas comme je voudrais  :
 j'obtiens une merveilleuse "Violation d'accès" lorqu'un caractère doit être remplacé par Source^ := CDestination

J'ai bien pensé à un problème de référencement des chaînes et j'ai donc ajouté la ligne
"UniqueString( SChaine );" en début de procédure :
çà fonctionne alors mais les performances ne sont plus à la hauteur !

Si vous avez une petite îdée sur la question, merci de m'en faire part.

25 réponses

Fred_Ca_Pulse Messages postés 20 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 19 juin 2008
16 août 2006 à 11:24
Effectivement Jinh a raison :
- sa procédure marche
- le "@SChaine[1]" doit bien être équivalent à "UniqueString" : la vitesse d'exécution est la même

Cependant cette vitesse ne semble pas pouvoir être meilleure que celle de la version fonction :
- environ 10% moins rapide sur mes tests
- dans les 2 cas la chaîne résultante doit être allouée : mais en fonction la copie des caractères ne nécessite qu'une passe au lieu de 2

Il semble que le problème est le suivant :
- soit le cas simple
STest1 := 'toto';
STest2 := STest1; // Poiteur sur la même zone mémoire pour le moment !
CaractereRemplacerP( 'o', 'a', STest2 );
- si la procédure faisait le remplacement directement (sans UniqueString) 
. on aurait au final : STest1 = STest2 = 'tata'
. c'est pas vraiment le but car il ne faut pas modifier STest1
- Delphi est donc très fort (qui en doutait !) : il a raison de planter
- on n'optimise donc rien en transformant la fonction en procédure
jinh68 Messages postés 215 Date d'inscription mardi 29 juillet 2003 Statut Membre Dernière intervention 1 septembre 2006
16 août 2006 à 11:30
Très beau bilan ;)!


Je pense qu'en réalité, tu obtiendrais les meilleurs gain en performance en passant par l'asm(sur phidels.com, tu trouveras quelques posts traitant de ce sujet si cela t'intéresse ;)).

j!nH
DRJEROME Messages postés 436 Date d'inscription jeudi 9 janvier 2003 Statut Membre Dernière intervention 5 février 2015
16 août 2006 à 10:04
tu veux optimiser la fonction stringreplace si je comprends bien ?

DrJerome
Fred_Ca_Pulse Messages postés 20 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 19 juin 2008
16 août 2006 à 10:26
Oui, en quelque sorte, car ma fonction correspond plutot à un "CharReplace".
La fonction marche très bien et est environ 12 fois plus rapide que son équivalent effectué par stringreplace.
En procédure elle devrait être plus rapide encore ... si çà marchait.

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
jinh68 Messages postés 215 Date d'inscription mardi 29 juillet 2003 Statut Membre Dernière intervention 1 septembre 2006
16 août 2006 à 10:43
Salut,

L'adresse du  premier caractère d'une string Delphi n'est pas équivalent à l'adresse du string elle-même(préambule de 8 octets: compteur de référence + longueur).

Cette version marche, dis nous si les performances sont meilleures(même si je pense que l'appel à @SChaine[1] revient à faire une UniqueString(sTaChaine), puisque cela force à placer l'adresse sur le tas).

procedure CaractereRemplacerP( CSource, CDestination : Char; var SChaine : String );
var
   ILong : Longint;
   Source : PChar;
begin
     ILong := Length( SChaine );
     Source := Pointer( @SChaine[1] );
     while ILong <> 0 do begin
           if Source^ = CSource
              then Source^ := CDestination;
           Inc( Source );
           Dec( ILong );
     end;
end;

j!nH
DRJEROME Messages postés 436 Date d'inscription jeudi 9 janvier 2003 Statut Membre Dernière intervention 5 février 2015
16 août 2006 à 10:49
salut Jinh

ça m'a l'air excellent... en plus tu n'utilises pas de setlength : pour l'optimisation ça doit être bon ;)

DrJerome (ou JROD)
jinh68 Messages postés 215 Date d'inscription mardi 29 juillet 2003 Statut Membre Dernière intervention 1 septembre 2006
16 août 2006 à 10:52
Hey un phidéliste, ça fait toujours plaisir d'en croiser un ;) !

j!nH
Emandhal Messages postés 194 Date d'inscription dimanche 2 mars 2003 Statut Membre Dernière intervention 10 octobre 2006 3
16 août 2006 à 10:57
Pourquoi ne pas remplacer Pointer( @SChaine[1] ); par PChar(SChaine); ?


Tout problème a sa solution... Mais en général, celle que l'on trouve n'est jamais la bonne...
Emandhal Messages postés 194 Date d'inscription dimanche 2 mars 2003 Statut Membre Dernière intervention 10 octobre 2006 3
16 août 2006 à 10:59
procedure CaractereRemplacerP( CSource, CDestination : Char; var SChaine : String );
var
   Source : PChar;
begin
     Source := PChar( SChaine );
     while Source^<>#0 do
     begin
           if Source^ = CSource then Source^ := CDestination;
           Inc( Source );
     end;
end;

Tout problème a sa solution... Mais en général, celle que l'on trouve n'est jamais la bonne...
jinh68 Messages postés 215 Date d'inscription mardi 29 juillet 2003 Statut Membre Dernière intervention 1 septembre 2006
16 août 2006 à 11:01
Apparemment cela plante, pour les raisons cités au-dessus je pense(adresse sur le tas).

j!nH
DRJEROME Messages postés 436 Date d'inscription jeudi 9 janvier 2003 Statut Membre Dernière intervention 5 février 2015
16 août 2006 à 11:09
Emandhal voulait-il dire : 
<hr />
procedure CaractereRemplacerP( CSource, CDestination : Char; var SChaine : String );
var
   Source : PChar;
begin
     Source := Pointer( @SChaine[1] );
     while Source^<>#0 do
     begin
           if Source^ = CSource then Source^ := CDestination;
           Inc( Source );
     end;
end;
<hr />

DrJerome
DRJEROME Messages postés 436 Date d'inscription jeudi 9 janvier 2003 Statut Membre Dernière intervention 5 février 2015
16 août 2006 à 11:12
ou même :
<hr />
procedure CaractereRemplacerP( CSource, CDestination : Char; var SChaine : String );
var
   Source : PChar;
begin
     Source := @SChaine[1];
     while Source^<>#0 do
     begin
           if Source^ = CSource then Source^ := CDestination;
           Inc( Source );
     end;
end;
<hr />

DrJerome
Emandhal Messages postés 194 Date d'inscription dimanche 2 mars 2003 Statut Membre Dernière intervention 10 octobre 2006 3
16 août 2006 à 11:24
heuuu ui désolé, sinon la chaine est stockée dans le segment de code qui est protégé en écriture.

Tout problème a sa solution... Mais en général, celle que l'on trouve n'est jamais la bonne...
DRJEROME Messages postés 436 Date d'inscription jeudi 9 janvier 2003 Statut Membre Dernière intervention 5 février 2015
16 août 2006 à 11:26
c'est équivalent à
<hr />
procedure CaractereRemplacerP( CSource, CDestination : Char; var SChaine : String );
var
   Source : PChar;
begin
     Source := @SChaine[1];
     while Source^<>^@ do
     begin
           if Source^ = CSource then Source^ := CDestination;
           Inc( Source );
     end;
end;
<hr />

DrJerome
DRJEROME Messages postés 436 Date d'inscription jeudi 9 janvier 2003 Statut Membre Dernière intervention 5 février 2015
16 août 2006 à 11:30
oups on s'est croisé..


donc en résumé, la fonction c'est mieux que la procédure

DrJerome
Fred_Ca_Pulse Messages postés 20 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 19 juin 2008
16 août 2006 à 11:39
L'asm doit peut-être donner une meilleure performance ... mais j'y connais rien du tout ... alors on va se contenter de la fonction :


function CaractereRemplacer( CSource, CDestination : Char; const SChaine : String ) : String;
var
   ILong : Longint;
   Source, Dest : PChar;
begin
     ILong := Length( SChaine );
     SetLength( Result, ILong );
     Source := Pointer( SChaine );
     Dest := Pointer( Result );
     while ILong <> 0 do begin
           if Source^ = CSource
              then Dest^ := CDestination
              else Dest^ := Source^;
           Inc( Source );
           Inc( Dest );
           Dec( ILong );
     end;
end;

Qui fonctionne bien. Merci à tous.
Cirec Messages postés 3833 Date d'inscription vendredi 23 juillet 2004 Statut Modérateur Dernière intervention 18 septembre 2022 50
16 août 2006 à 13:49
Salut,


si j'ai bien compris ta fonction est plus rapide que l'équvalent en procédure

Essaye celle-ci, en fait c'est exactement la même que la fonction mais en procédure

Procedure CaractereRemplacer( CSource, CDestination : Char; const SChaine : String; out result : String );
var
   ILong : Longint;
   Source, Dest : PChar;
begin
     ILong := Length( SChaine );
     SetLength( Result, ILong );
     Source := Pointer( SChaine );
     Dest := Pointer( Result );
     while ILong <> 0 dobegin
           if Source^ = CSource
              then Dest^ := CDestination
              else Dest^ : = Source^;
           Inc( Source );
           Inc( Dest );
           Dec( ILong );
     end;
end;






// Utilisation :


procedure TfrmMain.Button6Click(Sender: TObject);
Var Tmp : String;
begin
  CaractereRemplacer('e', 'i', 'Teste de remplacement de caractères', Tmp);
  Label1.Caption := Tmp;
end;

Dis moi ce que ça donne !














@+
Cirec
jinh68 Messages postés 215 Date d'inscription mardi 29 juillet 2003 Statut Membre Dernière intervention 1 septembre 2006
16 août 2006 à 14:29
Il y'aura un petit problème s'il passe Tmp en entrée et qu'il veut récupérer la même chaine en sortie ;).

j!nH
Fred_Ca_Pulse Messages postés 20 Date d'inscription mercredi 15 mars 2006 Statut Membre Dernière intervention 19 juin 2008
16 août 2006 à 14:59
Effectivement Cirec, ta procédure ne marche pas dans le cas d'appels du type :

Tmp := 'Teste de remplacement de caractères';
CaractereRemplacer('e', 'i', Tmp, Tmp);

La variable de sortie est mise à vide.
Dommage car effectivement dans le cas que tu donne les performances sont meilleures que ma fonction initiale.

Par contre j'ai essayé :
procedure CaractereRemplacer( CSource, CDestination : Char; const SChaine : String; var Result : String );
var
   ILong : Longint;
   Source, Dest : PChar;
begin
     ILong := Length( SChaine );
     if Length( Result ) <> ILong
        then SetLength( Result, ILong ); // Réallocation si nécessaire
     Source := Pointer( SChaine );
     Dest := Pointer( Result );
     while ILong <> 0 do begin
           if Source^ = CSource
              then Dest^ := CDestination
              else Dest^ := Source^;
           Inc( Source );
           Inc( Dest );
           Dec( ILong );
     end;
end;

Sur un appel du type
CaractereRemplacer('e', 'i', Tmp, Tmp);
cela donne une superbe démonstration du même plantage que ma procédure initiale (celle sans UniqueString) :
'Violation d'accès'.
Et effectivement là çà me parait tout à fait logique : on veut écrire dans une chaîne constante et donc en lecture seule.

Qu'est-ce qu'ils sont durs à gober ces fichus pointeurs !
f0xi Messages postés 4205 Date d'inscription samedi 16 octobre 2004 Statut Modérateur Dernière intervention 12 mars 2022 34
16 août 2006 à 16:14
28.3 .. 29.8 ms pour 100Kr

// S, cOld et cNew en const = -37..62 ms pour 100Kr
function ReplaceChar(const S : string; const cOld, cNew : Char) : String;
var
   pResult : PChar;
   i       : integer;
begin
     SetLength(Result,Length(S));
     // ne pas utiliser de PChar sur S = -8..14 ms pour 100Kr
     pResult := pChar(Result);
     i := 1;
     // repeat plus rapide que while = -10..40 ms pour 100Kr
     repeat
        // assignation directe du caractere = -1..3 ms pour 100Kr
        pResult[0] := S[i];
        if pResult[0] = cOld then
           pResult[0] := cNew;
        inc(pResult);
        inc(i);
     until S[i] = #0;
end;

Rejoignez-nous