44

I found this on another stack question:

//http://stackoverflow.com/questions/3418231/c-replace-part-of-a-string-with-another-string
//
void 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) {
        size_t end_pos = start_pos + from.length();
        str.replace(start_pos, end_pos, to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
}

and my method:

string convert_FANN_array_to_binary(string fann_array)
{
    string result = fann_array;
    cout << result << "\n";
    replaceAll(result, "-1 ", "0");
    cout << result << "\n";
    replaceAll(result, "1 ", "1");
    return result;
}

which, for this input:

cout << convert_FANN_array_to_binary("1 1 -1 -1 1 1 ");

now, the output should be "110011"

here is the output of the method:

1 1 -1 -1 1 1  // original
1 1 0 1  // replacing -1's with 0's
11 1  // result, as it was returned from convert_FANN_array_to_binary()

I've been looking at the replaceAll code, and, I'm really not sure why it is replacing consecutive -1's with one 0, and then not returning any 0's (and some 1's) in the final result. =\

Chris Maes
  • 35,025
  • 12
  • 111
  • 136
NullVoxPopuli
  • 61,906
  • 73
  • 206
  • 352
  • 2
    In this particular instance it looks as though another solution would be more appropriate – i.e. don’t use string operations at all, use an array of integers / bools. – Konrad Rudolph Mar 17 '11 at 18:01
  • It needs to be strings, because we are reading from an ascii file. – NullVoxPopuli Mar 17 '11 at 18:05
  • 3
    If you follow @Konrad's advice, you could use [std::replace](http://www.cplusplus.com/reference/algorithm/replace/) to replace values. The fact that you are reading from an ascii file is no reason not to represent your numbers as integers. – Björn Pollex Mar 17 '11 at 18:09
  • 3
    Then convert them. The flow of programs is always the same: 1. read input, 2. convert to appropriate format, 3. apply calculation, 4. convert to output format, 5. output. You are trying to skip step (2) and making your life unnecessarily hard. Strings are rarely the appropriate format for anything other than text. – Konrad Rudolph Mar 17 '11 at 18:12
  • agreed. We need to re-write a method in our library before we can read in a serialized file though. We'll get there. – NullVoxPopuli Mar 17 '11 at 18:13
  • possible duplicate: https://stackoverflow.com/q/5607085/1971003 – Guy Avraham Jun 12 '18 at 20:36

6 Answers6

60

A complete code:

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 a more 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 changed: " 
          << 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 changed: abc abc def
ReplaceStringInPlace() input string modified: a?? a?? def
Czarek Tomczak
  • 20,079
  • 5
  • 49
  • 56
  • ReplaceString(string("abc\dir\dir1"), string("\\"), string("\\\\")); didn't work – qqqqq Jun 05 '15 at 21:56
  • 8
    You should actually check if `search` string is empty, otherwise an endless loop will occur. – newbie Sep 05 '15 at 02:22
  • To fix the infinite loop, I suggest changing "pos += replace.length();" with "pos += replace.length() + (search.empty() ? 1 : 0);" or the equivalent (could store (search.empty() ? 1 : 0) in a variable before the loop to avoid evaluating search.empty() each time) so that replaceString("abc", "", ":") returns ":a:b:c:". Most string replace functions I have used in other languages work this way. – Some Guy Dec 11 '22 at 15:54
21

The bug is in str.replace(start_pos, end_pos, to);

From the std::string doc at http://www.cplusplus.com/reference/string/string/replace/

string& replace ( size_t pos1, size_t n1,   const string& str );

You are using an end-position, while the function expects a length.

So change to:

while((start_pos = str.find(from, start_pos)) != std::string::npos) {
         str.replace(start_pos, from.length(), to);
         start_pos += to.length(); // ...
}

Note: untested.

Sjoerd
  • 6,837
  • 31
  • 44
  • 1
    BTW, I don't approve of the code nor the style, but that's not the question here. – Sjoerd Mar 17 '11 at 18:07
  • 58
    Is there a better way to do string replace? I'm really amazed that this isn't something built into string.h.... like... seriously.. the higher level languages all have it. – NullVoxPopuli Mar 17 '11 at 18:11
  • 6
    @NullVoxPopuli I found your problem: C++ is not a higher level language – edhurtig Jun 23 '16 at 01:39
  • 1
    @Sjoerd if you don't approve of the code and style, what would you suggest as an alternative? – j b Jul 18 '16 at 14:58
  • 2
    In C++11 there is a way std::regex_replace(str, std::regex(from), to) @NullVoxPopuli – ericcurtin Dec 13 '17 at 19:04
12

C++11 now includes the header <regex> which has regular expression functionality. From the docs:

// regex_replace example
#include <iostream>
#include <string>
#include <regex>
#include <iterator>

int main ()
{
  std::string s ("there is a subsequence in the string\n");
  std::regex e ("\\b(sub)([^ ]*)");   // matches words beginning by "sub"
  // using string/c-string (3) version:
  std::cout << std::regex_replace (s,e,"sub-$2");
  std::cout << std::endl;
  return 0;
}

Of course, now you have two problems.

Jherico
  • 28,584
  • 8
  • 61
  • 87
11

I found the replace functions given in previous answers, all using in-place str.replace() call internally, very slow when working with a string of about 2 MB length. Specifically, I called something like ReplaceAll(str, "\r", ""), and on my particular device, with the text file containing a lot of newlines, it took about 27 seconds. I then replaced it with function just concatenating sub-strings in a new copy, and it took only about 1 seconds. Here is my version of ReplaceAll():

void replaceAll(string& str, const string& from, const string& to) {
    if(from.empty())
        return;
    string wsRet;
    wsRet.reserve(str.length());
    size_t start_pos = 0, pos;
    while((pos = str.find(from, start_pos)) != string::npos) {
        wsRet += str.substr(start_pos, pos - start_pos);
        wsRet += to;
        pos += from.length();
        start_pos = pos;
    }
    wsRet += str.substr(start_pos);
    str.swap(wsRet); // faster than str = wsRet;
}

Greg

gregko
  • 5,642
  • 9
  • 49
  • 76
  • 4
    You can squeeze a little more efficiency out of this by doing `str.swap(wsRet)` at the end instead of an assignment. This can cheaply exchange the strings' contents instead of performing a possibly expensive copy. – Blastfurnace Jul 12 '13 at 17:50
  • @Blastfurnace, indeed, thank you for this tip! I tested it, works fine. Will update the sample code above with it as well. – gregko Jul 12 '13 at 21:58
  • I have a function very similar to this and you are right about the performance. When `from` and `to` are _different sizes_ this can be very fast. If they are the same size then the common, in place, version works well. – Blastfurnace Jul 12 '13 at 22:11
  • Indeed, for example I use a specialized function to replace in place single chars. I guess the moving around the rest of the string with each replacement is what causes delays, when the lengths are not equal. Thanks! – gregko Jul 13 '13 at 18:24
  • Why no C++11 syntax? Very confusing for newbie with all the errors coming up. – Piotr Kula Jun 09 '15 at 19:55
  • Great answer! But I think there is still some room for a slight performance improvement. The first line inside the while loop `wsRet += str.substr(start_pos, pos - start_pos);` may be improved as `wsRet.append(str, start_pos, pos - start_pos);` as the latter gets rid of extra time and space for the temporary copy of the substring. For the same reason, the first line after the while loop `wsRet += str.substr(start_pos);` may be improved as `wsRet.append(str, start_pos);`. Also, line 3-4 inside the while loop can be combined into a single line `start_pos = pos + from.size()`. – Peng Feb 19 '22 at 23:25
  • This treats an empty "from" string as not occurring in "str" at all, which seems incorrect. I suggest removing the check for from.empty() at the front, and instead replace the "pos += from.length();" with "pos += from.length() + (from.empty() ? 1 : 0);" to have it find the empty string only once between each character (instead of infinitely looping), so replaceAll("abc", "", ":") returns ":a:b:c:". Most string replace functions I have used in other languages work this way. – Some Guy Dec 11 '22 at 15:48
11

This is going to go in my list of 'just use a Boost library' answers, but here it goes anyway:

Have you considered Boost.String? It has more features than the standard library, and where features overlap, Boost.String has a more much more natural syntax, in my opinion.

phooji
  • 10,086
  • 2
  • 38
  • 45
1

Try this:

#include <string>

string replace_str(string & str, const string & from, const string & to)
{
  while(str.find(from) != string::npos)
    str.replace(str.find(from), from.length(), to);
  return str;
}
JRL
  • 3,363
  • 24
  • 36
  • 2
    this look will never terminate if `to` contains `from`. – Markus Sep 04 '17 at 16:27
  • This is calling `str.find()` twice per iteration, which is redundant. But more importantly, it is searching the `str` from the very beginning on each call. `string::find()` has an optional parameter to specify the index to start searching from, use it so you don't have to re-search the portion of the string that you have already processed. – Remy Lebeau Aug 09 '22 at 17:55