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

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
0
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)
0
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
0
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...
0
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...
0
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
0
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
0
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
0
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...
0
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
0
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
0
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.
0
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
0
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
0
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 !
0
f0xi Messages postés 4205 Date d'inscription samedi 16 octobre 2004 Statut Modérateur Dernière intervention 12 mars 2022 35
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;

0
Rejoignez-nous