Remplacer un caractère dans une chaîne [Résolu]

Signaler
Messages postés
20
Date d'inscription
mercredi 15 mars 2006
Statut
Membre
Dernière intervention
19 juin 2008
-
Messages postés
20
Date d'inscription
mercredi 15 mars 2006
Statut
Membre
Dernière intervention
19 juin 2008
-
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

Messages postés
20
Date d'inscription
mercredi 15 mars 2006
Statut
Membre
Dernière intervention
19 juin 2008

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
Messages postés
215
Date d'inscription
mardi 29 juillet 2003
Statut
Membre
Dernière intervention
1 septembre 2006

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
Messages postés
436
Date d'inscription
jeudi 9 janvier 2003
Statut
Membre
Dernière intervention
5 février 2015

tu veux optimiser la fonction stringreplace si je comprends bien ?

DrJerome
Messages postés
20
Date d'inscription
mercredi 15 mars 2006
Statut
Membre
Dernière intervention
19 juin 2008

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.
Messages postés
215
Date d'inscription
mardi 29 juillet 2003
Statut
Membre
Dernière intervention
1 septembre 2006

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
Messages postés
436
Date d'inscription
jeudi 9 janvier 2003
Statut
Membre
Dernière intervention
5 février 2015

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)
Messages postés
215
Date d'inscription
mardi 29 juillet 2003
Statut
Membre
Dernière intervention
1 septembre 2006

Hey un phidéliste, ça fait toujours plaisir d'en croiser un ;) !

j!nH
Messages postés
194
Date d'inscription
dimanche 2 mars 2003
Statut
Membre
Dernière intervention
10 octobre 2006
2
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...
Messages postés
194
Date d'inscription
dimanche 2 mars 2003
Statut
Membre
Dernière intervention
10 octobre 2006
2
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...
Messages postés
215
Date d'inscription
mardi 29 juillet 2003
Statut
Membre
Dernière intervention
1 septembre 2006

Apparemment cela plante, pour les raisons cités au-dessus je pense(adresse sur le tas).

j!nH
Messages postés
436
Date d'inscription
jeudi 9 janvier 2003
Statut
Membre
Dernière intervention
5 février 2015

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
Messages postés
436
Date d'inscription
jeudi 9 janvier 2003
Statut
Membre
Dernière intervention
5 février 2015

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
Messages postés
194
Date d'inscription
dimanche 2 mars 2003
Statut
Membre
Dernière intervention
10 octobre 2006
2
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...
Messages postés
436
Date d'inscription
jeudi 9 janvier 2003
Statut
Membre
Dernière intervention
5 février 2015

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
Messages postés
436
Date d'inscription
jeudi 9 janvier 2003
Statut
Membre
Dernière intervention
5 février 2015

oups on s'est croisé..


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

DrJerome
Messages postés
20
Date d'inscription
mercredi 15 mars 2006
Statut
Membre
Dernière intervention
19 juin 2008

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.
Messages postés
3824
Date d'inscription
vendredi 23 juillet 2004
Statut
Modérateur
Dernière intervention
18 décembre 2020
37
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
Messages postés
215
Date d'inscription
mardi 29 juillet 2003
Statut
Membre
Dernière intervention
1 septembre 2006

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
Messages postés
20
Date d'inscription
mercredi 15 mars 2006
Statut
Membre
Dernière intervention
19 juin 2008

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 !
Messages postés
4202
Date d'inscription
samedi 16 octobre 2004
Statut
Modérateur
Dernière intervention
13 juin 2020
35
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;