2

I came across an interesting exercise. Basically I have to remove all excessive spaces from a string, and by excessive I mean all the spaces at the beginning of the string, at the end of the string, and there should not be more than two consecutive whitespaces.

This is what I tried

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

string RemoveSpaces(string s) {
    auto it = s.begin();
    while(*it == ' ') { // removes spaces at the beginning
        if(*it == ' ') s.erase(it);
    }

    auto it2 = s.end(); // removes spaces at the end of a string
    it2--;

    while(*it2 == ' ') it2--;
    it2++;
    while(*it2 == ' ') {
        if(*it2 == ' ') s.erase(it2);
    }

    for(int i = 0; i < s.length() - 1; i++) { // this does NOT work
        if(s.at(i) == ' ' && s.at(i + 1) == ' ') {
            auto it3 = s.at(i);
            s.erase(it3);
        }
    }

    return s;
}

int main() {
    string s;
    getline(cin, s);

    string s1 = RemoveSpaces(s);

    cout << "|" << s << "|" << endl;
    cout << "|" << s1 << "|" << endl;

    return 0;
}


However this does not do what I expected it to do. My code successfully removes the spaces at the beginning and the end of a string, but I cannot go further than that. Can anyone help?

EDIT I fixed the problem. Here is the part of code that now deletes extra whitespaces between words, so that it leaves just one space between two words.

    auto it3= s.begin();

    for(int i = 0; i < s.length() - 1; i++) {
        if(s.at(i) == ' ' && s.at(i + 1) == ' ') {
            s.erase(s.begin()+i);
            i--;
        }

    }

Thank you all for helping me.

l0ner9
  • 263
  • 1
  • 7
  • 1
    Probably you should split the string to a `vector`and concatenate the vector of strings to a new string. – harper Jul 02 '19 at 14:36
  • `but I cannot go further than that` Your problem isn't clear. You tried but couldn't think of how to do it? – Gaurav Singh Jul 02 '19 at 14:36
  • 1
    Walk the string constructing a new string. State starts out as "eat spaces", don't emit a space unless you find a non-space, then first emit the pending space, then the non-space. That will let you discard trailing spaces. – Eljay Jul 02 '19 at 14:38
  • @GauravSingh exactly! – l0ner9 Jul 02 '19 at 14:40
  • please try to formulate a question more specific than "Can anyone help?". Your code seems to have an attempt of removing white spaces in the middle but it is not clear in what way that does not work. Did you try to use a debugger? – 463035818_is_not_an_ai Jul 02 '19 at 14:41
  • @formerlyknownas_463035818 I would suggest you try running the code. I was very clear and elaborate about what the exercise was, what DOES work and what does NOT work. I should have mentioned in what way it does not work(the code that is supposed to remove whitespaces between the end and beginning does not do anything, the string just stays as it is) but to say that my elaboration narrows down to "Can anyone help" is not what my post was about. – l0ner9 Jul 02 '19 at 14:44
  • Please use a code formatter like http://format.krzaq.cc/. Is this your real code? It doesn't compile and after fixing the error it throws an exception. – Thomas Sablik Jul 02 '19 at 14:50
  • You might want to give some sample input strings where the output is not expected or when program is crashing. Also mention what exactly is the problem. From my test, it seems it is crashing when there is leading space in input string. – Gaurav Singh Jul 02 '19 at 14:51
  • I think most people have assumed you want to turn `" a b "` into `"ab"`, but I believe you actually want `"ab"` (with _two_ spaces). Is this so? If so, you should [edit] your question to clarify. – Martin Bonner supports Monica Jul 02 '19 at 14:55
  • @GauravSingh here is an example. If I have " This is a string " my code output would be "This is a string" without the spaces at the beginning and end. – l0ner9 Jul 02 '19 at 14:55
  • *""Can anyone help" is not what my post was about."* - Maybe I just think I saw that very question at the end of your post. Regardless, clearly `auto it3 = s.at(i); s.erase(it3)` won't work. The `erase` method takes an *iterator*; you're giving it a `char`. And you may want to consider what `erase` returns, as it will come in handy for this task. Check the documentation of that member. – WhozCraig Jul 02 '19 at 14:56
  • @WhozCraig It would have been just about that if I had copied my code, told that it was not working and said Can anyone help. It is kind of funny that you don't seem to notice that I elaborated what the exercise was, what my code does do and what it does NOT do, and even put up comments in the code so it is more readable and understandable. Ah yes, now I understand. Let me see if I can fix it quickly. Thanks for your help – l0ner9 Jul 02 '19 at 14:57
  • 1
    `while(*it == ' ') {if(*it == ' ') s.erase(it);}` won't work. Once you have called `s.erase(it)` you have invalidated `it`. Much better to use `while (s[0] == ' ') { s.erase(s.begin())`. Aside: you don't need the test inside the loop (it will always be true). – Martin Bonner supports Monica Jul 02 '19 at 14:59
  • my comment was not meant as an offense but rather trying to support. If you think the question is clear enough then maybe also others will and you can get an answer, all fine. I dont understand what that part of the code does without a debugging sessions, hence I thought it could be more clear. Nevermind... – 463035818_is_not_an_ai Jul 02 '19 at 15:05
  • @formerlyknownas_463035818 I am sorry if I came across as offensive, but you made it sound like the only thing I wrote was Can anyone help. I am very aware that each of you is giving away your precious time to help strangers just for the love of it but I think I might have said a tad bit more than "Can anyone help". – l0ner9 Jul 02 '19 at 15:07
  • I didnt claim that your whole post boils down to "Can anyone help?". Indeed there is more information. Though as a matter of fact that is the only concrete quesiton in your question and a good question should contain a specific question, the input, output and expected output. Even if you think it is obvious it is better to mention all that explicitly, as often it turns out that it isnt that obvious at all. Just saying... – 463035818_is_not_an_ai Jul 02 '19 at 15:09
  • 1
    @0ner9 In the original post you wrote that two consequitive spaces are allowed. – Vlad from Moscow Jul 02 '19 at 16:19
  • @l0ner9 Please [edit](https://stackoverflow.com/posts/56854810/edit) the question if two or more consecutive spaces are *not* allowed. – Ted Lyngmo Jul 02 '19 at 16:25

5 Answers5

3

You could make use of std::stringstreams capabilities:

#include <string>
#include <sstream>

std::string RemoveSpaces(const std::string& str) {
    std::string out;                 // the result
    std::string word;                // used to extract words from str 
    std::istringstream ss(str);      // create an istringstream from str
    while(ss >> word) {              // extract a word
        if(!out.empty()) out += ' '; // add a space between words
        out += word;                 // add the extracted word 
    }
    return out;
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
0
#include <iostream>

std::string remove_excessive_spaces(const std::string& str)
{
    std::string ret;

    for(std::size_t i = str.find_first_not_of(' '); i < str.size(); i++)
    {
        if(str[i] != ' ' || ret[ret.size() - 1] != ' ')
        {
            ret.push_back(str[i]);
        }
    }

    return ret;
}


int main()
{
    std::string sentence("    some weird  sentence  xd    ");

    std::cout<<sentence<<"\n";
    std::cout<<remove_excessive_spaces(sentence)<<"\n";

    return 0;
}
phön
  • 1,215
  • 8
  • 20
  • 1
    It is an incorrect function because two consecutive spaces are allowed and the original string should be changed without creating a new string. – Vlad from Moscow Jul 02 '19 at 15:20
  • @VladfromMoscow based on "there should not be more than two consecutive whitespaces" you're correct. But I think it's not what the OP actually wants, if you look at his code, that's not exactly what he's trying to do. – Arthur Passos Jul 02 '19 at 15:38
  • @Vlad from Moscow Modifying the `s` parameter in-place will not enable RVO, so a new string will be created anyway. May be could we `std::move()` the modified `s` at `return`? But I'm not sure this is the main topic of the question. – prog-fh Jul 02 '19 at 15:44
  • @prog-fh I have not understood what is the meaning of RVO in this case and why a new object of the type std;:string will be created. – Vlad from Moscow Jul 02 '19 at 15:50
  • 1
    @Vlad from Moscow I was referring to ''the original string should be changed without creating a new string''. The OP's function takes the string by value and returns by value so even if the parameter is modified in-place the result will be a copy of this modified string (the parameter cannot be the result, a local variable yes). On the other hand, if we create and populate a new string, then this new string will be the result (without any other copy, RVO). So if we really wanted an in-place modification, the only option would be passing the string by non-const reference. – prog-fh Jul 02 '19 at 16:00
  • @prog-fh He just incorrectly declared his function.:) – Vlad from Moscow Jul 02 '19 at 16:01
  • I would rather answer the question as stated instead of answering the question OP actually wants. The next one reading the question maybe wants something different and has no clue what OP actually wanted, but they can only read the question as is. "Not more than two" means two are ok but not three – 463035818_is_not_an_ai Jul 02 '19 at 16:21
0

I use this little chunk of code frequently when my simulations read input from the command line or text files to ensure the data is clean. It makes use of the STL's copy_if() with a custom predicate. It reduces all inner whitespace to a single space, and removes all leading and trailing whitespace.

/* Removes inner whitespace from str_in */
string remove_excess_spaces(string str_in){
    //predicate class to use with copy_if
    //determines if a character should be copied based on if there is
    //a space following a non-space character
    class copy_space{
    public:
        copy_space() : was_last_space(true) {};

        bool operator()(char c) {
            if(!isspace(c)){
                was_last_space = false;
                return true;
            }
            else{
                if(was_last_space) return false;

                was_last_space = true;
                return true;
            }
        }
    private:
        bool was_last_space;
    } pred;

    //copy into single-spaced string
    string str;
    str.resize(str_in.size());
    auto it = std::copy_if(str_in.begin(), str_in.end(), str.begin(), pred);

    //remove trailing empty spots
    str = str.substr(0, it - str.begin());

    //convert all remaining whitespace to ' '. Accounts for tabs, miscelaneous newlines, etc.
    std::replace_if(str.begin(), str.end(), isspace, ' ');

    //remove any trailing whitespace
    if(isspace(str[str.size() - 1]))
        str.erase(str.size() - 1, 1);

    return str;
}

Play around with a working example here!

Amanda
  • 314
  • 3
  • 12
0

The standard library already has a function for searching consecutive elements in a range: search_n. With this function you can find the positions where a range of more than 2 whitespaces start. you can then erase this ws and go on, finding more consecutive whitespaces:

inline auto remove_triple_spaces(std::string str)
{    
    for(auto r = std::search_n(str.begin(), str.end(), 3, ' '); r != str.end(); r = std::search_n(r, str.end(), 3, ' '))
    {
        r = str.erase(r); // returns new iterator pointing to the 2nd ws. the first got erased        
    }   
    return str;
}

Removing the leading and trailing whitespaces is called trim. you can do it with std::find_if coming from the beginning and once from the end:

inline auto trim_ws(std::string s)
{
    // erase all trailing ws (start from the end for performance)
    auto one_past_last_non_ws = std::find_if(s.rbegin(), s.rend(), [](char c){ return c != ' '; }).base();
    s.erase(one_past_last_non_ws, s.end());

    //erase all leading ws
    auto first_non_ws = std::find_if(s.begin(), s.end(), [](char c){ return c != ' '; });   
    s.erase(s.begin(), first_non_ws);

    return s;
}

You can now combine both functions:

#include <string>
#include <iostream>
#include <algorithm>

inline auto trim_ws(std::string s)
{
    // erase all trailing ws
    auto one_past_last_non_ws = std::find_if(s.rbegin(), s.rend(), [](char c){ return c != ' '; }).base();
    s.erase(one_past_last_non_ws, s.end());

    //erase all leading ws
    auto first_non_ws = std::find_if(s.begin(), s.end(), [](char c){ return c != ' '; });   
    s.erase(s.begin(), first_non_ws);

    return s;
}

inline auto remove_triple_spaces(std::string str)
{    
    for(auto r = std::search_n(str.begin(), str.end(), 3, ' '); r != str.end(); r = std::search_n(r, str.end(), 3, ' '))
    {
        r = str.erase(r); // returns new iterator pointing to the 2nd ws. the first got erased        
    }   
    return str;
} 

int main()
{
    std::string s(" h sdf    fd das fdsf sd fallo ");
    std::cout << s << "\n";
    std::cout << remove_triple_spaces(trim_ws(s)) << "!\n";
    return 0;
}
phön
  • 1,215
  • 8
  • 20
0

So many lines of code . . .

Here the C++ standard solution using nested std::regex. With that, we get a one-liner.

Please see:

#include <iostream>
#include <string>
#include <regex>

int main()
{
    // Test string
    std::string s("   abc de  fg   hi    jk     lm  ");

    // First remove leading and trailing spaces, then remove too many spaces in between
    s = std::regex_replace(std::regex_replace(s, std::regex("^ +| +$|(\S+)"), "$1"), std::regex(" {3,}"), "  ");

    // Show result
    std::cout << s << '\n' << std::string(s.size(), '^') << '\n';

    return 0;
}

This is really not complicated: The inner regex, replaces

  • The begin of text followed by
  • one or more spaces

OR

  • One or more spaces followed by
  • end of text

with the orignal rest text. And with this new text, we go into the second regex, where we simply replace 3 or more spaces by 2 spaces.

Should be understandable. And is ultra short.

A M
  • 14,694
  • 5
  • 19
  • 44