Tom Leese
Tom Leese

Reputation: 19679

Replace part of a string with another string

How do I replace part of a string with another string using the standard C++ libraries?

QString s("hello $name");  // Example using Qt.
s.replace("$name", "Somename");

Upvotes: 286

Views: 499257

Answers (19)

Rixef
Rixef

Reputation: 26

Building off of Michael Mrozek's accepted answer for the replaceAll() function, we could just do it like this:

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;
}
    
void replaceAll(string &str, const string &from, const string &to)
{
    while(replace(str,from,to));
}

string hello = "Hello blargedy blargedy!";
replaceAll(hello, "blargedy","World");

Upvotes: 1

Ignat Loskutov
Ignat Loskutov

Reputation: 784

It requires some case analysis to write an optimal (or at least not quadratic) algorithm for all inputs.

The naive algorithm (also the most up-voted answer at the time of writing) is quadratic in the worst case because it shifts the whole suffix at each iteration, so it's O(n) calls to replace(), O(n) each because of that shift.

Essentially, the haystack string can be seen as a sequence of strings that are equal to what, separated by some other strings (that don't have what as a substring). So, all we need to do to avoid quadratic runtime is to make sure that we copy each of such strings only once, not the whole suffix or prefix each time. It can be achieved with the "two pointer technique", the exact way we do that depends on who is longer:

  • if the replacements will shrink the string (that is, with is shorter than what), then let's start from the beginning of the string and maintain two offsets — read and write one — and the write one will never be greater. After traversing the whole string (in just one pass, in-place), the write offset stands for the last character we've copied, so it's also the new size of the string.
  • if the replacements will grow the string (with is longer than what), we'll do the similar thing but backwards. To know which write offset to begin with, we're going to have to know the number of occurrences and resize the string in advance, otherwise it's pretty symmetric to the previous case.
  • if with and what have equal length, we don't have to shift the string, so pretty much any approach will suffice — the first one looks better because it only requires one pass.
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <string>
#include <string_view>

size_t CountOccurrences(std::string_view s, std::string_view needle) {
    size_t res = 0;
    size_t pos = 0;
    while ((pos = s.find(needle, pos)) != std::string_view::npos) {
        ++res;
        pos += needle.size();
    }
    return res;
}

std::string ReplaceNotLonger(std::string s, std::string_view what, std::string_view with) {
    assert(what.size() >= with.size());
    std::string_view::size_type wpos = 0;
    std::string_view::size_type rpos = 0;
    while (true) {
        auto new_rpos = s.find(what, rpos);
        if (new_rpos == std::string::npos) {
            new_rpos = s.size();
        }
        auto n = new_rpos - rpos;
        std::copy(s.begin() + rpos, s.begin() + new_rpos, s.begin() + wpos);
        wpos += n;
        rpos = new_rpos;
        if (rpos == s.size()) {
            break;
        }
        std::copy(with.begin(), with.end(), s.begin() + wpos);
        wpos += with.size();
        rpos += what.size();
    }
    s.resize(wpos);
    return s;
}

std::string ReplaceLonger(std::string s, std::string_view what, std::string_view with) {
    assert(what.size() < with.size());
    auto occurrences = CountOccurrences(s, what);
    auto rpos = s.size();
    auto wpos = rpos + occurrences * (with.size() - what.size());
    s.resize(wpos);
    
    while (wpos != rpos) {
        auto new_rpos = s.rfind(what, rpos - what.size());
        if (new_rpos == std::string::npos) {
            new_rpos = 0;
        } else {
            new_rpos += what.size();
        }
        auto n = rpos - new_rpos;
        std::copy_backward(s.begin() + new_rpos, s.begin() + rpos, s.begin() + wpos);
        wpos -= n;
        rpos = new_rpos;
        if (wpos == rpos) {
            break;
        }
        std::copy_backward(with.begin(), with.end(), s.begin() + wpos);
        wpos -= with.size();
        rpos -= what.size();
    }
    return s;
}

std::string Replace(std::string s, std::string_view what, std::string_view with) {
    assert(!what.empty());
    if (what.size() >= with.size()) {
        return ReplaceNotLonger(std::move(s), what, with);
    }
    return ReplaceLonger(std::move(s), what, with);
}

Upvotes: 3

Daemon
Daemon

Reputation: 101

Here is a one liner that uses c++'s standard library.

The replacement better not have the old string in it (ex: replacing , with ,,), otherwise you have an INFINITE LOOP. Moreso, it is slow for large strings compared to other techniques because the find operations start at the begining of the string call every time. Look for better solutions if you're not too lazy. I put this in for completeness and inspiration for others. You've been warned.

while(s.find(old_s) != string::npos) s.replace(s.find(old_s), old_s.size(), new_s);

And a lambda option

auto replaceAll = [](string& s, string o, string n){ while(s.find(o) != string::npos) s.replace(s.find(o), o.size(), n); };

// EXAMPLES:
// Used like
string text = "hello hello world";
replaceAll(text, "hello", "bye"); // Changes text to "bye bye world"
// Do NOT use like
string text = "hello hello world";
replaceAll(text, "hello", "hello hello"); // Loops forever

Upvotes: 1

mubasshir00
mubasshir00

Reputation: 349

You can use this code for remove subtring and also replace , and also remove extra white space . code :

#include<bits/stdc++.h>
using namespace std;

void removeSpaces(string &str)
{   
    int n = str.length();
    int i = 0, j = -1;

    bool spaceFound = false;
    while (++j <= n && str[j] == ' ');

    while (j <= n)
    {
        if (str[j] != ' ')
        {
          
            if ((str[j] == '.' || str[j] == ',' ||
                 str[j] == '?') && i - 1 >= 0 &&
                 str[i - 1] == ' ')
                str[i - 1] = str[j++];
            else str[i++] = str[j++];
 
            spaceFound = false;
        }
        else if (str[j++] == ' ')
        {
            if (!spaceFound)
            {
                str[i++] = ' ';
                spaceFound = true;
            }
        }
    }

    if (i <= 1)
         str.erase(str.begin() + i, str.end());
    else str.erase(str.begin() + i - 1, str.end());
}
int main()
{
    string s;
    cin >> s;

    for(int i = s.find("WUB"); i >= 0; i = s.find("WUB"))
        s.replace(i,3," ");
    removeSpaces(s);
    cout << s << endl;

    return 0;
}

Upvotes: -2

maxoumime
maxoumime

Reputation: 3501

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

You could wrap this in a function but this one-line solution sounds acceptable. The problem is that this will change the first occurence only, you might want to loop over it, but it also allows you to insert several variables into this string with the same token (%s).

Upvotes: 9

S.C. Madsen
S.C. Madsen

Reputation: 5246

Using std::string::replace:

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

Upvotes: 45

camp0
camp0

Reputation: 392

What about the boost solution:

boost::replace_all(value, "token1", "token2");

Upvotes: 5

Yashwanth Kumar
Yashwanth Kumar

Reputation: 389

This could be even better to use

void replace(string& input, const string& from, const string& to)
{
    auto pos = 0;
    while(true)
    {
        size_t startPosition = input.find(from, pos);
        if(startPosition == string::npos)
            return;
        input.replace(startPosition, from.length(), to);
        pos += to.length();
    }
}

Upvotes: 4

Tom
Tom

Reputation: 2631

With C++11 you can use std::regex like so:

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

The double backslash is required for escaping an escape character.

Upvotes: 175

TarmoPikaro
TarmoPikaro

Reputation: 5233

My own implementation, taking into account that string needs to be resized only once, then replace can happen.

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

Usage could be for example like this:

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

Upvotes: 1

Damian
Damian

Reputation: 4631

If you want to do it quickly you can use a two scan approach. Pseudo code:

  1. first parse. find how many matching chars.
  2. expand the length of the string.
  3. second parse. Start from the end of the string when we get a match we replace, else we just copy the chars from the first string.

I am not sure if this can be optimized to an in-place algo.

And a C++11 code example but I only search for one char.

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

}

Upvotes: 2

Volomike
Volomike

Reputation: 24886

If all strings are std::string, you'll find strange problems with the cutoff of characters if using sizeof() because it's meant for C strings, not C++ strings. The fix is to use the .size() class method of std::string.

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

That replaces sHaystack inline -- no need to do an = assignment back on that.

Example usage:

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;

Upvotes: 4

user3016543
user3016543

Reputation: 149

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

Upvotes: 3

someprogrammer
someprogrammer

Reputation: 269

I'm just now learning C++, but editing some of the code previously posted, I'd probably use something like this. This gives you the flexibility to replace 1 or multiple instances, and also lets you specify the start point.

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

Upvotes: 0

Galik
Galik

Reputation: 48615

I use generally this:

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

It repeatedly calls std::string::find() to locate other occurrences of the searched for string until std::string::find() doesn't find anything. Because std::string::find() returns the position of the match we don't have the problem of invalidating iterators.

Upvotes: 7

Lucas Civali
Lucas Civali

Reputation: 19

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

Upvotes: 1

Michael Mrozek
Michael Mrozek

Reputation: 175355

There's a function to find a substring within a string (find), and a function to replace a particular range in a string with another string (replace), so you can combine those to get the effect you want:

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

In response to a comment, I think replaceAll would probably look something like this:

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

Upvotes: 391

Czarek Tomczak
Czarek Tomczak

Reputation: 20645

To have the new string returned use this:

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

If you need performance, here is an optimized function that modifies the input string, it does not create a copy of the string:

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 modified: " 
          << input << std::endl;

ReplaceStringInPlace(input, "bc", "??");
std::cout << "ReplaceStringInPlace() input string modified: " 
          << input << std::endl;

Output:

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

Upvotes: 16

anon
anon

Reputation:

Yes, you can do it, but you have to find the position of the first string with string's find() member, and then replace with it's replace() member.

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

If you are planning on using the Standard Library, you should really get hold of a copy of the book The C++ Standard Library which covers all this stuff very well.

Upvotes: 6

Related Questions