Reemplazar parte de una cadena con otra cadena

186

¿Es posible en C ++ reemplazar parte de una cadena con otra cadena?

Básicamente, me gustaría hacer esto:

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

Pero me gustaría usar las bibliotecas estándar de C ++.

Tom Leese
fuente
1
posible duplicado de ¿Cuál es la función para reemplazar la cadena en C? - Vaya, perdón, eso es C, no C ++; Desearía poder desestimar.
Polygenelubricants
1
@poly Creo que también se debe haber pedido C ++, pero no puedo encontrarlo
Michael Mrozek
1
Hay una etiqueta estándar en la pregunta, pero tal vez le interesen los algoritmos de cadena de boost, que también incluyen una amplia variedad de algoritmos de reemplazo (in situ / copia, mayúsculas y minúsculas, mayúsculas / minúsculas / n-ésima )
UncleBens
@Michael Mrozek Hay uno en stackoverflow.com/questions/3418231/... pero es más nuevo y su método replaceAll es más robusto.
dave-holm

Respuestas:

288

Hay una función para encontrar una subcadena dentro de una cadena ( find), y una función para reemplazar un rango particular en una cadena con otra cadena ( replace), para que pueda combinarlas para obtener el efecto que desea:

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");

En respuesta a un comentario, creo replaceAllque probablemente se vería así:

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'
    }
}
Michael Mrozek
fuente
2
¿Cómo lo arreglaría si la cadena original tuviera más de una instancia de "$ name" y quisiera reemplazarlos a todos?
Tom Leese
1
¿Por qué no se pasan fromy se topasan por constreferencia? ¿Cuál es su función si fromno está allí? -1de mi parte por eso.
sbi
10
@sbi Fixed, aunque podría haberlo redactado como recomendaciones en lugar de ataques: simplemente no se me ocurrió, rara vez pienso en usarlo consty si escribiera un método de utilidad como este solo lo llamaría si supiera el reemplazo eran válidas
Michael Mrozek
10
@Michael: Bien, convertí mi voto negativo en un voto positivo. Descartar constes ignorar una de las mejores herramientas de C ++. El paso por constreferencia debe ser el modo predeterminado para los parámetros de función. (FTR, sin el const, que ni siquiera podía pasar literales de cadena a su función, porque no se pueden temporales se unen a los no constreferencias Así la función ni siquiera hacer lo que fue escrito para..)
SBI
19
¿Sigue siendo la única solución en 2018? Si es así y algún comité de C ++ está leyendo esto, resuélvelo. Es vergonzoso. dividir (cadena, cadena) y reemplazar (cadena, cadena) por favor!
user997112
96

Con C ++ 11 puede usar std::regexasí:

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

La barra invertida doble es necesaria para escapar de un personaje de escape.

Tom
fuente
Estoy bastante seguro de std::regex_replaceque no acepta la cadena de Qt.
BartoszKP
1
Tienes razón. A medida que sucede, QString proporciona un método de reemplazo que acepta un QRexExp, lo que permite utilizar las propias cosas de Qt. Pero creo que la respuesta actual podría corregirse reemplazando stringcon string.toStdString().
Tom
1
O simplemente cambiando Stringa std::string, porque la pregunta no está relacionada con Qt. Por favor considere hacer eso. Con mucho gusto votaré su respuesta después.
BartoszKP
55
La cadena sin procesar permite escribir en R"(\$name)"lugar de "\\$name".
Jarod42
2
¿Cuánto será más lento que buscar / reemplazar sin considerar el tiempo de construcción de std :: regex?
jw_
18

std::stringtiene un replacemétodo, ¿es eso lo que estás buscando?

Tu podrías intentar:

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

No lo he probado yo mismo, solo leí la documentación en find()y replace().

SC Madsen
fuente
2
Por lo que puedo ver, el método de reemplazo std :: string no toma dos cadenas como me gustaría.
Tom Leese
2
Esto no funciona para mí. sizeof debe ser reemplazado por string ("Somename"). size () - 1
TimZaman
@TimZaman: Eso me desconcierta, la documentación establece claramente que puede inicializar desde una cadena de estilo C.
SC Madsen
55
el segundo argumento debería ser la longitud de "$ name" (en lugar de la longitud de "Somename"), ¿no?
Daniel Kiss
10

Para que se devuelva la nueva cadena, use esto:

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 necesita rendimiento, aquí hay una función optimizada que modifica la cadena de entrada, no crea una copia de la cadena:

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

Pruebas:

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;

Salida:

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
fuente
Su llamada a subject.replace en ReplaceStringInPlace () ¿realmente modifica la cadena en el lugar?
Damian
Miré brevemente la fuente y parece que usa la semántica de movimiento para mover el frente de la cadena vieja a su lugar, de modo que no se copie, pero la nueva pieza insertada se copia en la cadena vieja y la cola de la cadena vieja se copia en el búfer redimensionado de la cadena anterior. Es posible que la cadena se expanda tanto que todo el búfer subyacente se reasigne, pero si reemplaza 1 a 1 como en su ejemplo, creo que ocurre "en su lugar", o sin ninguna copia, pero si expande la cadena, solo la primera parte de la cadena anterior no se copia, y tal vez solo entonces.
Motes
6

Sí, puede hacerlo, pero debe encontrar la posición de la primera cadena con el miembro find () de la cadena y luego reemplazarla con su miembro replace ().

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

Si está planeando usar la Biblioteca estándar, debería obtener una copia del libro La biblioteca estándar de C ++ que cubre todo esto muy bien.

Drew Noakes
fuente
1
es size_t y no size_type
revo el
1
Es std :: string :: size_type, no size_t o el size_type sin adornos.
jmucchiello
5

Generalmente uso esto:

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

Llama repetidamente std::string::find()para localizar otras ocurrencias de la cadena buscada hasta std::string::find()que no encuentra nada. Porque std::string::find()devuelve el posición de la coincidencia, no tenemos el problema de invalidar iteradores.

Galik
fuente
4

Esto suena como una opción

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

Podría envolver esto en una función, pero esta solución de una línea parece aceptable. El problema es que esto solo cambiará la primera ocurrencia, es posible que desee recorrerlo, pero también le permite insertar varias variables en esta cadena con el mismo token ( %s)

maxoumime
fuente
1
Me gusta el estilo pero las diferentes cadenas me parecen confusas ^^str.replace(str.find("%s"), string("%s").size(), "Something");
Paul Würtz
3

Si todas las cadenas son std :: string, encontrará problemas extraños con el corte de caracteres si se usa sizeof()porque está destinado a cadenas C, no a cadenas C ++. La solución es usar el .size()método de clase de std::string.

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

Eso reemplaza a sHaystack en línea: no es necesario hacer una asignación = de nuevo en eso.

Ejemplo de uso:

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
fuente
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);
usuario3016543
fuente
2

Si desea hacerlo rápidamente, puede usar un enfoque de dos escaneos. Pseudocódigo:

  1. primer análisis encuentra cuántos caracteres coinciden.
  2. expande la longitud de la cuerda.
  3. segundo análisis Comience desde el final de la cadena cuando obtengamos una coincidencia que reemplazamos, de lo contrario, simplemente copiamos los caracteres de la primera cadena.

No estoy seguro de si esto puede optimizarse para algo en el lugar.

Y un ejemplo de código C ++ 11 pero solo busco un carácter.

#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
fuente
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
fuente
2
¿Puede explicar este código (en su respuesta)? ¡Puede obtener más votos a favor de esa manera!
The Guy with The Hat
1

Mi propia implementación, teniendo en cuenta que la cadena debe redimensionarse solo una vez, luego puede reemplazarse.

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

El uso podría ser, por ejemplo, así:

std::string dirSuite = replaceAll(replaceAll(relPath.parent_path().u8string(), "\\", "/"), ":", "");
TarmoPikaro
fuente
0

Ahora estoy aprendiendo C ++, pero editando parte del código publicado anteriormente, probablemente usaría algo como esto. Esto le brinda la flexibilidad de reemplazar 1 o varias instancias, y también le permite especificar el punto de inicio.

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;
}
algún programador
fuente
0

Esto podría ser incluso mejor usar

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
fuente