0

Having some trouble understanding parts of the code; the output I am getting is also wrong. The problem is to replace all spaces in a string with '%20'. The full code is shown below; it compiles but doesn't run exactly as it should.

#include <iostream>
#include <string>
using namespace std;

void replaceSpaces(string str){

    //Getting the length of the string, counting the number of spaces 
    int strLen = str.length();
    int i, count = 0;
    for (i = 0; i <= strLen; i++) {
        if(str[i]==' ')
        count++;
    }

    //Determining the new length needed to allocate for replacement characters '%20'
    int newLength = strLen + count * 2;

    str[newLength] = '\0';
    for (i = strLen - 1; i >= 0; i--) {
        if (str[i] == ' ') {
            str[newLength - 1] = '0';
            str[newLength - 2] = '2';
            str[newLength - 3] = '%';
            newLength = newLength - 3;
        }

        else {
            str[newLength - 1] = str[i];
            newLength = newLength -1;
        }
    }
    cout << str <<endl;

}

int main() {

    string str = "hello jellybean hello";
    replaceSpaces(str);

    return 0;

}

I am probably missing something obvious, but when allocating for the new string length in this line:

int newLength = strLen + count * 2;

Here we are multiplying the number of spaces by 2, but if we are trying to replace all spaces with '%20', why not multiply it by 3?


str[newLength] = '\0';

Does this line indicate that the position past the last character in the string is assigned a null space?


Am also confused about the else statement.

 else {
        str[newLength - 1] = str[i];
        newLength = newLength -1;
    }

Not sure if I completely understand the circumstance when this would be executed.


When the functions are compiled and run, if

string str = "hello jellybean hello";

the expected output would be hello%20jellybean%20hello, except the output I am getting is hello%20jellybean%20h.

In terms of time complexity, since there are two independent for loops, would the time complexity be O(n)?

I know I'm asking a lot of different questions, many thanks in advance for any answers!

gsamaras
  • 71,951
  • 46
  • 188
  • 305
jellyxbean
  • 83
  • 8
  • 1
    Indexing (and setting) an arbitrary location in a string doesn't automagically grow the string to that length. After that I stopped looking. Oh well, I did read the question about `* 3`. For each replacement you're subtracting one character - the original space - and adding 3. Thus ... – davidbak Jul 13 '16 at 22:39
  • `str[newLength]` will access memory location beyond the memory space allocated for `str`. What you want to do is to create a brand new string with the new size, copy the old string over while replacing the whitespace with "%20" – ShuberFu Jul 13 '16 at 22:41
  • The easiest approach would be to use `strtok()` to tokenize your string (split at spaces and create string tokens). Then, just iterate through the tokens and put a %20 between each token... `char *tokens = strtok(my_string, " "); while(tokens) { cout << token << "%20"; tokens = strtok(NULL, " "); }` – Ingenioushax Jul 13 '16 at 22:41
  • 1
    Google "c++ urlencode". – Hans Passant Jul 13 '16 at 22:42
  • 1
    @Ingenioushax - `strtok` doesn't work with `std::string`. And it's rarely the right answer. – Pete Becker Jul 13 '16 at 22:52
  • @PeteBecker: I can't understand why strtok() is often the incorrect answer. Does it have some weird behavior if you're not careful? Sure. As for not working on strings, +1. Didn't pay attention to the string datatype. He could always use a stringstream to wrap the string in, beginning and end iterator, a vector, and an ostream_iterator wrapped in a std::copy. `std::copy(myStrVec.begin(), myStrVec.end(), std::ostream_iterator(std::cout, '\n')` – Ingenioushax Jul 14 '16 at 14:26

5 Answers5

2

This is wrong:

str[newLength] = '\0';

std::string objects maintain their NUL terminator internally based on their size. You want

str.resize(newLength);

instead.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • I added the line `str.resize(newLength);` before this line `str[newLength] = '\0';`. From my understanding, `str.resize` will allocate the new string length, but when I try executing it the output is very jumbled. Could you provide some more insight on this? – jellyxbean Jul 15 '16 at 03:27
  • @jellyxbean, as Chris already mentioned, `str[newLenght]` is wrong. I had mentioned that too in my answer, which you unaccepted, which is OK of course, but I feel that you just jumped to this answer without understanding it either. – gsamaras Aug 01 '16 at 17:36
  • @gsamaras I actually figured it out using the str.resize() function which is why I ended up accepting this answer instead! It makes sense now; I am allocating space for the string with a size of new length and then setting the last index to '\0'. – jellyxbean Aug 01 '16 at 17:40
  • @jellyxbean but as Chris correctly said, the `std::string` objects maintain the NULL terminator internally. I don't get your point, but anyway, the comment's section is not for chatting. If you found the solution good. If not, please post a new question (and share the link if you like). – gsamaras Aug 01 '16 at 17:47
1
int newLength = strLen + count * 2;

says to allocate space (later), equal to the length of the string, plus the number of whitespaces found multiplied by two, which makes sense.

For example: so glad to help, should use the slots that the whitespaces live into for the % and they will need two more slots each, for the 20 part of the replacement that will come into play.


This is WRONG:

str[newLength] = '\0';

can't you see? You access memory out of the bounds of your string. You act like you actually allocated space equal to the newLength, but you haven't that anywhere in the code yet.

Out of bounds accessing result in Undefined Behavior and that's bad.


The else statement is just for copying non-whitespace characters, but you should already given up on that code (if it's not yours) and start from scratch or/and take a sneak peak at: Encode/Decode URLs in C++.


As for the wrong result, you should know by reaching that point of that answer, that this is expected.

Community
  • 1
  • 1
gsamaras
  • 71,951
  • 46
  • 188
  • 305
0

Trying to do the modification in place is tricky. It's much easier to create a new string:

std::string new_string;
for (int i = 0; i < str.length(); ++i) {
    if (str[i] == ' ')
        new_string += "%20";
    else
        new_string += str[i];
}
return new_string;

or, if you like range-for:

std::string new_string;
for (char ch : str) {
    if (ch == ' ')
        new_string += "%20";
    else
        new_string += ch;
}
return new_string;
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
0

You can change that string argument in function to reference, then there wont be any need for new string, at other part of the code, you can use insert function to add '2' and '0', and you only need to convert space to '&'.

void replaceSpaces(string &str) {
        size_t strLen = str.length();
        for (int i = 0; i < strLen; i++) {
            if (str[i] == ' ') {
                str[i] = '%';
                str.insert(str.begin() + i + 1, '2');
                str.insert(str.begin() + i + 2, '0');
                strLen += 2;
            }
        }
    }
adoooo
  • 1
  • 1
  • 2
  • Modifying in place is very inefficient. Every call to `insert()` has to copy all the remaining characters in the string, so this is `O(m*n)` where `m` is the number of spaces and `n` is the size of the string. – Barmar Jul 13 '16 at 23:30
0

This is easy; replace examplestring with your string in the code, and use as you would:

#include <iostream> //debug output
#include <string>

using std::string;
using std::cout;
using std::endl;

//the string to convert
string examplestring = "this is the example string for spaces into %20";

int main()
{
    int countspaces = 0; //its faster to fill a known size
    for (auto &x : examplestring)if (x == ' ')countspaces++; //counts spaces

    string newstring; //declare new string
    newstring.resize(examplestring.size() + (countspaces*3)); //pre-set size to make it run faster

    int newstringiterator = 0; //keep track of new string location

    //if ' '(space), place %20 in newstring and add 3 to iteration
    //else just place the letter and iterate

    for (int i=0;i<examplestring.size();i++)
    {
        if (examplestring[i] == ' ') 
        { 
            newstring.insert(newstringiterator, "%20");
            newstringiterator += 3;
        }
        else newstring[newstringiterator++] = examplestring[i];
    }

  //final newstring is the original with %20 instead of spaces. 
  cout << newstring << endl;

  system("PAUSE"); //to read console output
  return 0; //return to zero
}

This will output newstring, which is the old string with '%20' instead of spaces.

Charlie
  • 585
  • 9
  • 17