Replace part of a string with another string
Asked Answered
Q

19

274

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");
Quiles answered 5/8, 2010 at 19:6 Comment(2)
There is a std tag on the question, but perhaps you might be interested in boost's string algorithms, which also includes a wide choice of replace algorithms (inplace/copy, case sensitive/case insensitive, replace first/last/all/n-th).Prevocalic
possible duplicate of How do I Search/Find and Replace in a standard string?Etherify
C
382

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'
    }
}
Cioffred answered 5/8, 2010 at 19:11 Comment(15)
How would I fix it if the original string had more that one instance of "$name" and I wanted to replace all of them.Quiles
Why aren't from and to passed per const reference? What does your function if from isn't there? -1 from me for that.Raid
Add a loop and use the "pos" argument of the find() methodIseabal
@Raid Fixed, although you could've phrased it as recommendations instead of attacks -- it simply didn't occur to me, I rarely think to use const and if I wrote a utility method like this I would only call it if I knew the replacement were validCioffred
@Michael: Good, I turned my down-vote into an up-vote. Dismissing const is disregarding one of C++' best tools. Passing per const reference should be the default mode for function parameters. (FTR, without the const, you couldn't even pass string literals to your function, because you cannot bind temporaries to non-const references. So the function wouldn't even do what it was written to.)Raid
After copying and using the replaceAll method, my string kept getting shorter with each call. According to cplusplus.com/reference/string/string/replace the signature for str.replace is this: string& replace ( size_t pos1, size_t n1, const string& str ); Usually an argument named n1 indicates a length and changing the end_pos argument to just be the length of the from string solved the problem.Unfix
Line: size_t start_pos = string.find(from); should be: size_t start_pos = str.find(from);Danaus
This method goes to endless cycle if from==""Thyrsus
@user484936 Did you try it? I assume you mean the second one, but neither of them gets stuck in a loop if from is an empty stringCioffred
@MichaelMrozek Yes, I tried second one and got infinite cycle. "to" must be also empty stringThyrsus
Is this still the only solution in 2018? If so and any C++ committee are reading this, sort it out. It's embarrassing. split(string, string) and replace(string, string) please!Sadick
Please consider if replace string with empty str "", your answer will segmentation fault!!Prehistory
In the example code for std::replace, there is an implementation of replace_all: en.cppreference.com/w/cpp/string/basic_string/replaceFenian
The old MFC CString class has had string replacement functionality for at least one decade. A shame that there is still no such thing in std::string.Progressist
Maybe update this answer to use std::string_view for from and to?Cartierbresson
G
169

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.

Gualtiero answered 23/3, 2015 at 10:9 Comment(6)
I'm pretty sure std::regex_replace doesn't accept Qt's string.Jana
You are right. As it happens QString provides a replace method which accepts a QRexExp, allowing to use Qt's own stuff. But I think The current answer could be corrected by replacing string with string.toStdString().Gualtiero
Or just by changing String to std::string, because the question is not related to Qt. Please consider doing that - I'll gladly upvote your answer afterwards.Jana
Raw string allows to write R"(\$name)" instead of "\\$name".Telecast
How much will this be slower than find/replace without considering the constructing time of std::regex?Orthotropous
Does this replace all occurrances? Or just the first?Karolkarola
I
43

Using std::string::replace:

s.replace(s.find("$name"), sizeof("$name") - 1, "Somename");
Iseabal answered 5/8, 2010 at 19:9 Comment(7)
From what I can see the std::string replace method doesn't take two strings as I would like.Quiles
This doesnt work for me. sizeof should be replaced by string("Somename").size()-1Strontian
@TimZaman: That puzzles me, the documentation clearly states you can initialize from a C-style string.Iseabal
the 2nd argument should be the length of "$name" (instead of the length of "Somename"), shouldn't it?Governorship
This wouldn't work if there are multiple occurrences of "$name" in s string.Iatrochemistry
this answer is straight up wrong with no explanation of what is going on.Centrifugate
Not sure I follow what you mean @SamB? I assure you the answer works for the example given in the question. But admittedly not for multiple occurrences of "$name". But I think that is quite a different story than being "straight up wrong".Iseabal
J
14

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
Jequirity answered 4/2, 2013 at 0:7 Comment(2)
Your call to subject.replace in ReplaceStringInPlace() is that really modifying the string inplace?Amaranthine
I briefly looked at the source and it looks like it does use move semantics to move the front of the old string into place, so that is not copied, but the new piece inserted is copied into the old string and the tail of the old string is copied into the resized buffered of the old string. It is possible that the string expands so much the entire underlying buffer is reallocated, but if you replace 1 to 1 as in his example, I think it does occur "in place", or without any copying, but if you expand the string, only the first part of the old string is not copied, and only maybe then.Homerhomere
B
9
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).

Bender answered 4/4, 2015 at 20:29 Comment(1)
Like the style but found the different strings confusing ^^ str.replace(str.find("%s"), string("%s").size(), "Something");Searby
E
6

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.

Engrail answered 5/8, 2010 at 19:10 Comment(2)
it's size_t and not size_typeDistillate
It's std::string::size_type, not size_t or the unadorned size_type.Flamen
H
6

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.

Hopping answered 29/9, 2014 at 15:33 Comment(0)
P
5

What about the boost solution:

boost::replace_all(value, "token1", "token2");
Polymerism answered 24/5, 2022 at 20:9 Comment(1)
Or boost::replace_all_copy() when a new std::string is preferred over modificationScolex
R
4

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;
Reisinger answered 4/2, 2016 at 4:15 Comment(0)
E
4

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();
    }
}
Essequibo answered 18/11, 2019 at 12:3 Comment(5)
string s = "ha"; replace(s, "h", "uhoh");Allies
instead of making a snide comment @JonathanWakely you could actually explicitly describe the problem you are implying in your commentPurposeless
@Purposeless It's not snide, and I wasn't implying, I was demonstrating. Teach a man to fish, and all that. Working out why the function doesn't work is a useful exercise. If you want there to be a comment explaining it, you can add one. I note you didn't do that either, you'd rather just criticise my comment.Allies
@JonathanWakely using "uhoh" and "ha" is snide to me. And sure, "demonstrate" and force the person reading this to run an infinite loop? Why not just tell them outright? It's not like they're going to gain anything by blowing up the code themselves. And the explanation is quite simple, so you might as well have put that instead.Purposeless
@JonathanWakely the very least you could have done was add the words "infinite loop" literally anywhere in your comment. Alas, no one has time to provide help anymore. Editing the answer to fix or point out this problem would have been great and all, but snide comments will really teach the answerer a lesson. Best stick to that.Velleman
C
3
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);
Crosslet answered 27/1, 2016 at 16:37 Comment(0)
A
2

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

}
Amaranthine answered 13/7, 2016 at 9:12 Comment(0)
C
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;
}
Critter answered 17/4, 2014 at 14:8 Comment(2)
Can you please explain this code (in your answer)? You might get more upvotes that way!Cartesian
Inputs where the replacement string has the old string in it like , replaced with ,, will cause an infinite loop. Can fix by offseting the position in replace with the length difference between from and to.Chancery
A
1

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(), "\\", "/"), ":", "");
Ancel answered 18/4, 2019 at 16:38 Comment(0)
C
1

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);
}
Coucher answered 6/4, 2023 at 22:47 Comment(0)
S
0

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;
}
Strap answered 2/12, 2015 at 19:23 Comment(0)
C
0

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
Chancery answered 13/2, 2023 at 20:49 Comment(0)
T
0

I realize this thread is old as dirt now, but I'm just building off of Michael Mrozek's accepted answer for the replaceAll() function. Why not 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");
Teage answered 22/3 at 5:41 Comment(0)
R
-2

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;
}
Rockey answered 4/8, 2020 at 20:19 Comment(3)
This replaces "WUB" with " " but that wasn't what OP asked for, and neither was removing spaces (which what most of your answer is doing). Your find/replace solution doesn't work if the replacement also matches the string to be replaced.Allies
No one is asking you to be an overachiever, just answer the question as is, you don't get extra points for complicating your code solving problems they didn't ask for.Meraree
Like the question is "How to remove part of a string with another," and you respond with a function named "RemovedSpaces," which is overly complicated for the simple task, and by the way this would remove spaces that are wanted. So "This is a text with WUB in it" turns into "Thisisatextwithinit".Meraree

© 2022 - 2024 — McMap. All rights reserved.