Zamień część ciągu na inny

186

Czy w C ++ jest możliwe zastąpienie części łańcucha innym łańcuchem?

Zasadniczo chciałbym to zrobić:

QString string("hello $name");
string.replace("$name", "Somename");

Chciałbym jednak użyć standardowych bibliotek C ++.

Tom Leese
źródło
1
możliwy duplikat Jaka funkcja zastępuje ciąg w C? - Ups, przepraszam, że to C, a nie C ++; Chciałbym odmówić.
polygenelubricants
1
@poly Wydaje mi się, że musiałem o to poprosić również o C ++, ale nie mogę go znaleźć
Michael Mrozek
1
W pytaniu znajduje się znacznik std, ale być może być może interesują Cię algorytmy boost, które obejmują również szeroki wybór algorytmów zastępowania (wstawianie / kopiowanie, rozróżnianie wielkości liter / rozróżnianie wielkości liter, zamiana pierwsza / ostatnia / wszystko / n-ty ).
UncleBens,
@Michael Mrozek Jest jeden na stackoverflow.com/questions/3418231/... ale jest nowszy, a twoja metoda replaceAll jest bardziej niezawodna.
dave-holm

Odpowiedzi:

288

Istnieje funkcja znajdowania podłańcucha w ciągu string ( find) oraz funkcja zastępowania określonego zakresu w ciągu innym ciągiem ( replace), dzięki czemu można je łączyć, aby uzyskać pożądany efekt:

bool replace(std::string& str, const std::string& from, const std::string& to) {
    size_t start_pos = str.find(from);
    if(start_pos == std::string::npos)
        return false;
    str.replace(start_pos, from.length(), to);
    return true;
}

std::string string("hello $name");
replace(string, "$name", "Somename");

W odpowiedzi na komentarz myślę, replaceAllże prawdopodobnie wyglądałby mniej więcej tak:

void replaceAll(std::string& str, const std::string& from, const std::string& to) {
    if(from.empty())
        return;
    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(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}
Michał Mrożek
źródło
2
Jak mam to naprawić, jeśli oryginalny ciąg zawierał więcej niż jedno wystąpienie „$ name” i chciałem je wszystkie zastąpić.
Tom Leese
1
Dlaczego nie są fromi toprzekazywane za constodniesienia? Jaka jest twoja funkcja, jeśli jej fromnie ma? -1ode mnie za to.
sbi
10
@sbi Naprawiono, chociaż można by to sformułować jako rekomendacje zamiast ataków - po prostu nie constprzyszło mi to do głowy, rzadko myślę o użyciu, a gdybym napisał taką metodę użyteczności, nazwałbym to, gdybym wiedział wymiana była ważna
Michael Mrozek
10
@Michael: Dobrze, zamieniłem swój głos oddolny na głos wzrostowy. Odrzucenie constoznacza pominięcie jednego z najlepszych narzędzi C ++. Przekazywanie na constreferencję powinno być domyślnym trybem dla parametrów funkcji. (FTR, bez znaku, nie constmożna nawet przekazać literałów łańcuchowych do funkcji, ponieważ nie można powiązać tymczasowych elementów z const
niepowiązaniami
19
Czy to wciąż jedyne rozwiązanie w 2018 roku? Jeśli tak i jakikolwiek komitet C ++ to czyta, rozwiąż to. To jest zawstydzające. podziel (ciąg, ciąg) i zamień (ciąg, ciąg) proszę!
user997112
96

Z C ++ 11 możesz używać std::regextak:

#include <regex>
...
std::string string("hello $name");
string = std::regex_replace(string, std::regex("\\$name"), "Somename");

Podwójny ukośnik jest wymagany do ucieczki przed znakiem ucieczki.

Tomek
źródło
Jestem prawie pewien, std::regex_replaceże nie akceptuje ciągu Qt.
BartoszKP,
1
Masz rację. Tak się składa, że ​​QString zapewnia metodę zastępowania, która akceptuje QRexExp, pozwalając na korzystanie z własnych rzeczy Qt. Ale myślę, że obecną odpowiedź można poprawić, zastępując stringstring.toStdString().
Tom
1
Lub po prostu zmieniając Stringna std::string, ponieważ pytanie nie jest związane z Qt. Zastanów się nad tym - chętnie poprę twoją odpowiedź później.
BartoszKP,
5
Surowy ciąg pozwala na pisanie R"(\$name)"zamiast "\\$name".
Jarod42
2
Ile to będzie wolniejsze niż znajdowanie / zamiana bez uwzględnienia czasu konstruowania std :: regex?
jw_
18

std::stringma replacemetodę, czy tego właśnie szukasz?

Możesz spróbować:

s.replace(s.find("$name"), sizeof("$name") - 1, "Somename");

Sam nie próbowałem, po prostu przeczytaj dokumentację na temat find()i replace().

SC Madsen
źródło
2
Z tego co widzę metoda std :: string replace nie bierze dwóch łańcuchów, jak chciałbym.
Tom Leese
2
To mi nie działa. sizeof należy zastąpić ciągiem („Somename”). size () - 1
TimZaman 10.04.
@TimZaman: To mnie zastanawia, dokumentacja wyraźnie stwierdza, że ​​możesz zainicjować z ciągu w stylu C.
SC Madsen,
5
drugi argument powinien być długością „$ name” (zamiast długości „Somename”), prawda?
Daniel Kiss
10

Aby zwrócić nowy ciąg, użyj tego:

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, 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 modified: " 
          << 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
Twoje wywołanie podmiotu.replace w funkcji ReplaceStringInPlace () naprawdę modyfikuje ciąg w miejscu?
Damian
Krótko spojrzałem na źródło i wygląda na to, że używa semantyki move, aby przesunąć przód starego łańcucha na miejsce, więc to nie jest kopiowane, ale wstawiony nowy kawałek jest kopiowany na stary łańcuch i ogon starego łańcucha jest kopiowany do buforowanego rozmiaru starego łańcucha. Możliwe jest, że łańcuch rozszerzy się tak bardzo, że cały podstawowy bufor zostanie ponownie przydzielony, ale jeśli zastąpisz 1 do 1, jak w jego przykładzie, myślę, że dzieje się to „na miejscu” lub bez kopiowania, ale jeśli rozwiniesz łańcuch, tylko pierwsza część starego ciągu nie jest kopiowana i tylko wtedy.
Motes
6

Tak, możesz to zrobić, ale musisz znaleźć pozycję pierwszego łańcucha za pomocą elementu find () łańcucha, a następnie zastąpić go jego elementem replace ().

string s("hello $name");
size_type pos = s.find( "$name" );
if ( pos != string::npos ) {
   s.replace( pos, 5, "somename" );   // 5 = length( $name )
}

Jeśli planujesz korzystać ze Standardowej Biblioteki, powinieneś naprawdę zdobyć egzemplarz książki Standardowa Biblioteka C ++, która bardzo dobrze opisuje wszystkie te rzeczy.

Drew Noakes
źródło
1
ma rozmiar_t, a nie rozmiar_typ
revo
1
Jest to std :: string :: typ_rozmiaru, a nie rozmiar_t lub nieosadzony typ_rozmiaru.
jmucchiello
5

Używam ogólnie tego:

std::string& replace(std::string& s, const std::string& from, const std::string& to)
{
    if(!from.empty())
        for(size_t pos = 0; (pos = s.find(from, pos)) != std::string::npos; pos += to.size())
            s.replace(pos, from.size(), to);
    return s;
}

Wielokrotnie wywołuje, std::string::find()aby zlokalizować inne wystąpienia szukanego ciągu, dopóki std::string::find()niczego nie znajdzie. Ponieważ std::string::find()zwraca pozycję dopasowania, nie mamy problemu z unieważnieniem iteratorów.

Galik
źródło
4

To brzmi jak opcja

string.replace(string.find("%s"), string("%s").size(), "Something");

Możesz zawinąć to w funkcję, ale to rozwiązanie jednowierszowe wydaje się dopuszczalne. Problem polega na tym, że zmieni to tylko pierwsze wystąpienie, możesz zapętlić go, ale pozwala także wstawić kilka zmiennych do tego ciągu za pomocą tego samego tokena ( %s)

maxoumime
źródło
1
Podobnie jak styl, ale różne łańcuchy były mylące ^^str.replace(str.find("%s"), string("%s").size(), "Something");
Paul Würtz
3

Jeśli wszystkie ciągi są std :: string, znajdziesz dziwne problemy z odcięciem znaków, jeśli są używane, sizeof()ponieważ są przeznaczone do łańcuchów C, a nie C ++. Rozwiązaniem jest użycie .size()metody klasy std::string.

sHaystack.replace(sHaystack.find(sNeedle), sNeedle.size(), sReplace);

Zastępuje on wbudowany sHaystack - nie trzeba już wykonywać przypisania =.

Przykładowe użycie:

std::string sHaystack = "This is %XXX% test.";
std::string sNeedle = "%XXX%";
std::string sReplace = "my special";
sHaystack.replace(sHaystack.find(sNeedle),sNeedle.size(),sReplace);
std::cout << sHaystack << std::endl;
Volomike
źródło
2
wstring myString = L"Hello $$ this is an example. By $$.";
wstring search = L"$$";
wstring replace = L"Tom";
for (int i = myString.find(search); i >= 0; i = myString.find(search))
    myString.replace(i, search.size(), replace);
użytkownik3016543
źródło
2

Jeśli chcesz to zrobić szybko, możesz zastosować metodę dwóch skanów. Pseudo kod:

  1. pierwsza analiza. znajdź ile pasujących znaków.
  2. rozwiń długość łańcucha.
  3. druga analiza. Zacznij od końca łańcucha, gdy otrzymamy dopasowanie, które zastępujemy, w przeciwnym razie po prostu kopiujemy znaki z pierwszego łańcucha.

Nie jestem pewien, czy można to zoptymalizować do miejscowego algo.

I przykład kodu C ++ 11, ale szukam tylko jednego znaku.

#include <string>
#include <iostream>
#include <algorithm>
using namespace std;

void ReplaceString(string& subject, char search, const string& replace)
{   
    size_t initSize = subject.size();
    int count = 0;
    for (auto c : subject) { 
        if (c == search) ++count;
    }

    size_t idx = subject.size()-1 + count * replace.size()-1;
    subject.resize(idx + 1, '\0');

    string reverseReplace{ replace };
    reverse(reverseReplace.begin(), reverseReplace.end());  

    char *end_ptr = &subject[initSize - 1];
    while (end_ptr >= &subject[0])
    {
        if (*end_ptr == search) {
            for (auto c : reverseReplace) {
                subject[idx - 1] = c;
                --idx;              
            }           
        }
        else {
            subject[idx - 1] = *end_ptr;
            --idx;
        }
        --end_ptr;
    }
}

int main()
{
    string s{ "Mr John Smith" };
    ReplaceString(s, ' ', "%20");
    cout << s << "\n";

}
Damian
źródło
1
std::string replace(std::string base, const std::string from, const std::string to) {
    std::string SecureCopy = base;

    for (size_t start_pos = SecureCopy.find(from); start_pos != std::string::npos; start_pos = SecureCopy.find(from,start_pos))
    {
        SecureCopy.replace(start_pos, from.length(), to);
    }

    return SecureCopy;
}
Lucas Civali
źródło
2
Czy możesz wyjaśnić ten kod (w swojej odpowiedzi)? W ten sposób możesz uzyskać więcej głosów pozytywnych!
Facet z kapeluszem
1

Moja własna implementacja, biorąc pod uwagę, że ciąg musi być zmieniany tylko raz, wtedy może nastąpić zamiana.

template <typename T>
std::basic_string<T> replaceAll(const std::basic_string<T>& s, const T* from, const T* to)
{
    auto length = std::char_traits<T>::length;
    size_t toLen = length(to), fromLen = length(from), delta = toLen - fromLen;
    bool pass = false;
    std::string ns = s;

    size_t newLen = ns.length();

    for (bool estimate : { true, false })
    {
        size_t pos = 0;

        for (; (pos = ns.find(from, pos)) != std::string::npos; pos++)
        {
            if (estimate)
            {
                newLen += delta;
                pos += fromLen;
            }
            else
            {
                ns.replace(pos, fromLen, to);
                pos += delta;
            }
        }

        if (estimate)
            ns.resize(newLen);
    }

    return ns;
}

Użycie może być na przykład takie:

std::string dirSuite = replaceAll(replaceAll(relPath.parent_path().u8string(), "\\", "/"), ":", "");
TarmoPikaro
źródło
0

Właśnie uczę się C ++, ale edytując część wcześniej opublikowanego kodu, prawdopodobnie użyłbym czegoś takiego. Zapewnia to elastyczność zastępowania 1 lub wielu wystąpień, a także pozwala określić punkt początkowy.

using namespace std;

// returns number of replacements made in string
long strReplace(string& str, const string& from, const string& to, size_t start = 0, long count = -1) {
    if (from.empty()) return 0;

    size_t startpos = str.find(from, start);
    long replaceCount = 0;

    while (startpos != string::npos){
        str.replace(startpos, from.length(), to);
        startpos += to.length();
        replaceCount++;

        if (count > 0 && replaceCount >= count) break;
        startpos = str.find(from, startpos);
    }

    return replaceCount;
}
someprogrammer
źródło
0

Może być jeszcze lepszy w użyciu

void replace(string& input, const string& from, const string& to)
{
    while(true)
    {
        size_t startPosition = input.find(from);
        if(startPosition == string::npos)
            break;
        input.replace(startPosition, from.length(), to);
    }
}
Yashwanth Kumar
źródło