Wie suche / finde und ersetze ich in einer Standardzeichenfolge?

Antworten:

74

Warum implementieren Sie nicht Ihren eigenen Ersatz?

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
quelle
3
Sie spielen hier ein bisschen mit dem Speicher herum, mit all den Aufrufen zum "Ersetzen": Komplexität wäre n², wenn Sie "o" aus "ooooooo ... o" entfernen. Ich denke, man kann es besser machen, aber diese Lösung hat den Vorteil, dass sie leicht zu verstehen ist.
Zonko
1
Warum ist dies keine tatsächliche for-Schleife, sondern eine verschleierte for-Schleife?
Shirik
Ich bin es gewohnt, das Prinzip der geringsten Überraschung anzuwenden. For-Schleifen werden meistens für einfache Indexinkremente verwendet. Hier ist meiner Meinung nach eine while-Schleife klarer.
Yves Baumes
1
@aldo In der Regel ist es besser, Komplexität zu vermeiden und beispielsweise Regex zu verwenden, wie in anderen Antworten erwähnt. Abhängig von Ihren Anforderungen möchten Sie möglicherweise Ihre Projektabhängigkeiten steuern. Ein kleines Code-Snippet, das genau das tut, was Sie brauchen, nicht mehr, ist manchmal besser.
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");

Hier ist die offizielle Dokumentation zu replace_all.

TheNamelessOne
quelle
1
Beachten Sie, dass Sie für das Muster und die Ersetzung keine expliziten std :: string-Zeichenfolgen erstellen müssen: boost :: replace_all (target, "foo", "bar");
Alexis Wilke
4
+1, mit einer Einschränkung: replace_allwird für Versionen von Boost> 1.43 in Sun Studio für jede Version <12.3
Brian Vandenberg
3
boosterhöht die Kompilierungszeit auf eingebetteten Geräten erheblich. Sogar ARMv7 Quad Core. 100 Codezeilen werden in 2 Minuten kompiliert, ohne Boost, 2 Sekunden.
Piotr Kula
4
@ppumkin: Das bedeutet, dass Ihr Compiler (oder Build-Setup oder was auch immer) scheiße ist, nicht die Zielarchitektur, die nichts damit zu tun hat.
Daniel Kamil Kozar
Wenn Ihr Compiler vorkompilierte Header unterstützt, wird dringend empfohlen, diese bei Verwendung von Boost zu verwenden. Das spart wirklich Zeit.
Alexey
33

In C ++ 11 können Sie dies als Einzeiler mit einem Aufruf an 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;

Ausgabe:

Removeallspaces
Brent Bradburn
quelle
Danke, sehr einfach zu bedienen und zu merken!
Julian Declercq
Beachten Sie auch, dass fromdies ein regulärer Ausdruck sein kann. Sie können daher bei Bedarf komplexere Übereinstimmungskriterien verwenden. Was ich nicht sehe, ist, wie man dies macht, ohne irgendeine Form der Analyse regulärer Ausdrücke anzuwenden - stattdessen nur eine direkte Interpretation der fromZeichen zu verwenden.
Brent Bradburn
Dies erfordert möglicherweise einen aktuellen Compiler. Es hat mit gcc 5.0 funktioniert, aber ich hatte einige Probleme mit gcc 4.8.4.
Brent Bradburn
@nobar, ja, wenn ich mich richtig erinnere, war die Regex-Unterstützung in 4.8.x nicht vollständig. Sie können auch komplexere Suchvorgänge durchführen, aber Sie werden zeitlich bestraft ... Es wird langsamer als die anderen, einfacheren Such- und Ersetzungsfunktionen.
Alexis Wilke
2
Bitte beachten Sie, dass dies nur für sehr einfache alphanumerische Zeichen funktioniert und nichts anderes, ohne abhängig von der Art der Zeichenfolge viel Vorverarbeitung durchzuführen. Ich habe noch keinen universellen Regex-basierten String-Ersatz gefunden.
Piyush Soni
17

Warum nicht eine geänderte Zeichenfolge zurückgeben?

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;
}

Wenn Sie Leistung benötigen, finden Sie hier eine optimierte Funktion, mit der die Eingabezeichenfolge geändert und keine Kopie der Zeichenfolge erstellt wird:

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;

Ausgabe:

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
Zarek Tomczak
quelle
6

Mein templatisiertes Inline-In-Place-Suchen und Ersetzen:

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;
}

Es wird die Anzahl der ersetzten Elemente zurückgegeben (zur Verwendung, wenn Sie dies nacheinander ausführen möchten usw.). Um es zu benutzen:

std::string str = "one two three";
int n = findAndReplace(str, "one", "1");
Marius
quelle
4
Ich habe dieses Beispiel unter GCC ausprobiert, aber es konnte nicht kompiliert werden - die Verwendung von T :: size_t gefiel mir nicht. Das Ersetzen von T :: size_t durch den Typnamen T :: size_type behebt das Problem.
Andrew Wyatt
3

Der einfachste Weg (etwas in der Nähe von dem anzubieten, was Sie geschrieben haben) ist die Verwendung von Boost.Regex , insbesondere von regex_replace .

std :: string hat die Methoden find () und replace () integriert, die Arbeit mit ihnen ist jedoch umständlicher, da sie den Umgang mit Indizes und Zeichenfolgenlängen erfordern.

Alan
quelle
3
Es gibt auch die Boost-String-Algorithmen, einschließlich replace_all (Regex kann für eine so einfache Substitution etwas schwergewichtig sein).
UncleBens
3

Ich glaube das würde funktionieren. Es werden const char * als Parameter verwendet.

//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
quelle
Vorausgesetzt, size_typefür eine Zeichenfolge ist unsignedIhre >=Überprüfung in der Schleifenbedingung immer true. Sie müssen std::string::nposdort verwenden.
Pavel Minaev
size_type ist nicht ohne Vorzeichen. Es ist auf vielen Plattformen nicht signiert, aber nicht auf allen.
Alan
12
Warum in aller Welt ist dies nicht Teil von std :: string? Gibt es eine andere ernsthafte String-Klasse in der Welt der Programmierung, die keine Such- und Ersetzungsoperation bietet? Sicherlich ist es üblicher als zwei Iteratoren zu haben und den Text zwischen ihnen ersetzen zu wollen? Manchmal fühlt sich std :: string wie ein Auto mit einer abstimmbaren Windschutzscheibe an, aber es gibt keine Möglichkeit, das Fenster des Fahrers herunterzurollen.
Spike0xff
@ Spike0xff Boost hatroll_down_window
ta.speot.is
1
@gustafr: Mein Fehler. Ich habe an Systemen gearbeitet, auf denen ältere Compiler size_t falsch definiert haben.
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
quelle
1
Wir müssen nur nach neuen Übereinstimmungen aus der letzten Übereinstimmung suchen, deshalb verfolgt der Algorithmus die letzte Übereinstimmung in pos sorgfältig. pos2 speichert immer die nächste Übereinstimmung, daher verketten wir die Zeichenfolge zwischen pos und pos2 mit dem Ergebnis und stellen dann pos und pos2 vor. Wenn keine andere Übereinstimmung gefunden werden kann, verketten wir den Rest der zu ergebenden Zeichenfolge.
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();
  }
}

Die Überprüfung, ob oldStr leer ist, ist wichtig. Wenn dieser Parameter aus irgendeinem Grund leer ist, bleiben Sie in einer Endlosschleife stecken.

Aber ja, verwenden Sie die bewährte C ++ 11- oder Boost-Lösung, wenn Sie können.

Ericcurtin
quelle