2

I'm reading input using istringstreams for easy conversion from string to integer. I'm having trouble reading the line when it contains errors, such as "1 45 3 XXXX 45 X", where i want it to simply ignore the letters. Normally, without any errors i would just have done:

string s = "1 2 34 5 6";
istringstream stream(s);
int temp;

cout << s << " -> ";

while(stream >> temp){
    //do something with temp for ex:
    cout << temp << " ";
}

This would give

 "1 2 34 5 6" -> 1 2 34 5 6

Obviously this doesnt work when i have a string of the form "1 45 3 XXXX 45 X" as it would break at XXXX and not continue. But what i would like to get is:

"1 45 3 XXXX 45 X" -> 1 45 3 45

So, i know the problem, but im stuck on how to solve it. I got this feeling there should be a very simple solution to this, but i cant figure it out, and most examples ive searched for online don't take errors in data into account or are too advanced for my needs.

eee
  • 23
  • 3
  • 2
    possible duplicate of [Filtering out invalid user inputs](http://stackoverflow.com/questions/3875780/filtering-out-invalid-user-inputs) or [how do I validate user input as a double in C++?](http://stackoverflow.com/questions/3273993/how-do-i-validate-user-input-as-a-double-in-c) – Anonymous Mar 31 '12 at 17:40
  • doesn't this raise an exception? catch it! – Not_a_Golfer Mar 31 '12 at 17:42
  • @Not_a_Golfer: If you enable them, then yes. Otherwise everything is silent. – Anonymous Mar 31 '12 at 17:46

5 Answers5

4

If you want to process a string and get everything that seems int-like then try the following approach:

#include <iostream>
#include <string>
#include <sstream>


int main() {
    std::string s = "1 45 3 XXXX 45 X 11111111111111111111 2.3";
    std::istringstream stream(s);
    int foo;

    while(true) {
        if(stream >> foo) {
            std::cout << foo << std::endl; // do something with the int
        } else if (stream.eof()) {
            break; // We're done
        } else {
            // We've hit a non-int
            stream.clear(); // clear the error flags
            stream.ignore(); // ignore everything till the delimeter
        }
    }
}

Or alternatively a version using exceptions:

int main() {
    std::string s = "1 45 3 XXXX 45 X 11111111111111111111 2.3";
    std::istringstream stream(s);
    stream.exceptions(std::ios::failbit | std::ios::badbit);
    int foo;

    while(!stream.eof()) {
        try {
            stream >> foo;
            std::cout << foo << std::endl;
        } catch (const std::ios::failure & e) {
            stream.clear();
            stream.ignore();
        }
    }
}

Output:

1
45
3
45
2
3
Anonymous
  • 18,162
  • 2
  • 41
  • 64
  • I did get the filtering suggested by others to work, but this was a much nicer and simpler solution in my opinion. Bonus that it catches all types of error. This was really helpful, thank you:) – eee Mar 31 '12 at 21:10
1

Removing anything that isn't a number or whitespace from your string before parsing it solves your problem. Did you consider that?

Something like this should do it.

void filter(string * input) {
    for(int i = 0; i < input->length(); i++) {
        char val = input->at(i);
        if (val != ' ' && !isdigit(val)) {
            // not a valid character
            input->erase(i, 1);
            i--;
        }
    }
}
cplusplus
  • 614
  • 6
  • 14
  • This doesn't *quite* work, since if you have a number in the input stream that overflows an `int`, this process will not detect it. – templatetypedef Mar 31 '12 at 17:53
  • @templatetypedef iterating through the characters to check if the char value is between the '0' and '9' or is whitespace does not involve any thing that can overflow an int. – cplusplus Mar 31 '12 at 18:13
  • @cplusplus- Sorry, to clarify - even if you remove all non-digit, non-space characters, you may still end up with a string of values that cannot be converted to `int`s, since the values are too large. For example, the string 11111111111111111111111111111111111 is valid by the above code, but would cause a stream to fail. Does that make sense? – templatetypedef Mar 31 '12 at 18:16
  • @templatetypedef perfect sense, good pointing that out, however it's an additional (different) problem OP needs to address. – cplusplus Mar 31 '12 at 18:25
  • Seems a bit of an hassle to write filtering considering every possible error in that case. As the readin returns an error when it has failed, shouldnt it be possible to rewrite the loop somehow to make it go over the whole string (unfilterted) and ignoring the failed readins? like a natural filter? seems like it would be easier than filtering for each possible error, when the >> already will be false when not successfull (though i do like your suggestion, im trying out the idea of filtering right now:) – eee Mar 31 '12 at 18:35
  • Another way to approach this which i think you might be getting at above is to split the string into an array (e.g. you can use `strtok()`) based on whitespace. You go from `"1 garbage 2 34 5 XXX , 6"` to an array of `{"1", "garbage", "2", "34", "5", "XXX", ",", "6"}`. After that you can then run the `atoi()` function on each member of the array, ignoring failures. But that means changing a lot of what you already have, and the filter function above works perfectly, i tried it on your code. – cplusplus Mar 31 '12 at 18:42
0

Filter alphabets in the string before passing to istringstrem as below

string s = "1 2 34 xxx 5 6"; 
string data;

for (unsigned int i = 0; i < sizeof(s); i++)
{
  if ((std::isdigit(s[i])) || (s[i] = ' '))
  data += s[i];
}
istringstream stream(data); 
cout << data << " -> ";  
vinitha
  • 21
  • 2
0

You could simply convert it to C style string and scan through to see if there's at least one non-digit. If so, break & don't print the string.

char *cstr;
bool flag = true;

while(stream >> temp){
    i=0;
    flag = true;
    cstr = new char[temp.size()+1];
    strcpy (cstr, temp.c_str());
    while(cstr[i]) {
     if(!isdigit(cstr[i])) { 
       flag = false;
       break;
     }
    i++;
    }
    if(flag) 
    cout << temp << " ";
}

Not sure if this is the efficient way but my guess is that even with any other library function, you may have to do equivalent scan through in one way or another. So I think this is fine.

P.P
  • 117,907
  • 20
  • 175
  • 238
0

If the undesirable text (or better, the desirable integers) is always in the same positions, I would just use sscanf() instead:

std::string s = "1 45 3 XXXX 45 X";
int temp[4] = {0}; 

cout << s << " -> "; 

sscanf(s.c_str(), "%d %d %d %*s %d", &temp[0], &temp[1], &temp[2], &temp[3]);

for(int i = 0; i < 4; ++i)
{ 
    cout << temp[i] << " "; 
} 
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770