3

I want to delete all characters between two same characters in a string. My function takes a string (by reference) and a char in its arguments.

Assuming that I used an std::string variable like this: "hah haaah hah hello!" as the first parameter and a char 'h' as the second parameter, something like this should happen: "hah haaah hah hello!" ===> "hh hh hh hello". As you can see, every character between two h characters has been removed. How do I achive something like this?

I've tried to use iterators and ended up with this:

void delete_chars_between(std::string& line, char del)
{
    std::string::iterator itr_from = std::find(line.begin(), line.end(), del);
    std::string::iterator itr_to = std::find(itr_from + 1, line.end(), del);

    while (true) {
        if(itr_to != line.end())
            line.erase(itr_from + 1, itr_to);

        itr_from = std::find(itr_to, line.end(), del);

        if (itr_from == line.end())
            break;

        itr_to = std::find(itr_from + 1, line.end(), del);

        if (itr_to == line.end())
            break;
    }
}

First, I search for the first occurrence of del, I store the iterator to its position in itr_from. After that, I search for the second occurrence of del. And finally I run a while loop that starts by erasing characters in a certain range if itr_to is valid. I repeat that over and over again while my iterators are not equal to line.end().

But for some reason, this code doesn't work properly. It sometimes removes whitespaces and doesn't even touch the characters I was aiming to delete.

Thanks for your help.

LogicStuff
  • 19,397
  • 6
  • 54
  • 74
Michael
  • 548
  • 6
  • 23
  • 1
    I wonder if [`std::regex_replace`](http://en.cppreference.com/w/cpp/regex/regex_replace) would be more appropriate here. – NathanOliver Jun 30 '16 at 14:12
  • @NathanOliver, unfortunately I don't know how to use regex yet. I really want to achieve my goal without using it. – Michael Jun 30 '16 at 14:14
  • Maybe it is easier first to split your input by space in an array of strings like this: http://stackoverflow.com/questions/236129/split-a-string-in-c? – Leo Chapiro Jun 30 '16 at 14:16
  • Iterators may be invalidated by `erase` try resetting both following the `erase` operation. – Matt Jun 30 '16 at 14:17
  • If you would treat input string as char array, you can simply go char by char over it, starting in copy mode, and for each char either copy it, or ignore it, to new empty string (append the char to it). If you encounter delimiter character, just switch between the modes (but append delimiter char anyway). – Ped7g Jun 30 '16 at 17:33

2 Answers2

3

std::string iterators are invalidated by all operations which modify the length of the string, so using itr_from and itr_to after the call to line.erase is Undefined Behaviour.

You need to use the return value of erase:

while (true) {
    if(itr_to != line.end())
        itr_to = line.erase(itr_from + 1, itr_to);

    itr_from = std::find(itr_to, line.end(), del);

    if (itr_from == line.end())
        break;

    itr_to = std::find(itr_from + 1, line.end(), del);

    if (itr_to == line.end())
        break;
}
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
0

To avoid undefined behavior you should reset both iterators before calling erase.

Looking at the expected output it seems that a closing delimiter shouldn't be used to start another interval:

"hh hh hh hello"  not  "hhhhhhhello"
   ^  ^  ^

So, this is my proposal:

void delete_chars_between(std::string& line, char del)
{
    std::string::iterator itr_from = std::find(line.begin(), line.end(), del);
    // I don't want to pass an iterator to two past the last element
    if ( itr_from == line.end() )
        return; 
    std::string::iterator itr_to = std::find(itr_from + 1, line.end(), del);
    //                                               ^^^^

    while ( itr_to != line.end() )
    {
        itr_to = line.erase(itr_from + 1, itr_to);

        itr_from = std::find(itr_to + 1, line.end(), del);
        // to start another couple ^^^^
        if (itr_from == line.end())
            break;

        itr_to = std::find(itr_from + 1, line.end(), del);
    }
}

Live example HERE.

Bob__
  • 12,361
  • 3
  • 28
  • 42