Jak zamienić wszystkie wystąpienia znaku w ciągu?

480

Jaki jest skuteczny sposób na zastąpienie wszystkich wystąpień postaci inną postacią std::string?

big-z
źródło

Odpowiedzi:

742

std::stringnie zawiera takiej funkcji, ale można użyć replacefunkcji autonomicznej z algorithmnagłówka.

#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
źródło
6
std::stringto pojemnik zaprojektowany specjalnie do pracy z sekwencjami znaków. link
Kirill V. Lyadvinsky
164
Niestety pozwala to zastąpić tylko jeden znak innym znakiem. Nie może zastąpić znaku większą liczbą znaków (to znaczy ciągiem znaków). Czy istnieje sposób na zastąpienie wyszukiwania większą liczbą znaków?
SasQ,
6
@Kirill V. Lyadvinsky Co jeśli chcę tylko usunąć wystąpienie.
SIFE
4
@ KirillV.Lyadvinsky: Kiedy używam tej metody do zamiany wszystkich x na y, wynikiem jest długi ciąg y bez względu na oryginalny ciąg. Ciekawe, co według ciebie byłoby problemem. (kod jest dokładnie taki sam jak napisałeś)
Transcendent
6
@Transcendent: Właśnie tak się dzieje std::string::replace()zamiast std::replace()! „x” ( char) jest domyślnie rzutowane na size_t[wartość 120], dlatego cały ciąg lub jego część zostanie wypełniona 120 kopiami „y”.
IBue
127

Pomyślałem, że wrzucę również rozwiązanie doładowania :

#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
źródło
Następnie brakuje kilku -Iflag dla kompilatora, aby mógł on znaleźć biblioteki Boost w twoim systemie. Być może najpierw musisz go zainstalować.
Martin Ueding
Powyższe jest bardziej skuteczne, ponieważ wychodzi ze standardową biblioteką lib. Nie wszyscy używają biblioteki doładowania ;-)
hfrmobile
122

Pytanie koncentruje się na characterwymianie, ale ponieważ uznałem tę stronę za bardzo przydatną (szczególnie uwagę Konrada ), chciałbym udostępnić tę bardziej uogólnioną implementację, która pozwala również poradzić sobie z substrings:

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

Stosowanie:

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;

Wyjścia:

Number_Of_Beans

XXjXugtXty

hhjhugthty


EDYTOWAĆ:

Powyższe można zaimplementować w bardziej odpowiedni sposób, w przypadku gdy wydajność ma znaczenie, zwracając nic ( void) i wykonując zmiany bezpośrednio na łańcuchu strpodanym jako argument, przekazywany przez adres zamiast wartości . Pozwoliłoby to uniknąć niepotrzebnej i kosztownej kopii oryginalnego ciągu, zwracając jednocześnie wynik. Twoje połączenie, a następnie ...

Kod :

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

Mam nadzieję, że będzie to pomocne dla niektórych innych ...

Gauthier Boaglio
źródło
4
Ten ma problem z wydajnością w przypadkach, gdy łańcuch źródłowy jest duży i istnieje wiele wystąpień łańcucha do zastąpienia. string :: replace () byłby wywoływany wiele razy, co powoduje wiele kopii ciągu. Zobacz moje rozwiązanie, które rozwiązuje ten problem.
minastaros
1
Wybieranie nitów naprzód: według adresu => przez odniesienie . To, czy jest to adres, czy nie, jest szczegółem wdrożenia.
Max Truxa
1
Powinieneś sprawdzić, czy fromłańcuch jest pusty, w przeciwnym razie powstanie nieskończona pętla.
początkujący
34

Wyobraź sobie duży binarny obiekt blob, w którym wszystkie bajty 0x00 zostaną zastąpione przez „\ 1 \ x30”, a wszystkie bajty 0x01 przez „\ 1 \ x31”, ponieważ protokół transportowy nie zezwala na bajty \ 0.

W przypadkach, gdy:

  • łańcuch zastępujący i zastępowany mają różne długości,
  • istnieje wiele wystąpień zamienianego ciągu w ciągu źródłowym i
  • łańcuch źródłowy jest duży,

dostarczone rozwiązania nie mogą być zastosowane (ponieważ zastępują tylko pojedyncze znaki) lub mają problem z wydajnością, ponieważ wywoływałyby ciąg :: zamień kilka razy, co generuje kopie rozmiaru obiektu blob w kółko. (Nie znam rozwiązania wspomagającego, może z tej perspektywy jest OK)

To jeden spacery wzdłuż wszystkich zdarzeń w ciągu źródłowym i buduje nowy kawałek po kawałku ciąg raz :

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
źródło
To zdecydowanie najlepsze rozwiązanie tutaj oparte na samym STL. Jeśli zamierzasz skorzystać z niestandardowej funkcji, która będzie łatwa w użyciu w dowolnym miejscu, zrób to.
Roger Sanders
21

Proste wyszukiwanie i zamiana dla jednej postaci wyglądałoby mniej więcej tak:

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

Aby to zrobić dla całego łańcucha, najłatwiej byłoby zapętlić, dopóki nie s.findzaczniesz powracaćnpos . Przypuszczam, że mógłbyś złapać również range_errorwyjście z pętli, ale to trochę brzydkie.

PRZETRZĄSAĆ
źródło
7
Chociaż jest to prawdopodobnie odpowiednie rozwiązanie, gdy liczba znaków do zastąpienia jest niewielka w porównaniu z długością łańcucha, nie skaluje się dobrze. W miarę wzrostu odsetka znaków w oryginalnym ciągu, które należy wymienić, metoda ta zbliża się do O (N ^ 2) w czasie.
i
7
Prawdziwe. Moją ogólną filozofią jest robienie rzeczy łatwych (pisanie i czytanie), dopóki nieefektywność nie spowoduje prawdziwych problemów. Istnieją pewne okoliczności, w których możesz mieć humorystyczne łańcuchy, w których liczy się O (N ** 2), ale w 99% przypadków moje łańcuchy mają wielkość 1K lub mniej.
TED,
3
... to powiedziawszy, bardziej podoba mi się metoda Kirilla (i już ją głosowałem).
TED,
Co się stanie, jeśli nie zostanie znalezione „x”? Ponadto, dlaczego używasz podwójnych aparatów ortodontycznych?
Prasath Govind
@PrasathGovind - Właśnie pokazywałem wymagane połączenia (stąd „coś w stylu”). Ważne, ale niejasne szczegóły, takie jak poprawna obsługa błędów, pozostały dla czytelnika ćwiczeniem. Jeśli chodzi o „podwójne nawiasy klamrowe”, nie jestem pewien, co to jest ani o czym mówisz. Dla mnie „klamra” to {postać. Nie wiem, co to jest „podwójny aparat ortodontyczny”. Być może masz problem z czcionkami?
TED,
6

Jeśli chcesz zastąpić więcej niż jedną postać i masz do czynienia tylko z tym std::string, ten fragment kodu będzie działał, zastępując sNeedle w sHaystack sReplace, a sNeedle i sReplace nie muszą być tego samego rozmiaru. Ta procedura wykorzystuje pętlę while do zastąpienia wszystkich wystąpień, a nie tylko pierwszego znalezionego od lewej do prawej.

while(sHaystack.find(sNeedle) != std::string::npos) {
  sHaystack.replace(sHaystack.find(sNeedle),sNeedle.size(),sReplace);
}
Volomike
źródło
To jest O (n ^). Możesz to zrobić w czasie O (n).
Changming Sun,
3
@ChangmingSun, które rozwiązanie O (n) masz na myśli?
habakuk
2
Spowoduje to nieskończoną pętlę, jeśli kNeedle stanie się podciągiem sReplace.
duma
Plus finddwa razy telefon. Zastanów się, czy ten wynik nie jest zmienną temp.
Luc Bloom
4

Jak sugerował Kirill, albo zastosuj metodę replace, albo iteruj wzdłuż łańcucha, zastępując każdy znak niezależnie.

Alternatywnie możesz użyć findmetody lub w find_first_ofzależności od tego, co musisz zrobić. Żadne z tych rozwiązań nie wykona zadania za jednym razem, ale z kilkoma dodatkowymi wierszami kodu powinieneś sprawić, by działały dla Ciebie. :-)

Konrad
źródło
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
źródło
Jeśli wordjest długi, może być dużo narzutu podczas wywoływania funkcji. Można zoptymalizować ten przekazując word, targeti replacementjako const odniesień.
TrebledJ
2

Co z Abseil StrReplaceAll ? Z pliku nagłówka:

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

Stara szkoła :-)

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;

Wynik:

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

Iván Rodríguez
źródło
0

To działa! Użyłem czegoś podobnego do tego w aplikacji księgarni, w której inwentarz był przechowywany w pliku CSV (np. Plik .dat). Ale w przypadku pojedynczego znaku, co oznacza, że ​​zamiennik jest tylko jednym znakiem, np. „|”, Musi być w cudzysłowie „|” aby nie wyrzucić niepoprawnej stałej const char.

#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

Nietypowo używam obecnie CentOS, więc moja wersja kompilatora jest poniżej. Wersja C ++ (g ++), domyślnie C ++ 98:

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

Jeśli chcesz użyć std::strings, możesz użyć funkcji tej przykładowej aplikacji bez strsubzmian lub zaktualizować ją, jeśli chcesz, aby przyjmowała inny typ lub zestaw parametrów, aby osiągnąć mniej więcej ten sam cel. Zasadniczo używa właściwości i funkcjonalności, std::stringaby szybko usunąć dopasowany zestaw znaków i wstawić żądane znaki bezpośrednio w ciągu std::string. Za każdym razem, gdy wykonuje tę operację zastępowania, offset jest aktualizowany, jeśli nadal może znaleźć pasujące znaki do zastąpienia, a jeśli nie może tego zrobić z powodu niczego więcej do zastąpienia, zwraca ciąg znaków w stanie z ostatniej aktualizacji.

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

Jeśli nie chcesz polegać na używaniu std::strings jako parametrów, aby zamiast tego przekazywać ciągi w stylu C, możesz zobaczyć zaktualizowaną próbkę poniżej:

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

W prostych sytuacjach działa to całkiem dobrze bez użycia innej biblioteki niż std :: string (która jest już w użyciu).

Zamień wszystkie wystąpienia znaku a na znak b w some_string :

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

Jeśli ciąg jest duży lub wiele wezwań do zastąpienia jest problemem, możesz zastosować technikę wymienioną w tej odpowiedzi: https://stackoverflow.com/a/29752943/3622300

Guney Ozsan
źródło
0

oto rozwiązanie, które rzuciłem, w maksymalnym duchu DRI. przeszuka sNeedle w sHaystack i zastąpi go sReplace, nTimes jeśli nie jest 0, w przeciwnym razie wszystkie wystąpienia sNeedle. nie będzie wyszukiwać ponownie w zastąpionym tekście.

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;
}
nieujawnione
źródło