6

I have a bunch of input files that look like the following:

(8,7,15)
(0,0,1) (0,3,2) (0,6,3)
(1,0,4) (1,1,5)

I need to write a function that parses these inputs one number at a time, so I need to be able to separate the input by numbers, e.g.: 8, then 7, then 15, then 0, another 0, so on.

The only way I've thought of so far is to use istream.get() which returns the next character's ASCII code, which I can convert back to its character format by casting it to char. Then I'd check if the character was a number or not (so the brackets are ignored) but this way, any double (or triple) digit numbers are only read one digit at a time.

What would be the best way to achieve this?

By the way, I must use istream. It's part of the specification that I'm not allowed to change

Thanks

Arvin
  • 1,307
  • 2
  • 19
  • 31
  • Is there anything wrong with reading double or triple digit numbers one character at a time? All you have to do is multiply the number read so far by 10 and then add the value of the next digit. Put that in a loop and you're done. – john Aug 19 '11 at 06:10
  • Thanks john, that's basically the manual way of doing it, I was hoping there would exist somewhere in STL something that would help me to do this much nicer! – Arvin Aug 19 '11 at 06:13
  • 1
    Well you could mess aruond with istream::unget which returns the last read character to the string. That way you could unget the first digit and then use >>. But frankly the manual way is the nice way. – john Aug 19 '11 at 06:17
  • 1
    Or, since your input seems quite regular you can read the puncutation into dummy variables. Something like `in >> lparen >> num1 >> comma1 >> num2 >> comma2 >> num3 >> rparen;` where lparen etc are declared as char. But such code is quite brittle, I would do it the manual way. – john Aug 19 '11 at 06:20

5 Answers5

7

This is one solution:

struct integer_only: std::ctype<char> 
{
    integer_only(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table()
    {
        static std::vector<std::ctype_base::mask> 
            rc(std::ctype<char>::table_size,std::ctype_base::space);

        std::fill(&rc['0'], &rc['9'+1], std::ctype_base::digit);
        return &rc[0];
    }
};

int main() {
        std::cin.imbue(std::locale(std::locale(), new integer_only()));
        std::istream_iterator<int> begin(std::cin);
        std::istream_iterator<int> end;
        std::vector<int> vints(begin, end);
        std::copy(vints.begin(), vints.end(), std::ostream_iterator<int>(std::cout, "\n"));
        return 0;
}

Input:

(8,7,15)
(0,0,1) (0,3,2) (0,6,3)
(1,0,4) (1,1,5)

Output:

8 7 15 0 0 1 0 3 2 0 6 3 1 0 4 1 1 5 

Online demo : http://ideone.com/Lwx9y

In the above, you've to replace std::cin with the file stream after opening the file successfully, as:

 std::ifstream file("file.txt");
 file.imbue(std::locale(std::locale(), new integer_only()));
 std::istream_iterator<int> begin(file);
 std::istream_iterator<int> end;
 std::vector<int> vints(begin, end); //container of integers!

Here, vints is a vector which contains all the integers. You would like work with vints to do something useful. Also, you can use it where int* is expected as:

void f(int *integers, size_t count) {}

f(&vints[0], vints.size()); //call a function which expects `int*`.

Similar trick can be applied when reading only words from a file. Here is an example:

Community
  • 1
  • 1
Nawaz
  • 327,095
  • 105
  • 629
  • 812
  • That code alters the stream so it considers all non-digits as whitespace! I'm not sure if I'm impressed or not. Seems a bit quick and dirty to me. – john Aug 19 '11 at 06:29
  • Thanks for this! will test it out soon. btw, the file is read as standard input, so no need to open the file :) – Arvin Aug 19 '11 at 06:29
  • Actually I think I agree with john on this, I'm not sure if I like the idea of altering the stream, and having to declare a struct just to achieve this relatively simple task, seems a bit overkill compared to john's method! – Arvin Aug 19 '11 at 06:36
  • @Arvin: What is wrong with providing your own locale to the stream? Why the function `imbue()` exists in the first place if not to allow programmers to change the locale? Also, I wonder if the problem was so `simple task`, then why did you ask it in the first place? :-/ – Nawaz Aug 19 '11 at 06:39
  • @Nawaz: A quick question. You can adapt the above code to handle signed numbers? Presumably you would add '+' and '-' to your table as ctype_base::punct. Or does the istream code automatically handle signedness? – john Aug 19 '11 at 06:44
  • Note: On some systems if you imbue a file-stream after it has been opened the imbue is ignored. Thus you should imbue the stream before opening it. – Martin York Aug 19 '11 at 06:47
  • @john: This solution of course has some limitation, that it will work only with non-negative integers. – Nawaz Aug 19 '11 at 06:47
  • @Martin: Yes. I knew that but didn't mention in my answer to make the solution simple, and because I didn't see any system yet where it would fail. – Nawaz Aug 19 '11 at 06:49
  • Hmm yes you're right, I suppose there's nothing wrong with it other than my being uncomfortable using locales since I'm not familiar with them. The reason I ask was because I'm a C++ newbie so I'm not yet able to handle simple tasks like these (It's simple for my purposes since I don't need to validate anything). I'll look into locales and imbue() soon to see if they're easy enough to understand! – Arvin Aug 19 '11 at 07:04
3

Here's some code, you can adapt to meet your precise needs

for (;;)
{
  int ch = in.get();
  if (ch == EOF)
    break;
  if (isdigit(ch))
  {
    int val = ch - '0';
    for (;;)
    {
      ch = in.get();
      if (!isdigit(ch))
        break;
      val *= 10;
      val += ch - '0';
    }
    // do something with val
  }
}

This is untested code.

john
  • 71,156
  • 4
  • 49
  • 68
  • Thanks John! This code looks a bit nicer than Nawaz' solution, I think I like this a bit more, but I'll have to test both version to make up my mind – Arvin Aug 19 '11 at 06:31
  • Actually I think they're very similar. They both essentially ignore the punctuation. The decision you have to make is whether you are going to try to read and validate the punctuation. That's obviously more complex. I posted the above code because I thought you didn't know how to calculate an integer value by reading one character at a time. But I can see now I was wrong in that. – john Aug 19 '11 at 06:35
2

try to read a number. if that fails, clear error state and try to read a char (and ignore it). repeat these two steps until reading a char fails, in which case you are at EOF or true failure.

it might be optimized by recognizing ')' and then reading until '('.

but i don't think it's worth it.

cheers & hth.,

Cheers and hth. - Alf
  • 135,616
  • 15
  • 192
  • 304
2

Another solution:

#include <string>
#include <ostream>
#include <fstream>
#include <iostream>

struct triple
{
    long a;
    long b;
    long c;
};

std::ostream& operator << (std::ostream& os, const triple& value)
{
    return os << value.a << "/" << value.b << "/" << value.c;
}

int main()
{
    std::ifstream stream("Test.txt");
    if (!stream)
    {
        std::cout << "could not open the file" << std::endl;
    }

    std::string dummy;
    triple value;
    while (std::getline(stream, dummy, '(') >> value.a &&
           std::getline(stream, dummy, ',') >> value.b &&
           std::getline(stream, dummy, ',') >> value.c)
    {
        std::cout << value << std::endl;
    }
}
Simon
  • 1,436
  • 8
  • 11
1
int getFirstPos(const string& str)

{
int pos=0,PosHit=0;
bool bfind=false;
if((PosHit=str.find(','))!=string::npos){
    if(!bfind)  pos=PosHit;
    pos=pos>PosHit?PosHit:pos;
    bfind=true;
}
if((PosHit=str.find('('))!=string::npos){
    if(!bfind)  pos=PosHit;
    pos=pos>PosHit?PosHit:pos;
    bfind=true;
}
if((PosHit=str.find(')'))!=string::npos){
    if(!bfind)  pos=PosHit;
    pos=pos>PosHit?PosHit:pos;
    bfind=true;
}
return bfind?pos:string::npos;

}

void main()

{
    ifstream ifile("C:\\iStream.txt");
    string strLine;
    vector<double> vecValue;    //store the datas
    while(getline(ifile,strLine)){
        if(strLine.size()==0)
            continue;
        int iPos=0;
        while((iPos=getFirstPos(strLine))!=string::npos)
            strLine[iPos]=' ';
        istringstream iStream(strLine);
        double dValue=0;
        while(iStream>>dValue)
            vecValue.push_back(dValue);
    }
    //output the result!
    vector<double>::iterator it;
    for (it=vecValue.begin(); it!=vecValue.end()  ; ++it){
        cout<<setprecision(3)<<*it<<endl;
    }
}
jzq
  • 11
  • 4