Comment rechercher / rechercher et remplacer dans une chaîne standard?

94

Existe-t-il un moyen de remplacer toutes les occurrences d'une sous-chaîne par une autre chaîne dans std::string?

Par exemple:

void SomeFunction(std::string& str)
{
   str = str.replace("hello", "world"); //< I'm looking for something nice like this
}
Adam Tegen
la source
Copie

Réponses:

74

Pourquoi ne pas implémenter votre propre remplacement?

void myReplace(std::string& str,
               const std::string& oldStr,
               const std::string& newStr)
{
  std::string::size_type pos = 0u;
  while((pos = str.find(oldStr, pos)) != std::string::npos){
     str.replace(pos, oldStr.length(), newStr);
     pos += newStr.length();
  }
}
Yves Baumes
la source
3
Vous dérangez un peu la mémoire ici avec tous les appels à "replace": la complexité serait n² si vous supprimiez "o" de "ooooooo ... o". Je suppose qu'on peut faire mieux, mais cette solution a le mérite d'être facile à comprendre.
Zonko du
1
Pourquoi n'est-ce pas une véritable boucle for, plutôt qu'une boucle for obfusquée?
Shirik
J'ai l'habitude d'appliquer le principe de la «moindre surprise». Les boucles For sont destinées à une utilisation simple par incrément d'index, la plupart du temps. Ici, selon moi, une boucle while est plus claire.
yves Baumes
1
@aldo En règle générale, il vaut mieux éviter la complexité et, par exemple, utiliser des expressions régulières comme mentionné dans d'autres réponses. Mais en fonction de vos besoins, vous souhaiterez peut-être contrôler les dépendances de votre projet. Un petit extrait de code qui fait exactement ce dont vous avez besoin, rien de plus, est parfois mieux.
yves Baumes
158
#include <boost/algorithm/string.hpp> // include Boost, a C++ library
...
std::string target("Would you like a foo of chocolate. Two foos of chocolate?");
boost::replace_all(target, "foo", "bar");

Voici la documentation officielle sur replace_all.

TheNamelessOne
la source
1
Notez que vous n'avez pas à créer explicitement std :: string pour le motif et le remplacement: boost :: replace_all (target, "foo", "bar");
Alexis Wilke
4
+1, avec une mise en garde: replace_allsegfault pour les versions de boost> 1.43 sur Sun Studio pour toute version <12.3
Brian Vandenberg
3
boostaugmente considérablement le temps de compilation sur les appareils embarqués. Même quad core ARMv7. 100 lignes de code compilées en 2 minutes, sans boost, 2 secondes.
Piotr Kula
4
@ppumkin: cela signifie que votre compilateur (ou la configuration de la compilation, ou autre) est nul, pas l'architecture cible, qui n'a rien à voir avec cela.
Daniel Kamil Kozar le
Si votre compilateur prend en charge l'en-tête pré-compilé, il est fortement recommandé de l'utiliser lors de l'utilisation de boost. Cela fait vraiment gagner du temps.
Alexey Omelchenko
33

Dans C ++ 11, vous pouvez le faire en tant que ligne unique avec un appel à regex_replace:

#include <string>
#include <regex>

using std::string;

string do_replace( string const & in, string const & from, string const & to )
{
  return std::regex_replace( in, std::regex(from), to );
}

string test = "Remove all spaces";
std::cout << do_replace(test, " ", "") << std::endl;

production:

Removeallspaces
Brent Bradburn
la source
Merci, très facile à utiliser et à retenir!
Julian Declercq
Notez également qu'il frompeut s'agir d'une expression régulière - vous pouvez donc utiliser des critères de correspondance plus sophistiqués si vous en avez besoin. Ce que je ne vois pas, c'est comment faire cela sans appliquer une forme d'analyse d'expressions régulières - à la place en utilisant uniquement une interprétation directe des fromcaractères.
Brent Bradburn
Cela peut nécessiter un compilateur à jour. Cela fonctionnait avec gcc 5.0, mais j'ai eu quelques problèmes avec gcc 4.8.4.
Brent Bradburn
@nobar, ouais, si je me souviens bien, le support regex dans 4.8.x n'était pas complet. Vous pouvez également avoir des recherches plus sophistiquées, mais vous êtes pénalisé dans le temps ... Cela va être plus lent que les autres fonctions de recherche et de remplacement plus simples.
Alexis Wilke
2
Veuillez noter que cela ne fonctionnera que pour les caractères alphanumériques très basiques et rien d'autre sans faire beaucoup de prétraitement en fonction du type de chaîne. Je n'ai pas encore trouvé de remplacement de chaîne basé sur une expression régulière à usage général.
Piyush Soni
17

Pourquoi ne pas renvoyer une chaîne modifiée?

std::string ReplaceString(std::string subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
    return subject;
}

Si vous avez besoin de performances, voici une fonction optimisée qui modifie la chaîne d'entrée, elle ne crée pas de copie de la chaîne:

void ReplaceStringInPlace(std::string& subject, const std::string& search,
                          const std::string& replace) {
    size_t pos = 0;
    while((pos = subject.find(search, pos)) != std::string::npos) {
         subject.replace(pos, search.length(), replace);
         pos += replace.length();
    }
}

Tests:

std::string input = "abc abc def";
std::cout << "Input string: " << input << std::endl;

std::cout << "ReplaceString() return value: " 
          << ReplaceString(input, "bc", "!!") << std::endl;
std::cout << "ReplaceString() input string not changed: " 
          << input << std::endl;

ReplaceStringInPlace(input, "bc", "??");
std::cout << "ReplaceStringInPlace() input string modified: " 
          << input << std::endl;

Production:

Input string: abc abc def
ReplaceString() return value: a!! a!! def
ReplaceString() input string not modified: abc abc def
ReplaceStringInPlace() input string modified: a?? a?? def
Czarek Tomczak
la source
6

Mon modèle de recherche et de remplacement sur place en ligne:

template<class T>
int inline findAndReplace(T& source, const T& find, const T& replace)
{
    int num=0;
    typename T::size_t fLen = find.size();
    typename T::size_t rLen = replace.size();
    for (T::size_t pos=0; (pos=source.find(find, pos))!=T::npos; pos+=rLen)
    {
        num++;
        source.replace(pos, fLen, replace);
    }
    return num;
}

Il renvoie un décompte du nombre d'éléments substitués (à utiliser si vous souhaitez exécuter successivement ceci, etc.). Pour l'utiliser:

std::string str = "one two three";
int n = findAndReplace(str, "one", "1");
Marius
la source
4
J'ai essayé cet exemple sous GCC mais il n'a pas été compilé - il n'aimait pas l'utilisation de T :: size_t. Le remplacement de T :: size_t par le nom de type T :: size_type résout le problème.
Andrew Wyatt
3

Le moyen le plus simple (offrant quelque chose qui ressemble à ce que vous avez écrit) est d'utiliser Boost.Regex , en particulier regex_replace .

std :: string a intégré les méthodes find () et replace (), mais elles sont plus lourdes à utiliser car elles nécessitent de traiter les index et les longueurs de chaîne.

Alan
la source
3
Il y a aussi les algorithmes de chaînes de boost, y compris replace_all (regex pourrait être un peu lourd pour une substitution aussi simple).
UncleBens
3

Je pense que cela fonctionnerait. Il prend const char * comme paramètre.

//params find and replace cannot be NULL
void FindAndReplace( std::string& source, const char* find, const char* replace )
{
   //ASSERT(find != NULL);
   //ASSERT(replace != NULL);
   size_t findLen = strlen(find);
   size_t replaceLen = strlen(replace);
   size_t pos = 0;

   //search for the next occurrence of find within source
   while ((pos = source.find(find, pos)) != std::string::npos)
   {
      //replace the found string with the replacement
      source.replace( pos, findLen, replace );

      //the next line keeps you from searching your replace string, 
      //so your could replace "hello" with "hello world" 
      //and not have it blow chunks.
      pos += replaceLen; 
   }
}
Adam Tegen
la source
Étant donné que size_typepour une chaîne est unsigned, votre >=vérification de la condition de boucle sera toujours true. Vous devez l'utiliser std::string::nposlà-bas.
Pavel Minaev
size_type n'est pas non signé. Il n'est pas signé sur de nombreuses plates-formes, mais pas sur toutes.
Alan le
12
Pourquoi diable ne fait-il pas partie de std :: string? Existe-t-il une autre classe String sérieuse dans le monde de la programmation qui n'offre pas d'opération «rechercher et remplacer»? C'est sûrement plus courant que d'avoir deux itérateurs et de vouloir remplacer le texte entre eux ?? Parfois, std :: string ressemble à une voiture avec un pare-brise à spectre réglable mais aucun moyen de faire rouler la vitre du conducteur.
Spike0xff
@ Spike0xff boost aroll_down_window
ta.speot.is
1
@gustafr: Mon erreur. J'ai travaillé sur des systèmes où les anciens compilateurs définissaient incorrectement size_t.
Alan
1
// Replace all occurrences of searchStr in str with replacer
// Each match is replaced only once to prevent an infinite loop
// The algorithm iterates once over the input and only concatenates 
// to the output, so it should be reasonably efficient
std::string replace(const std::string& str, const std::string& searchStr, 
    const std::string& replacer)
{
    // Prevent an infinite loop if the input is empty
    if (searchStr == "") {
        return str;
    }

    std::string result = "";
    size_t pos = 0;
    size_t pos2 = str.find(searchStr, pos);

    while (pos2 != std::string::npos) {
        result += str.substr(pos, pos2-pos) + replacer;
        pos = pos2 + searchStr.length();
        pos2 = str.find(searchStr, pos);
    }

    result += str.substr(pos, str.length()-pos);
    return result;
}
Björn Ganster
la source
1
Nous avons seulement besoin de rechercher de nouvelles correspondances à partir de la dernière correspondance, c'est pourquoi l'algorithme suit attentivement la dernière correspondance en pos. pos2 stocke toujours la correspondance suivante, donc nous concaténons la chaîne entre pos et pos2 au résultat, puis avancons pos et pos2. Si aucune autre correspondance ne peut être trouvée, nous concaténons le reste de la chaîne pour obtenir le résultat.
Björn Ganster
1
#include <string>

using std::string;

void myReplace(string& str,
               const string& oldStr,
               const string& newStr) {
  if (oldStr.empty()) {
    return;
  }

  for (size_t pos = 0; (pos = str.find(oldStr, pos)) != string::npos;) {
    str.replace(pos, oldStr.length(), newStr);
    pos += newStr.length();
  }
}

Il est important de vérifier que oldStr est vide. Si pour une raison quelconque ce paramètre est vide, vous serez coincé dans une boucle infinie.

Mais oui, utilisez la solution éprouvée C ++ 11 ou Boost si vous le pouvez.

Ericcurtin
la source