Comment remplacer toutes les occurrences d'un caractère dans une chaîne?

480

Quelle est la manière efficace de remplacer toutes les occurrences d'un personnage par un autre personnage dans std::string?

big-z
la source

Réponses:

742

std::stringne contient pas une telle fonction mais vous pouvez utiliser une replacefonction autonome depuis l'en- algorithmtête.

#include <algorithm>
#include <string>

void some_func() {
  std::string s = "example string";
  std::replace( s.begin(), s.end(), 'x', 'y'); // replace all 'x' to 'y'
}
Kirill V. Lyadvinsky
la source
6
std::stringest un conteneur spécialement conçu pour fonctionner avec des séquences de caractères. lien
Kirill V. Lyadvinsky
164
Malheureusement, cela permet de remplacer un seul caractère par un autre caractère. Il ne peut pas remplacer un caractère par plus de caractères (c'est-à-dire par une chaîne). Existe-t-il un moyen de faire une recherche-remplacement avec plus de caractères?
SasQ
6
@Kirill V. Lyadvinsky Et si je voulais simplement supprimer un événement.
SIFE
4
@ KirillV.Lyadvinsky: Lorsque j'utilise cette méthode pour remplacer tous les x par des y, le résultat est une longue chaîne y, quelle que soit la chaîne d'origine. Je suis curieux de savoir quel serait, selon vous, le problème. (le code est exactement le même que celui que vous avez écrit)
Transcendent
6
@Transcendent: C'est exactement ce qui se passe avec std::string::replace()au lieu de std::replace()! 'x' ( char) est implicitement casté en size_t[valeur 120], ainsi la chaîne entière ou une partie de celle-ci sera remplie avec 120 copies de 'y'.
IBue
127

Je pensais que je jetterais aussi la solution boost :

#include <boost/algorithm/string/replace.hpp>

// in place
std::string in_place = "blah#blah";
boost::replace_all(in_place, "#", "@");

// copy
const std::string input = "blah#blah";
std::string output = boost::replace_all_copy(input, "#", "@");
UncleZeiv
la source
Il vous manque alors quelques -Iindicateurs pour votre compilateur afin qu'il trouve les bibliothèques Boost sur votre système. Vous devrez peut-être même l'installer d'abord.
Martin Ueding
Ce qui précède est plus efficace car il est livré avec la librairie std. Pas tous utilisant la bibliothèque boost ;-)
hfrmobile
122

La question est centrée sur le characterremplacement, mais, comme j'ai trouvé cette page très utile (surtout la remarque de Konrad ), je voudrais partager cette implémentation plus généralisée, qui permet de traiter substringségalement:

std::string ReplaceAll(std::string str, const std::string& from, const std::string& to) {
    size_t start_pos = 0;
    while((start_pos = str.find(from, start_pos)) != std::string::npos) {
        str.replace(start_pos, from.length(), to);
        start_pos += to.length(); // Handles case where 'to' is a substring of 'from'
    }
    return str;
}

Usage:

std::cout << ReplaceAll(string("Number Of Beans"), std::string(" "), std::string("_")) << std::endl;
std::cout << ReplaceAll(string("ghghjghugtghty"), std::string("gh"), std::string("X")) << std::endl;
std::cout << ReplaceAll(string("ghghjghugtghty"), std::string("gh"), std::string("h")) << std::endl;

Les sorties:

Number_Of_Beans

XXjXugtXty

hhjhugthty


ÉDITER:

Ce qui précède peut être implémenté de manière plus appropriée, au cas où les performances vous intéressent, en ne retournant rien ( void) et en effectuant les changements directement sur la chaîne strdonnée en argument, passée par adresse au lieu de par valeur . Cela éviterait une copie inutile et coûteuse de la chaîne d'origine, tout en renvoyant le résultat. Votre appel, alors ...

Code:

static inline void ReplaceAll2(std::string &str, const std::string& from, const std::string& to)
{
    // Same inner code...
    // No return statement
}

J'espère que cela sera utile pour d'autres ...

Gauthier Boaglio
la source
4
Celui-ci a un problème de performances dans les cas où la chaîne source est volumineuse et il existe de nombreuses occurrences de la chaîne à remplacer. string :: replace () serait appelé plusieurs fois, ce qui provoque de nombreuses copies de chaînes. Voir ma solution qui résout ce problème.
minastaros
1
Nit picking ahead: par adresse => par référence . Que ce soit une adresse ou non est un détail d'implémentation.
Max Truxa
1
Vous devez vérifier si la fromchaîne est vide, sinon une boucle sans fin se produira.
débutant le
34

Imaginez un gros objet binaire où tous les octets 0x00 doivent être remplacés par "\ 1 \ x30" et tous les octets 0x01 par "\ 1 \ x31" car le protocole de transport n'autorise aucun \ 0 octet.

Dans les cas où:

  • la chaîne de remplacement et la chaîne à remplacer ont des longueurs différentes,
  • il y a de nombreuses occurrences de la chaîne à remplacer dans la chaîne source et
  • la chaîne source est grande,

les solutions fournies ne peuvent pas être appliquées (car elles ne remplacent que des caractères uniques) ou ont un problème de performances, car elles appellent plusieurs fois string :: replace, ce qui génère des copies de la taille du blob encore et encore. (Je ne connais pas la solution boost, peut-être que c'est OK de ce point de vue)

Celui-ci parcourt toutes les occurrences de la chaîne source et construit la nouvelle chaîne pièce par pièce une fois :

void replaceAll(std::string& source, const std::string& from, const std::string& to)
{
    std::string newString;
    newString.reserve(source.length());  // avoids a few memory allocations

    std::string::size_type lastPos = 0;
    std::string::size_type findPos;

    while(std::string::npos != (findPos = source.find(from, lastPos)))
    {
        newString.append(source, lastPos, findPos - lastPos);
        newString += to;
        lastPos = findPos + from.length();
    }

    // Care for the rest after last occurrence
    newString += source.substr(lastPos);

    source.swap(newString);
}
minastaros
la source
C'est de loin la meilleure solution ici, qui repose uniquement sur la STL. Si vous allez ajouter une fonction personnalisée pour une utilisation facile n'importe où, faites-en celle-ci.
Roger Sanders
21

Une simple recherche et remplacement pour un seul caractère aurait quelque chose comme:

s.replace(s.find("x"), 1, "y")

Pour ce faire, pour la chaîne entière, la chose la plus simple serait de boucler jusqu'à ce que vous s.findcommenciez à revenir npos. Je suppose que vous pouvez également attraper range_errorpour quitter la boucle, mais c'est un peu moche.

TED
la source
7
Bien que ce soit probablement une solution appropriée lorsque le nombre de caractères à remplacer est petit par rapport à la longueur de la chaîne, il ne s'adapte pas bien. À mesure que la proportion de caractères de la chaîne d'origine à remplacer augmente, cette méthode approche O (N ^ 2) dans le temps.
et
7
Vrai. Ma philosophie générale est de faire ce qui est facile (écrire et lire) jusqu'à ce que les inefficacités causent de vrais problèmes. Il y a des circonstances où vous pourriez avoir des cordes gigantesques où O (N ** 2) compte, mais 99% du temps mes cordes sont 1K ou moins.
TED
3
... cela étant dit, j'aime mieux la méthode de Kirill (et je l'avais déjà votée).
TED
Que se passe-t-il si "x" n'est pas trouvé? Aussi, pourquoi utilisez-vous des accolades doubles?
Prasath Govind
@PrasathGovind - Je montrais juste les appels requis (d'où "quelque chose comme"). Des détails importants mais obscurcissants tels que la gestion correcte des erreurs ont été laissés au lecteur comme exercice. Quant aux "doubles accolades", je ne sais pas trop de quoi il s'agit, ni de quoi vous parlez. Pour moi, une "accolade" est le {personnage. Je ne sais pas ce qu'est une "double accolade". Vous avez peut-être un problème de police?
TED
6

Si vous cherchez à remplacer plus d'un seul caractère, et que vous ne traitez qu'avec std::string, cet extrait fonctionnerait, remplaçant sNeedle dans sHaystack par sReplace, et sNeedle et sReplace n'ont pas besoin d'être de la même taille. Cette routine utilise la boucle while pour remplacer toutes les occurrences, plutôt que la première trouvée de gauche à droite.

while(sHaystack.find(sNeedle) != std::string::npos) {
  sHaystack.replace(sHaystack.find(sNeedle),sNeedle.size(),sReplace);
}
Volomike
la source
C'est O (n ^). Vous pourriez le faire en temps O (n).
Changming Sun
3
@ChangmingSun quelle solution O (n) voulez-vous dire?
habakuk
2
Cette boucle sera infinie si kNeedle se trouve être une sous-chaîne de sReplace.
prideout
De plus, il y a un findappel deux fois. Pensez à faire de ce résultat une variable temporaire.
Luc Bloom
4

Comme l'a suggéré Kirill, utilisez la méthode replace ou parcourez la chaîne en remplaçant chaque caractère indépendamment.

Alternativement, vous pouvez utiliser la findméthode ou find_first_ofselon ce que vous devez faire. Aucune de ces solutions ne fera l'affaire en une seule fois, mais avec quelques lignes de code supplémentaires, vous devriez les faire fonctionner pour vous. :-)

Konrad
la source
3
#include <iostream>
#include <string>
using namespace std;
// Replace function..
string replace(string word, string target, string replacement){
    int len, loop=0;
    string nword="", let;
    len=word.length();
    len--;
    while(loop<=len){
        let=word.substr(loop, 1);
        if(let==target){
            nword=nword+replacement;
        }else{
            nword=nword+let;
        }
        loop++;
    }
    return nword;

}
//Main..
int main() {
  string word;
  cout<<"Enter Word: ";
  cin>>word;
  cout<<replace(word, "x", "y")<<endl;
  return 0;
}
Lloydie
la source
Si wordest long, il peut y avoir beaucoup de surcharge lors de l'appel de la fonction. Vous pouvez optimiser cela en passant word, targetet replacementcomme références de const.
TrebledJ
2

Et Abseil StrReplaceAll ? Depuis le fichier d'en-tête:

// This file defines `absl::StrReplaceAll()`, a general-purpose string
// replacement function designed for large, arbitrary text substitutions,
// especially on strings which you are receiving from some other system for
// further processing (e.g. processing regular expressions, escaping HTML
// entities, etc.). `StrReplaceAll` is designed to be efficient even when only
// one substitution is being performed, or when substitution is rare.
//
// If the string being modified is known at compile-time, and the substitutions
// vary, `absl::Substitute()` may be a better choice.
//
// Example:
//
// std::string html_escaped = absl::StrReplaceAll(user_input, {
//                                                {"&", "&amp;"},
//                                                {"<", "&lt;"},
//                                                {">", "&gt;"},
//                                                {"\"", "&quot;"},
//                                                {"'", "&#39;"}});
hotblack944
la source
1

Vieille école :-)

std::string str = "H:/recursos/audio/youtube/libre/falta/"; 

for (int i = 0; i < str.size(); i++) {
    if (str[i] == '/') {
        str[i] = '\\';
    }
}

std::cout << str;

Résultat:

H: \ recursos \ audio \ youtube \ libre \ falta \

Iván Rodríguez
la source
0

Cela marche! J'ai utilisé quelque chose de similaire à cela pour une application de librairie, où l'inventaire était stocké dans un fichier CSV (comme un fichier .dat). Mais dans le cas d'un seul caractère, ce qui signifie que le remplaçant n'est qu'un seul caractère, par exemple '|', il doit être entre guillemets "|" afin de ne pas lancer un caractère de conversion non valide.

#include <iostream>
#include <string>

using namespace std;

int main()
{
    int count = 0;  // for the number of occurences.
    // final hold variable of corrected word up to the npos=j
    string holdWord = "";
    // a temp var in order to replace 0 to new npos
    string holdTemp = "";
    // a csv for a an entry in a book store
    string holdLetter = "Big Java 7th Ed,Horstman,978-1118431115,99.85";

    // j = npos
    for (int j = 0; j < holdLetter.length(); j++) {

        if (holdLetter[j] == ',') {

            if ( count == 0 ) 
            {           
                holdWord = holdLetter.replace(j, 1, " | ");      
            }
            else {

                string holdTemp1 = holdLetter.replace(j, 1, " | ");

                // since replacement is three positions in length,
                // must replace new replacement's 0 to npos-3, with
                // the 0 to npos - 3 of the old replacement 
                holdTemp = holdTemp1.replace(0, j-3, holdWord, 0, j-3); 

                holdWord = "";

                holdWord = holdTemp;

            }
            holdTemp = "";
            count++;
        }
    } 
    cout << holdWord << endl;
    return 0;
}

// result:
Big Java 7th Ed | Horstman | 978-1118431115 | 99.85

De façon inhabituelle, j'utilise actuellement CentOS, donc ma version du compilateur est ci-dessous. La version C ++ (g ++), C ++ 98 par défaut:

g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-4)
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
oOpSgEo
la source
0

Si vous êtes prêt à utiliser std::strings, vous pouvez utiliser la fonction de cet exemple d'application strsubtelle quelle ou la mettre à jour si vous souhaitez qu'elle prenne un type ou un ensemble de paramètres différent pour atteindre à peu près le même objectif. Fondamentalement, il utilise les propriétés et fonctionnalités de std::stringpour effacer rapidement le jeu de caractères correspondant et insérer les caractères souhaités directement dans lestd::string . Chaque fois qu'il effectue cette opération de remplacement, l'offset est mis à jour s'il peut toujours trouver les caractères correspondants à remplacer, et s'il ne peut plus rien remplacer, il renvoie la chaîne dans son état depuis la dernière mise à jour.

#include <iostream>
#include <string>

std::string strsub(std::string stringToModify,
                   std::string charsToReplace,
                   std::string replacementChars);

int main()
{
    std::string silly_typos = "annoiiyyyng syyyllii tiipos.";

    std::cout << "Look at these " << silly_typos << std::endl;
    silly_typos = strsub(silly_typos, "yyy", "i");
    std::cout << "After a little elbow-grease, a few less " << silly_typos << std::endl;
    silly_typos = strsub(silly_typos, "ii", "y");

    std::cout << "There, no more " << silly_typos << std::endl;
    return 0;
}

std::string strsub(std::string stringToModify,
                   std::string charsToReplace,
                   std::string replacementChars)
{
    std::string this_string = stringToModify;

    std::size_t this_occurrence = this_string.find(charsToReplace);
    while (this_occurrence != std::string::npos)
    {
        this_string.erase(this_occurrence, charsToReplace.size());
        this_string.insert(this_occurrence, replacementChars);
        this_occurrence = this_string.find(charsToReplace,
                                           this_occurrence + replacementChars.size());
    }

    return this_string;
}

Si vous ne voulez pas compter sur l'utilisation de std::strings comme paramètres pour pouvoir passer des chaînes de style C à la place, vous pouvez voir l'exemple mis à jour ci-dessous:

#include <iostream>
#include <string>

std::string strsub(const char * stringToModify,
                   const char * charsToReplace,
                   const char * replacementChars,
                   uint64_t sizeOfCharsToReplace,
                   uint64_t sizeOfReplacementChars);

int main()
{
    std::string silly_typos = "annoiiyyyng syyyllii tiipos.";

    std::cout << "Look at these " << silly_typos << std::endl;
    silly_typos = strsub(silly_typos.c_str(), "yyy", "i", 3, 1);
    std::cout << "After a little elbow-grease, a few less " << silly_typos << std::endl;
    silly_typos = strsub(silly_typos.c_str(), "ii", "y", 2, 1);

    std::cout << "There, no more " << silly_typos << std::endl;
    return 0;
}

std::string strsub(const char * stringToModify,
                   const char * charsToReplace,
                   const char * replacementChars,
                   uint64_t sizeOfCharsToReplace,
                   uint64_t sizeOfReplacementChars)
{
    std::string this_string = stringToModify;

    std::size_t this_occurrence = this_string.find(charsToReplace);
    while (this_occurrence != std::string::npos)
    {
        this_string.erase(this_occurrence, sizeOfCharsToReplace);
        this_string.insert(this_occurrence, replacementChars);
        this_occurrence = this_string.find(charsToReplace,
            this_occurrence + sizeOfReplacementChars);
    }

    return this_string;
}
kayleeFrye_onDeck
la source
0

Pour les situations simples, cela fonctionne assez bien sans utiliser d'autre bibliothèque que std :: string (qui est déjà utilisée).

Remplacez toutes les occurrences du caractère a par le caractère b dans some_string :

for (size_t i = 0; i < some_string.size(); ++i) {
    if (some_string[i] == 'a') {
        some_string.replace(i, 1, "b");
    }
}

Si la chaîne est volumineuse ou si plusieurs appels à remplacer sont un problème, vous pouvez appliquer la technique mentionnée dans cette réponse: https://stackoverflow.com/a/29752943/3622300

Guney Ozsan
la source
0

voici une solution que j'ai roulée, dans un esprit DRI maximal. il recherchera sNeedle dans sHaystack et le remplacera par sReplace, nTimes si non 0, sinon toutes les occurrences de sNeedle. il ne cherchera pas à nouveau dans le texte remplacé.

std::string str_replace(
    std::string sHaystack, std::string sNeedle, std::string sReplace, 
    size_t nTimes=0)
{
    size_t found = 0, pos = 0, c = 0;
    size_t len = sNeedle.size();
    size_t replen = sReplace.size();
    std::string input(sHaystack);

    do {
        found = input.find(sNeedle, pos);
        if (found == std::string::npos) {
            break;
        }
        input.replace(found, len, sReplace);
        pos = found + replen;
        ++c;
    } while(!nTimes || c < nTimes);

    return input;
}
non dévoilé
la source