0

I want to read custom file structure and output them, also i want to ignore the lines which are not in the right format. Like comments, titles, etc.,

I've tried this code but its stop looping when it meets a line which is out of the structure.

Here is the txt file.

1001        Promod Dinal        IT-K20      42      42
1002        Sahan Navod         BM-K11      65      28

day_02

1003        Kaushani Dilinika   BM-K12      69      49
1004        Fathima Sahana      QS-K14      73      43  
int main()
{
    ifstream thefile;
    thefile.open("GameZone.txt");
    int id;
    char fName[30];
    char lName[30];
    char stream[30];
    int score;
    int time;

    if (!thefile.is_open()) {
        cout << "cant open the file" << endl;
    }
    else {
        while (!thefile.eof()) {
            if (thefile >> id >> fName >> lName >> stream >> score >> time) {
                cout << id << " ," << fName << " ," << lName << " ," << stream << " ," << score << " ," << time << endl;
            }else if(!(thefile >> id >> fName >> lName >> stream >> score >> time)){
                cout << "skip the row" << endl;
                continue;
            }
        }
    }
    return 0;
}

Output

1001 ,Promod ,Dinal ,IT-K20 ,42 ,42
1002 ,Sahan ,Navod ,BM-K11 ,65 ,28
Kamal Thennakoon
  • 1,439
  • 1
  • 7
  • 8
  • 1
    [Why !.eof() inside a loop condition is always wrong.](https://stackoverflow.com/q/5605125/9254539) You will also want to review [Why is iostream::eof inside a loop condition considered wrong?](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-considered-wrong) – David C. Rankin Jun 20 '19 at 16:14
  • I assume you are not permitted to use `std::string` or `std::istringstream` – drescherjm Jun 20 '19 at 16:17

2 Answers2

4

Do not try to parse fields directly from the file. Instead, read lines from the file and attempt to parse those lines. Use the following algorithm:

  1. Read a line from the file.
  2. If you were not able to read a line, stop, you are done.
  3. Try to parse the line into fields.
  4. If you were not able to parse the line, go to step 1.
  5. Process the fields.
  6. Go to step 1.
David Schwartz
  • 179,497
  • 17
  • 214
  • 278
  • David, Thank you for the answer. but i don't understand the 3rd step. can you explain it further. It would be really helpful if you can provide some codes with it., – Kamal Thennakoon Jun 20 '19 at 16:41
  • 1
    @CrhunteR, after reading the entire line into a string, e.g. `while (getline (fin, line)) {` create a `stringstream` from the line, e.g. `std::stringstream ss (line);` (I would also have created a struct that holds `int id, score, time; std::string fname, lname, stream;`), then declare and attempt to read from the `stringstream` into the temporary struct, e.g. `record_t record; if (ss >> record.id >> record.fname >> record.lname >> record.stream >> record.score >> record.time)` If that succeeds, just add the struct to a vector, e.g. `records.push_back(record);` – David C. Rankin Jun 20 '19 at 16:50
0

If you are still having difficulty with the implementation, then a very simple implementation would simple read each line, create a stringstream from the line to allow you to attempt to parse your values from, then depending on the result of reading from the stringstream output your values in your (rather strange " ,") csv format, or simply go read the next line and try again.

You should be using std::string instead of char[] to holds your string data in C++. Either will work, but the latter is much more user-friendly and flexible. In either case, you will want to coordinate all the differing types of data that make up one record as a struct. This has many benefits if you are actually doing something more than just dumping your data to stdout. For example, you can store all of your data read in a std::vector of struct and then be able to further process your data (e.g. sort, push_back, or erase records) as needed or pass it to other functions for further processing.

A simple struct using int and std::string could be:

struct record_t {       /* simple struct to coordinate data in single record */
    int id, score, time;
    std::string fname, lname, stream;
};

The reading and outputting of records in your csv format, can then be as simple as using a temporary struct to attempt to parse the line into, and if successful, output (or further use) the data as needed, e.g.

    std::string line;                       /* string to hold line */
    std:: ifstream fin (argv[1]);           /* in stream for file */

    while (getline (fin, line)) {           /* read entire line into line */
        std::stringstream ss (line);        /* create stringstream from line */
        record_t record;                    /* temp struct to read into */
        if (ss >> record.id >> record.fname >> record.lname >>
            record.stream >> record.score >> record.time)
            /* if successful read from stringstream, output record */
            std::cout << record.id << " ," << record.fname << " ," 
                    << record.lname << " ," << record.stream << " ," 
                    << record.score << " ," << record.time << '\n';
    }

Putting it altogether in a short example that takes the file to be read as the first argument to the program could be:

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

struct record_t {       /* simple struct to coordinate data in single record */
    int id, score, time;
    std::string fname, lname, stream;
};

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

    if (argc < 2) { /* validate at least 1 argument provided */
        std::cerr << "error: filename required.\n";
        return 1;
    }

    std::string line;                       /* string to hold line */
    std:: ifstream fin (argv[1]);           /* in stream for file */

    while (getline (fin, line)) {           /* read entire line into line */
        std::stringstream ss (line);        /* create stringstream from line */
        record_t record;                    /* temp struct to read into */
        if (ss >> record.id >> record.fname >> record.lname >>
            record.stream >> record.score >> record.time)
            /* if successful read from stringstream, output record */
            std::cout << record.id << " ," << record.fname << " ," 
                    << record.lname << " ," << record.stream << " ," 
                    << record.score << " ," << record.time << '\n';
    }
}

(note: do not hard-code filenames or use magic numbers in your code)

Example Use/Output

Output in your rather odd " ," csv format:

$ ./bin/readrecords dat/records.txt
1001 ,Promod ,Dinal ,IT-K20 ,42 ,42
1002 ,Sahan ,Navod ,BM-K11 ,65 ,28
1003 ,Kaushani ,Dilinika ,BM-K12 ,69 ,49
1004 ,Fathima ,Sahana ,QS-K14 ,73 ,43

To make things slightly more useful, you can, instead of simply outputting the records directly, store all records in a std::vector<record_t> (vector of struct). This then opens the possibility of further processing your data. See if you can understand the changes made below on how each record is stored in a vector and then a Range-based for loop is used to loop over each record held in the vector to output your information.

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

struct record_t {       /* simple struct to coordinate data in single record */
    int id, score, time;
    std::string fname, lname, stream;
};

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

    if (argc < 2) { /* validate at least 1 argument provided */
        std::cerr << "error: filename required.\n";
        return 1;
    }

    std::string line;                       /* string to hold line */
    std:: ifstream fin (argv[1]);           /* in stream for file */
    std::vector<record_t> records;          /* vector of records */

    while (getline (fin, line)) {           /* read entire line into line */
        std::stringstream ss (line);        /* create stringstream from line */
        record_t record;                    /* temp struct to read into */
        if (ss >> record.id >> record.fname >> record.lname >>
            record.stream >> record.score >> record.time)
            records.push_back(record);      /* if good read, add to vector */
    }

    if (records.size() > 0)         /* validate vector contains records */
        for (auto& r : records)             /* loop over all records */
            std::cout << r.id << " ," << r.fname << " ," << r.lname << " ,"
                    << r.stream << " ," << r.score << " ," << r.time << '\n';
    else    /* if no records read, throw error */
        std::cerr << "error: no records read from file.\n";
}

Look things over and let me know if you have any further questions.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • I can't thank you enough for all that you have done,. It also helped me to overcome some of my bad coding practices. I tested all the methods that you mentioned and then select the vector (vector of struct) to store all the records as you explained. – Kamal Thennakoon Jun 20 '19 at 23:41
  • Glad it helped. Sometimes it just takes seeing it written out once, then the pieces start to fall into place. Next would be to move the file-read into its own function, say `std::vector read_data (const std::string& name)` where you pass the filename and the function returns the filled vector, and then a short `void prn_csv_records (std::vector& records)` where you pass a reference to your records and the function then outputs the records in the format you needed. Based on the 2nd answer above, you can probably get that sorted out. Let me know if you run into problem. – David C. Rankin Jun 20 '19 at 23:53