0

I am reading stuff from a file and this is the format : c stands for circle and the double is the radius, r for rectangle and the double is width and height respectively and t for triangle and the double represents side length:

c 12
c 2
r 3 4
c 2.4
t 2.9
3 c          // wrong format 
t 2.9
10 r        // wrong format 

I run this code:

ifstream infile(names);

while(infile >> names) {
    if(names.at(0) == 'c') {           
        double r;                     
        infile >> r;
        cout << "radius = " << r << endl;
    } else if(names.at(0) == 'r') {    
        double w;                     
        double h;                      
        infile >> w;
        infile >> h;
        cout << "width = " << w << ", height = " << h << endl;
    } else if(names.at(0) == 't') {    
        double s;                      
        infile >> s;
        cout << "side = " << s << endl;
    } else {
        continue;
    }
}

infile.close()

And this is the output:

radius = 12
radius = 2
width = 3, height = 4
radius = 2.4
side = 2.9
radius = 0

I was wondering how I can skip the wrong format line. I have tried using geline but still no luck

EDIT: radius, height, width and side have to be > 0

  • 1
    What went wrong with `getline`? It's the right direction. See option 2 of this answer: https://stackoverflow.com/a/7868998/4581301 – user4581301 Mar 12 '18 at 03:12
  • @user4581301 since the values have to be greater than 0, it ends the program. do you know how to skip that line entirely if it starts with a number? –  Mar 12 '18 at 03:28
  • Same deal, Sam. As per https://stackoverflow.com/a/7868998/4581301 , read in a line. Put the line into a `stringstream`. Parse the line from the `stringstream`. If the reading is successful, store it. If not all of the damage is confined to the `stringstream` and you can carry on without the bad line. – user4581301 Mar 12 '18 at 03:49
  • Would that still work tho because the bad format line would start with a number? I want to skip the line completely if it starts with a number. –  Mar 12 '18 at 04:06
  • Read the first token into a string and then check to see if you can turn it into a number. If it converts to a number, bad line. Carry on to the next line. – user4581301 Mar 12 '18 at 04:10
  • @user4581301 how would I check to see if it can be turned into a number –  Mar 12 '18 at 04:12
  • [Start with `std::stod`](http://en.cppreference.com/w/cpp/string/basic_string/stof) because it's easy. If you don't have access to `std::stod`, [use `std::atof`](http://en.cppreference.com/w/cpp/string/byte/atof). If you want more control over the conversion, [use ` std::strtod`](http://en.cppreference.com/w/cpp/string/byte/strtof). Don't think you'll need it, though. – user4581301 Mar 12 '18 at 05:55

2 Answers2

0

The biggest potential problems you are running up against is the fact that you assume a line is valid if it begins with a c, t or r without first validating the remainder of the line matches the format for a circle, triangle or rectangle. While not fatal to this data set, what happens if one of the lines was 'cat' or 'turtle'?

By failing to validate all parts of the line fit the "mold" so to speak, you risk attempting to output values of r, h & w or s that were not read from the file. A simple conditional check of the read to catch the potential failbit or badbit will let you validate you have read what you think you read.

The remainder is basically semantics of whether you use the niceties of C++ like a vector of struct for rectangles and whether you use a string instead of char*, etc. However, there are certain benefits of using a string to read/validate the remainder of each line (or you could check the stream state and use .clear() and .ignore())

Putting those pieces together, you can do something like the following. Note, there are many, many different approaches you can take, this is just one approach,

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>

using namespace std;

typedef struct {        /* simple typedef for vect of rectangles */
    int width, height;
} rect_t;

int main (int argc, char **argv) {

    vector<double> cir;     /* vector of double for circle radius */
    vector<double> tri;     /* vector of double for triangle side */
    vector<rect_t> rect;    /* vector of rect_t for rectangles */
    string line;            /* string to use a line buffer */

    if (argc < 2) { /* validate at least one argument given */
        cerr << "error: insufficient input.\n"
                "usage: " << argv[0] << " filename\n";
        return 1;
    }

    ifstream f (argv[1]);   /* open file given by first argument */
    if (!f.is_open()) {     /* validate file open for reading */
        cerr << "error: file open failed '" << argv[1] << "'.\n";
        return 1;
    }

    while (getline (f, line)) {     /* read each line into 'line' */
        string shape;               /* string for shape */
        istringstream s (line);     /* stringstream to parse line */
        if (s >> shape) {           /* if shape read */
            if (shape == "c") {     /* is it a "c"? */
                double r;           /* radius */
                string rest;        /* string to read rest of line */
                if (s >> r && !getline (s, rest))  /* radius & nothing else */
                    cir.push_back(r);   /* add radius to cir vector */
                else    /* invalid line for circle, handle error */
                    cerr << "error: invalid radius or unexpected chars.\n";
            }
            else if (shape == "t") {
                double l;           /* side length */
                string rest;        /* string to read rest of line */
                if (s >> l && !getline (s, rest))  /* length & nothing else */
                    tri.push_back(l);   /* add length to tri vector */
                else    /* invalid line for triangle, handle error */
                    cerr << "error: invalid triangle or unexpected chars.\n";
            }
            else if (shape == "r") {    /* is it a rect? */
                rect_t tmp;             /* tmp rect_t */
                if (s >> tmp.width && s >> tmp.height)  /* tmp & nohtin else */
                    rect.push_back(tmp);    /* add to rect vector */
                else    /* invalid line for rect, handle error */
                    cerr << "error: invalid width & height.\n";
            }
            else    /* line neither cir or rect, handle error */
                cerr << "error: unrecognized shape '" << shape << "'.\n";
        }
    }

    cout << "\nthe circles are:\n";     /* output valid circles */
    for (auto& i : cir)
        cout << "  c: " << i << "\n";

    cout << "\nthe triangles are:\n";   /* output valid triangles */
    for (auto& i : tri)
        cout << "  t: " << i << "\n";

    cout << "\nthe rectangles are:\n";  /* output valid rectangles */
    for (auto& i : rect)
        cout << "  r: " << i.width << " x " << i.height << "\n";
}

By storing values for your circles, triangles and rectangles independent of each other, you then have the ability to handle each type of shape as its own collection, e.g.

Example Use/Output

$ ./bin/read_shapes dat/shapes.txt
error: unrecognized shape '3'.
error: unrecognized shape '10'.

the circles are:
  c: 12
  c: 2
  c: 2.4

the triangles are:
  t: 2.9
  t: 2.9

the rectangles are:
  r: 3 x 4

Look things over and let me know if you have further questions. The main takeaway is to insure you validate down to the point you can insure what you have read is either a round-peg to fit in the circle hole, a square-peg to fit in a square hole, etc..

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
0

The only thing I added was a getline where I put a comment at the "else" of the loop.

while(infile >> names) {
    if(names.at(0) == 'c') {           
        double r;                     
        infile >> r;
        cout << "radius = " << r << endl;
    } else if(names.at(0) == 'r') {    
        double w;                     
        double h;                      
        infile >> w;
        infile >> h;
        cout << "width = " << w << ", height = " << h << endl;
    } else if(names.at(0) == 't') {    
        double s;                      
        infile >> s;
        cout << "side = " << s << endl;
    } else {
        // discard of the rest of the line using getline()
        getline(infile, names);
        //cout << "discard: " << names << endl;

    }
}

Output:

radius = 12
radius = 2
width = 3, height = 4
radius = 2.4
side = 2.9
side = 2.9