17

I need to search a string and edit the formatting of it.

So far I can replace the first occurrence of the string, but I am unable to do so with the next occurrences of this string.

This is what I have working, sort of:

if(chartDataString.find("*A") == string::npos){ return;}
else{chartDataString.replace(chartDataString.find("*A"), 3,"[A]\n");}

If it doesn't find the string, nothing prints at all, so that's not good.

I know I need to loop through the entire string chartDataString and replace all occurrences. I know there are a lot of similar posts to this but I don't understand (like this Replace substring with another substring C++)

I've also tried to do something like this to loop over the string:

string toSearch = chartDataString;
string toFind = "*A:";
for (int i = 0; i<toSearch.length() - toFind.length(); i++){
   if(toSearch.substr(i, toFind.length()) == toFind){
       chartDataString.replace(chartDataString.find(toFind), 3, "[A]\n");   
   }
}

EDIT taking into consideration suggestions, this in theory should work, but I don't know why it doesn't

size_t startPos=0;
string myString = "*A";
while(string::npos != (startPos = chartDataString.find(myString, startPos))){
    chartDataString.replace(chartDataString.find(myString, startPos), 3, "*A\n");
    startPos = startPos + myString.length();
}   
Community
  • 1
  • 1
Sarah
  • 2,713
  • 13
  • 31
  • 45

9 Answers9

27

try the following

const std::string s = "*A";
const std::string t = "*A\n";

std::string::size_type n = 0;
while ( ( n = chartDataString.find( s, n ) ) != std::string::npos )
{
    chartDataString.replace( n, s.size(), t );
    n += t.size();
}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • 2
    But if string is 1MB and replaced char is found inside for 100 times, then this means mallocing 100MB just for 100bytes. – huseyin tugrul buyukisik Feb 19 '18 at 05:06
  • In which case, to avoid that, you would have to first count the number of occurrences of the substring in the string, then `reserve()` the string to its new final size, then you can replace the occurrences without reallocating each time. – Remy Lebeau Aug 09 '22 at 17:51
10

Use std::regex_replace available with C++11. This does exactly what you want and more.

https://en.cppreference.com/w/cpp/regex/regex_replace

std::string const result = std::regex_replace( chartDataString, std::regex( "\\*A" ), "[A]\n" );
Ben Corbett
  • 111
  • 1
  • 2
9

In case boost is available, you can use the following:

std::string origStr = "this string has *A and then another *A";
std::string subStringToRemove = "*A";
std::string subStringToReplace = "[A]";

boost::replace_all(origStr , subStringToRemove , subStringToReplace);

To perform the modification on the original string, OR

std::string result = boost::replace_all_copy(origStr , subStringToRemove , subStringToReplace);

To perform the modifications without modifying the original string.

Julien Marrec
  • 11,605
  • 4
  • 46
  • 63
Guy Avraham
  • 3,482
  • 3
  • 38
  • 50
3
/// Returns a version of 'str' where every occurrence of
/// 'find' is substituted by 'replace'.
/// - Inspired by James Kanze.
/// - http://stackoverflow.com/questions/20406744/
std::string replace_all(
    const std::string & str ,   // where to work
    const std::string & find ,  // substitute 'find'
    const std::string & replace //      by 'replace'
) {
    using namespace std;
    string result;
    size_t find_len = find.size();
    size_t pos,from=0;
    while ( string::npos != ( pos=str.find(find,from) ) ) {
        result.append( str, from, pos-from );
        result.append( replace );
        from = pos + find_len;
    }
    result.append( str, from , string::npos );
    return result;
/*
    This code might be an improvement to James Kanze's
    because it uses std::string methods instead of
    general algorithms [as 'std::search()'].
*/
}

int main() {
    {
        std::string test    = "*A ... *A ... *A ...";
        std::string changed = "*A\n ... *A\n ... *A\n ...";

        assert( changed == replace_all( test, "*A", "*A\n" ) );
    }
    {
        std::string GB = "My gorila ate the banana";

        std::string gg = replace_all( GB, "gorila", "banana" );
        assert( gg ==  "My banana ate the banana" );
        gg = replace_all( gg, "banana", "gorila"  );
        assert( gg ==  "My gorila ate the gorila" );

        std::string bb = replace_all( GB, "banana", "gorila" );
        assert( gg ==  "My gorila ate the gorila" );
        bb = replace_all( bb, "gorila" , "banana" );
        assert( bb ==  "My banana ate the banana" );
    }
    {
        std::string str, res;

        str.assign( "ababaabcd" );
        res = replace_all( str, "ab", "fg");
        assert( res == "fgfgafgcd" );

        str="aaaaaaaa"; assert( 8==str.size() );
        res = replace_all( str, "aa", "a" );
        assert( res == "aaaa" );
        assert( "" == replace_all( str, "aa", "" ) );

        str = "aaaaaaa"; assert( 7==str.size() );
        res = replace_all( str, "aa", "a" );
        assert( res == "aaaa" );

        str = "..aaaaaa.."; assert( 10==str.size() );
        res = replace_all( str, "aa", "a" );
        assert( res == "..aaa.." );

        str = "baaaac"; assert( 6==str.size() );
        res = replace_all( str, "aa", "" );
        assert( res == "bc" );
    }
}
Adolfo
  • 281
  • 2
  • 5
2

The find function takes an optional second argument: the position from which to begin searching. By default this is zero.

A good position to begin searching for the next match is the position where the previous replacement was inserted, plus that replacement's length. For instance if we insert a string of length 3 at position 7, then the next find should begin at position 10.

If the search string happens to be a substring of the replacement, this approach will avoid an infinite loop. Imagine if you try to replace all occurrences of log with analog, but don't skip over the replacement.

Kaz
  • 55,781
  • 9
  • 100
  • 149
  • How do I find out what the position of the first occurrence is? – Sarah Dec 05 '13 at 22:23
  • @Sarah You are *already using* it in your example code to perform the first replacement! It is the return value of `find`. – Kaz Dec 05 '13 at 22:28
  • 1
    The algorithm is something like 1. Initialize a position to zero; 2. Find the substring starting at the position. 3. If the substring is not found, you are done. Otherwise: 4. replace the substring at the position. 5. Add the length of the replacement string to the position. 6. Go back to step 2. – Kaz Dec 05 '13 at 22:30
2

It's fairly awkward (and probably not too efficient) to do it in place. I usually use a function along the lines of:

std::string
replaceAll( std::string const& original, std::string const& from, std::string const& to )
{
    std::string results;
    std::string::const_iterator end = original.end();
    std::string::const_iterator current = original.begin();
    std::string::const_iterator next = std::search( current, end, from.begin(), from.end() );
    while ( next != end ) {
        results.append( current, next );
        results.append( to );
        current = next + from.size();
        next = std::search( current, end, from.begin(), from.end() );
    }
    results.append( current, next );
    return results;
}

Basically, you loop as long as you can find an instance of from, appending the intermediate text and to, and advancing to the next instance of from. At the end, you append any text after the last instance of from.

(If you're going to do much programming in C++, it's probably a good idea to get used to using iterators, like the above, rather than the special member functions of std::string. Things like the above can be made to work with any of the C++ container types, and for this reason, are more idiomatic.)

James Kanze
  • 150,581
  • 18
  • 184
  • 329
  • I would genuinely like to use this but I don't think it works and I am not exactly sure how to make it work. When I call replaceAll(chartDataString, "*A", "[A]\n"); nothing actually happens and I don't know enough to know how to make it work – Sarah Dec 06 '13 at 00:24
  • @Sarah It works for me. (It is, in fact, adopted from production code.) Are you sure that `chartDataString` contains the sequence `"*A"`? And are you looking at the return value; this function doesn't change anything in `original`, `from` or `to`? (Generally speaking, it's a lot cleaner to use pure functions, rather than to have functions which modify existing objects. Performance sometimes dictates otherwise, but as a general rule, prefer pure functions to mutating functions.) – James Kanze Dec 06 '13 at 09:52
0

If ever the strings you need to invert are not of the same size:

void Replace::replace(std::string & str, std::string const & s1, std::string const & s2)
{
    size_t pos = 0;
    while ((pos = str.find(s1, pos)) != std::string::npos)
    {
        str.erase(pos, s1.length());
        str.insert(pos, s2);
        pos += s2.length();
    }
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Nat
  • 1
0
string replaceAll(string del, string replace, string line){
   
    int len=del.length();
    string output="[Programming Error]";
    if(line.find(del)!=-1){
        do{
            output=line.replace(line.find(del),len,replace);
        }while(output.find(del)!=-1);
    }
    return output;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
0

Below is a complete display of how find, string::replace and replace working.

There is no direct implementation of replaceAll in cpp.

We can tweak replace to perform our intent:

string original = "A abc abc abc A";
string test = original;
cout << endl << "Original string: " << original;  //output: A abc abc abc A

//FINDING INDEX WHERE QUERY SUBSTRING FOUND
int index = test.find("a");
cout << endl << "index: " << index; //output: 2
int outOfBoundIndex = test.find("xyz");
cout << endl << "outOfBoundIndex: " << outOfBoundIndex; //output: -1

//REPLACE SINGLE OCCURENCES
string queryString = "abc";
int queryStringLength = queryString.size();
index = test.find(queryString);
if(index > -1 && index < (test.size() - 1))
    test.replace(index, queryStringLength, "xyz");

cout << endl << endl << "first occurrence \'abc\' replaced to \'xyz\': " << test;  //output: A xyz abc abc A

//REPLACE ALL OCCURRENCES
test = original;

//there is a cpp utility function to replace all occurrence of single character. It will not work for replacing all occurences of string.
replace(test.begin(), test.end(), 'a', 'X');
cout << endl << endl << "Replacing all occurences of character \'a\' with \'X\': " << test;  //output: A Xbc Xbc Xbc A
test = original;
index = test.find("abc");
while(index > -1 && index < (test.size() - 1)){
    test.replace(index, queryStringLength, "xyz");
    index = test.find("abc");
}
cout << endl << "replaceAll implementation: " << test;  //output: A xyz xyz xyz A
Om Sao
  • 7,064
  • 2
  • 47
  • 61