0

I have a file:

P 0.5 0.6 0.3
30 300
80 150
160 400
200 150
250 300
T
r  45 0 0
s 0.5 1.5 0 0
t 200 –150
.
.
.

When I read in 'P' I know that 3 floats will follow. That will be followed by a finite number of X and Y coordinates. The number will vary until a 'T' is reached which I have to recognize. Then there could be an 'r', 's' or 't' followed by some values.

Anyways I know how to recognize 'P' and then take in the 2 floats but then I know I have to have a while loop for the X and Y coordinates which will stop when I get to a 'T'. I do not know enough about C++ to make the loop stop and recognize the 'T' and then do something else.

An example to explain would be appreciated. Thanks in advance!

Aaron McKellar
  • 627
  • 4
  • 10
  • 20
  • BTW your question made me ask this one: http://stackoverflow.com/questions/2309604 "Does anyone actually use stream extraction operators?" – Dan Feb 23 '10 at 14:09

4 Answers4

9

I'll show you what I think it's the proper C++ way of doing this. First define a class for representing your first line and for doing its IO:

struct FirstLine
{
    double x, y, z;
    friend std::istream & operator>>(std::istream & is, FirstLine & data)
    {
        std::string line, ignore;
        std::getline(is, line);
        std::istringstream iss(line);
        iss >> ignore >> data.x >> data.y >> data.z;
        assert(ignore == "P" && iss);
        return is;
    }
    friend std::ostream & operator<<(std::ostream & os, FirstLine const & data)
    {
        return os << "P " << data.x << " " << data.y << " " << data.z;
    }    
};

I've added some basic error checking with assert, you'll probably want something more robust in your final program.

Now a class for middle lines:

struct MiddleLine
{
    double x, y;
    friend std::istream & operator>>(std::istream & is, MiddleLine & data)
    {
        std::string line;
        std::getline(is, line);
        if(line == "T")
            is.clear(std::ios::failbit);
        else
        {
            int n = sscanf(line.c_str(), "%lf %lf", &data.x, &data.y);
            assert(n == 2);
        }
        return is;
    }
    friend std::ostream & operator<<(std::ostream & os, MiddleLine const & data)
    {
        return os << data.x << " " << data.y;
    }    
};

When we reach the end of the section where the middle lines are we are supposed to encounter a "T". In that case we raise the fail bit of the stream, which will tell client that there are no more middle lines to read.

Finally a class for the last lines:

struct LastLine
{
    std::string identifier; // r, s or t
    std::vector<double> values;
    friend std::istream & operator>>(std::istream & is, LastLine & data)
    {
        std::string line;
        std::getline(is, line);
        std::istringstream iss(line);
        iss >> data.identifier;
        assert(data.identifier == "r" || data.identifier == "s" 
               || data.identifier == "t");
        std::copy(std::istream_iterator<double>(iss), 
                  std::istream_iterator<double>(), std::back_inserter(data.values));
        return is;
    }
    friend std::ostream & operator<<(std::ostream & os, LastLine const & data)
    {
        os << data.identifier << " ";
        std::copy(data.values.begin(), data.values.end(),
                  std::ostream_iterator<double>(os, " "));
        return os;
    }      
};

Last lines are more complicated becase we don't know how many values are in each, so we just read as many as we can.

That was the tricky part. Now our main function will simply read one first line, then an unknown number of middle lines, and finally an unknown number of last lines:

int main()
{
    std::string const data = "P 0.5 0.6 0.3\n
                             "30 300\n"
                             "80 150\n"
                             "160 400\n"
                             "200 150\n"
                             "250 300\n"
                             "T\n"
                             "r  45 0 0\n"
                             "s 0.5 1.5 0 0\n"
                             "t 200 –150";

    std::istringstream iss(data);

    FirstLine first_line;
    iss >> first_line;

    std::vector<MiddleLine> middle_lines;
    std::copy(std::istream_iterator<MiddleLine>(iss), 
              std::istream_iterator<MiddleLine>(), 
              std::back_inserter(middle_lines));
    iss.clear();

    std::vector<LastLine> last_lines;
    std::copy(std::istream_iterator<LastLine>(iss), 
              std::istream_iterator<LastLine>(), 
              std::back_inserter(last_lines));
    assert(iss.eof());       

    std::cout << first_line << "\n";
    std::copy(middle_lines.begin(), middle_lines.end(),
              std::ostream_iterator<MiddleLine>(std::cout, "\n"));
    std::copy(last_lines.begin(), last_lines.end(),
              std::ostream_iterator<LastLine>(std::cout, "\n"));
    return 0;
}

This is the output you'll get::

P 0.5 0.6 0.3
30 300
80 150
160 400
200 150
250 300
r 45 0 0
s 45 0 0 0.5 1.5 0 0
t 45 0 0 0.5 1.5 0 0 200

I've used a string as the source of my data but you'll probably want to read from a file.

And that's all, you can see that I didn't write a single loop.

Here's the code in codepad.

Manuel
  • 12,749
  • 1
  • 27
  • 35
  • Great job showing how to do this with `op>>`. A few questions: 1) Why `sscanf` for `MiddleLine`? 2) Why `getline` a line at a time into an `istringstream` instead of working with the `istream` parameter directly? 3) Is `failbit` the correct bit to be setting? `istream_iterator` goes until end of stream, so why not `eofbit`? – Dan Feb 23 '10 at 14:05
  • 1) I thought it would be less verbose that way 2) Mixing getline and formatted operations (op>>) forces you to use `clear`+`ignore` all the time, which is a pain. 3) It's not really the end of the stream, just the "logical" end of the middle lines section. But EOF could have worked, it's true. – Manuel Feb 23 '10 at 18:00
  • It feels like overkill, does'nt? – user204724 Feb 24 '10 at 10:03
  • @gineer - getting IO right is tricky, I find that a structured approach like this is a time-saver in the long run. Besides I'm only using the most basic IO facilities, I don't know how it can be overkill. IMO overkill would have been writing a parser in ANTLR or in Boost.Spirit. – Manuel Feb 24 '10 at 10:17
  • Thanks for that `std::copy(std::ostream_iterator,...)` idiom, I hadn't seen it before, and I have plenty of use for it! – tzaman Feb 27 '10 at 02:26
0
  • Keep a kind of 'global state'
  • Write a loop that reads a line from the file until end-of-file.
  • Read the line into a buffer
  • Check the first character of the buffer, if it is P or T or r or s or t, change the global state of the application
  • If the first character was a T, use a sscanf(Buffer+1,"%lf %lf %lf",&first,&second,&third) to read the rest of the line.
  • Do something similar if the first character is r, s or t.
  • If the application is in the 'P-state' just scan the buffer using sscanf(Buffer,"%lf %lf",&first,&second)
Patrick
  • 23,217
  • 12
  • 67
  • 130
0

I think you can use standard streams
to check "P" and "T"
use get(char &ch);
and putback(ch) to push it back to stream
and
yourstream >> x >> y >> endl;

http://www.cplusplus.com/reference/iostream/istream/putback/

// Example
// istream putback
#include <iostream>
using namespace std;

int main () {  
  char c;  
  int n;  
  char str[256];  

  cout << "Enter a number or a word: ";
  c = cin.get();  

  if ( (c >= '0') && (c <= '9') )
  {  
    cin.putback (c);
    cin >> n;
    cout << "You have entered number " << n << endl;
  }  
  else
  {  
    cin.putback (c);
    cin >> str;
    cout << " You have entered word " << str << endl;
  }  

  return 0;  
}
Georg Fritzsche
  • 97,545
  • 26
  • 194
  • 236
user204724
  • 160
  • 9
0

user to read text file line by line and then run string word;

ifstream fin(your file)
while(! fin.eof())
{
    string line = fin.getline();
    istringstream iss(line, istringstream::in);
    string token;
    while( iss >> token)     
    {
      if (token.compare("T")) {
        ...
      } else {
        float f = atof(token.c_str());
     }
    }
}
Moisei
  • 1,162
  • 13
  • 30