Jak wyszukać / znaleźć i zamienić w standardowym ciągu?

94

Czy istnieje sposób na zastąpienie wszystkich wystąpień podciągu innym ciągiem w std::string?

Na przykład:

void SomeFunction(std::string& str)
{
   str = str.replace("hello", "world"); //< I'm looking for something nice like this
}
Adam Tegen
źródło
Możliwy duplikat Zamień część ciągu na inny
vll

Odpowiedzi:

75

Dlaczego nie wdrożyć własnego zamiennika?

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
źródło
3
Trochę majstrujesz przy pamięci przy wszystkich wywołaniach „replace”: złożoność wyniesie n², jeśli usuniesz „o” z „ooooooo… o”. Myślę, że można zrobić lepiej, ale to rozwiązanie ma tę zaletę, że jest łatwe do zrozumienia.
Zonko
1
Dlaczego nie jest to rzeczywista pętla for, a nie zaciemniona pętla for?
Shirik
Przywykłem do stosowania zasady „najmniejszej niespodzianki”. W większości przypadków pętle For służą do prostego zwiększania indeksu. Tutaj według mnie pętla while jest wyraźniejsza.
yves Baumes
1
@aldo Generalnie lepiej jest unikać złożoności i na przykład używać wyrażenia regularnego, jak wspomniano w innych odpowiedziach. Ale w zależności od potrzeb możesz chcieć kontrolować zależności projektu. Mały fragment kodu, który robi dokładnie to, czego potrzebujesz, nie więcej, jest czasem lepszy.
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");

Oto oficjalna dokumentacja na replace_all.

TheNamelessOne
źródło
1
Zauważ, że nie musisz jawnie tworzyć std :: string dla wzorca i zastępowania: boost :: replace_all (target, "foo", "bar");
Alexis Wilke,
4
+1, z zastrzeżeniem: replace_allbędzie segfault dla wersji boost> 1.43 w Sun Studio dla dowolnej wersji <12.3
Brian Vandenberg
3
boostznacznie wydłuża czas kompilacji na urządzeniach wbudowanych. Nawet czterordzeniowy procesor ARMv7. 100 linii kodu kompiluje się w 2 minuty, bez przyspieszenia, 2 sekundy.
Piotr Kula
4
@ppumkin: oznacza to, że Twój kompilator (lub konfiguracja kompilacji lub cokolwiek innego) jest do niczego, a nie architektura docelowa, która nie ma z tym nic wspólnego.
Daniel Kamil Kozar
Jeśli Twój kompilator obsługuje wstępnie skompilowany nagłówek, zdecydowanie zaleca się użycie go podczas korzystania z funkcji boost. To naprawdę oszczędza czas.
Alexey Omelchenko
33

W C ++ 11 możesz to zrobić jako jednowierszowy z wywołaniem 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;

wynik:

Removeallspaces
Brent Bradburn
źródło
Dzięki, bardzo łatwy w użyciu i zapamiętaj!
Julian Declercq
Zauważ również, że frommoże to być wyrażenie regularne - możesz więc w razie potrzeby użyć bardziej zaawansowanych kryteriów dopasowywania. Nie widzę, jak to zrobić bez stosowania jakiejś formy analizy wyrażeń regularnych - zamiast tego używam tylko bezpośredniej interpretacji fromznaków.
Brent Bradburn
Może to wymagać aktualnego kompilatora. Działało z gcc 5.0, ale miałem pewne problemy z gcc 4.8.4.
Brent Bradburn
@nobar, tak, jeśli dobrze pamiętam, obsługa wyrażeń regularnych w 4.8.x nie była kompletna. Możesz także wyszukiwać bardziej wyrafinowane, ale tracisz czas ... To będzie wolniejsze niż inne, prostsze funkcje wyszukiwania i zamiany.
Alexis Wilke
2
Zauważ, że zadziała to tylko dla bardzo podstawowych znaków alfanumerycznych i nic więcej bez wykonywania dużej ilości wstępnego przetwarzania w zależności od typu ciągu. Nie znalazłem jeszcze zastępowania ciągów opartych na wyrażeniach regularnych ogólnego przeznaczenia.
Piyush Soni
17

Dlaczego nie zwrócić zmodyfikowanego ciągu?

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

Jeśli potrzebujesz wydajności, oto zoptymalizowana funkcja, która modyfikuje ciąg wejściowy, ale nie tworzy kopii ciągu:

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

Testy:

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;

Wynik:

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
źródło
6

Moje wbudowane wyszukiwanie i zastępowanie szablonów:

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

Zwraca liczbę podstawionych elementów (do użycia, jeśli chcesz to uruchomić po kolei itp.). Aby z niego skorzystać:

std::string str = "one two three";
int n = findAndReplace(str, "one", "1");
Marius
źródło
4
Wypróbowałem tę próbkę pod GCC, ale nie mogła się skompilować - nie podobało mu się użycie T :: size_t. Zastąpienie T :: size_t nazwą typu T :: size_type rozwiązuje problem.
Andrew Wyatt,
3

Najłatwiejszym sposobem (oferowanie czegoś podobnego do tego, co napisałeś) jest użycie Boost.Regex , a konkretnie regex_replace .

std :: string ma wbudowane metody find () i replace (), ale są one bardziej kłopotliwe w użyciu, ponieważ wymagają obsługi indeksów i długości łańcuchów.

Alan
źródło
3
Istnieją również algorytmy ciągów zwiększających, w tym replace_all (wyrażenie regularne może być nieco ciężkie dla takiego prostego podstawienia).
UncleBens
3

Wierzę, że to zadziała. Jako parametr przyjmuje const char *.

//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
źródło
Zakładając, że size_typedla łańcucha jest to unsigned, >=warunek sprawdzenia w pętli zawsze będzie true. Musisz std::string::npostam użyć .
Pavel Minaev
size_type nie jest bez znaku. Jest niepodpisany na wielu platformach, ale nie na wszystkich.
Alan
12
Dlaczego na świecie to nie jest częścią std :: string? Czy jest jakaś inna poważna klasa String w świecie programowania, która nie oferuje operacji „znajdź i zamień”? Z pewnością jest to bardziej powszechne niż posiadanie dwóch iteratorów i chęć zastąpienia tekstu między nimi? Czasami std :: string jest jak samochód z regulowaną przednią szybą, ale bez możliwości opuszczenia szyby kierowcy.
Spike0xff
@ Spike0xff boost maroll_down_window
ta.speot.is
1
@gustafr: Mój błąd. Pracowałem na systemach, w których starsze kompilatory nieprawidłowo definiowały 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
źródło
1
Musimy tylko szukać nowych dopasowań z ostatniego dopasowania, dlatego algorytm dokładnie śledzi ostatnie dopasowanie w poz. pos2 zawsze przechowuje następne dopasowanie, więc łączymy łańcuch między pos i pos2 w wyniku, a następnie przesuwamy pos i pos2. Jeśli nie można znaleźć innego dopasowania, łączymy pozostałą część ciągu, aby uzyskać wynik.
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();
  }
}

Sprawdzenie, czy oldStr jest pusty, jest ważne. Jeśli z jakiegoś powodu ten parametr jest pusty, utkniesz w nieskończonej pętli.

Ale tak, jeśli możesz, użyj wypróbowanego i przetestowanego rozwiązania C ++ 11 lub Boost.

ericcurtin
źródło