How to read a stream with comma separated values in C++?

倾然丶 夕夏残阳落幕 提交于 2021-01-28 09:49:11

问题


I would like to emphasize on a fundamental question below:

Assume you have a CSV file and fill cells with Input Headers

The code should read this from .csv file and write the results into .csv file. It is nice to also code output total number of cases, plus average of cases. Here is a taken sample form SO and I would like to see how this can be efficiently adopted to complete this basic example.

   void create() 
{ 
    // file pointer 
    fstream fout; 

    // opens an existing csv file or creates a new file. 
    fout.open("reportcard.csv", ios::out | ios::app); 

    cout << "Enter the details of 5 students:"
        << " roll name maths phy chem bio"; 
    << endl; 

    int i, roll, phy, chem, math, bio; 
    string name; 

    // Read the input 
    for (i = 0; i < 5; i++) { 

        cin >> roll 
            >> name 
            >> math 
            >> phy 
            >> chem 
            >> bio; 

        // Insert the data to file 
        fout << roll << ", "
            << name << ", "
            << math << ", "
            << phy << ", "
            << chem << ", "
            << bio 
            << "\n"; 
    } 
} 

Also, Read a particular record

void read_record() 
{ 

    // File pointer 
    fstream fin; 

    // Open an existing file 
    fin.open("reportcard.csv", ios::in); 

    // Get the roll number 
    // of which the data is required 
    int rollnum, roll2, count = 0; 
    cout << "Enter the roll number "
        << "of the student to display details: "; 
    cin >> rollnum; 

    // Read the Data from the file 
    // as String Vector 
    vector<string> row; 
    string line, word, temp; 

    while (fin >> temp) { 

        row.clear(); 

        // read an entire row and 
        // store it in a string variable 'line' 
        getline(fin, line); 

        // used for breaking words 
        stringstream s(line); 

        // read every column data of a row and 
        // store it in a string variable, 'word' 
        while (getline(s, word, ', ')) { 

            // add all the column data 
            // of a row to a vector 
            row.push_back(word); 
        } 

        // convert string to integer for comparision 
        roll2 = stoi(row[0]); 

        // Compare the roll number 
        if (roll2 == rollnum) { 

            // Print the found data 
            count = 1; 
            cout << "Details of Roll " << row[0] << " : \n"; 
            cout << "Name: " << row[1] << "\n"; 
            cout << "Maths: " << row[2] << "\n"; 
            cout << "Physics: " << row[3] << "\n"; 
            cout << "Chemistry: " << row[4] << "\n"; 
            cout << "Biology: " << row[5] << "\n"; 
            break; 
        } 
    } 
    if (count == 0) 
        cout << "Record not found\n"; 
} 


  [1]: https://i.stack.imgur.com/q6VfZ.png

回答1:


I've mainly concentrated on adding overloads for operator<< and operator>> since you showed some interest in those earlier and describe what they are doing in comments in the code.

Since you are mixing input and output from streams that are comma separated and other streams I've added an adapter for CSV streaming as well as overloads for streaming to/from a user.

First, create a class to keep all data that belongs together in one data record. I've made it a simple struct here, which is a class with public access to its members per default.

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

// your student record
struct student {
    std::string name; // It's usually good to have larger types first so name goes first
    int roll;
    int math;
    int phy;
    int chem;
    int bio;
};

// read a student from an istream (like std::cin) - whitespace separated
std::istream& operator>>(std::istream& is, student& s) {
    return is >> s.roll >> s.name >> s.math >> s.phy >> s.chem >> s.bio;
}

// write a student to an ostream (like std::cout)
std::ostream& operator<<(std::ostream& os, const student& s) {
    return os << "Details of Roll " << s.roll << ":\n"
              << "Name: " << s.name << '\n'
              << "Maths: " << s.math << '\n'
              << "Physics: " << s.phy << '\n'
              << "Chemistry: " << s.chem << '\n'
              << "Biology: " << s.bio << '\n';
}
//--------------------------------------------------------------------------------------
// An adapter for comma separated streaming
struct CSVStudent {
    CSVStudent(student& s) : stud(s) {}
    CSVStudent(const CSVStudent&) = delete;

    // The CSVStudent holds a reference to a "student"
    student& stud;
};

// read a record from an istream - comma separated
std::istream& operator>>(std::istream& is, CSVStudent& csvstud) {
    std::string line;

    student& s = csvstud.stud; // an alias to the student to have to type less

    if(std::getline(is, line)) { // read a complete line
        // put the line in an istringstream for extraction:
        std::istringstream ss(line);

        char delim; // a dummy for reading commas

        // Extract the comma separated values. "delim" is not checked so it could be
        // any char breaking up the int:s.
        //
        // The below does things in the following order:
        // 1.  "ss >> s.roll >> delim"
        //     This extracts roll and a comma and returns
        //     a reference to ss, which is used in 2.
        // 2.  std::getline(ss, s.name, ',')
        //     Extracts a string until a comma is encountered.
        // 3.  Normal extraction for the rest of the int:s with the
        //     dummy variable "delim" where the commas are supposed to be.

        if(not(std::getline(ss >> s.roll >> delim, s.name, ',') >> s.math >> delim >>
               s.phy >> delim >> s.chem >> delim >> s.bio)) {
            // If we get here, the extraction from the istringstream failed, so set
            // the failstate on the istream too. Note the "not" on the line above.
            is.setstate(std::ios::failbit);
        }
    }
    return is;
}

// write a record to an ostream - comma separated
std::ostream& operator<<(std::ostream& os, const CSVStudent& csvstud) {
    const student& s = csvstud.stud;
    os << s.roll << ',' << s.name << ',' << s.math << ',' << s.phy << ',' << s.chem
       << ',' << s.bio << '\n';
    return os;
}
//--------------------------------------------------------------------------------------
// get all students in the file as a std::vector<student>
std::vector<student> read_student_file(const std::string& filename) {
    std::vector<student> retval;
    std::ifstream fin(filename);
    if(fin) { // file opened successfully
        student stud;
        CSVStudent csvstud{stud}; // holds a reference to stud

        // loop for as long as student records can be read successfully
        while(fin >> csvstud)       // use the csv sdapter
            retval.push_back(stud); // and put the stud in the vector
    }
    return retval;
}
//--------------------------------------------------------------------------------------
void create(const std::string& filename) {
    // open an existing csv file or creates a new file.
    std::ofstream fout(filename, std::ios::out | std::ios::app);
    if(fout) {
        std::cout << "Enter the details of 5 students:"
                     " roll name maths phy chem bio\n";

        // Read the input
        for(int i = 0; i < 5; i++) {
            student stud;
            std::cout << (i + 1) << ": ";
            if(std::cin >> stud) {
                // Insert the data to file if one was entered successfully
                fout << CSVStudent(stud); // uses the adapters operator<<
            } else {
                std::cerr << "You failed to enter data for student " << (i + 1) << '\n';
                break;
            }
        }
    }
}
//--------------------------------------------------------------------------------------
int main() {
    std::string filename = "reportcard.csv";

    std::vector<student> students = read_student_file(filename);

    std::cout << "There are " << students.size() << " students in the file.\n";
    if(not students.empty()) {
        // show the last record if there are any records in the file
        std::cout << "Record " << students.size() << " is:\n\n";
        std::cout << students.back() << '\n';
    }

    // create 5 new records
    create(filename);
}

If reportcard.csv contains this:

1,Ted,1,2,3,4
2,Foo,2,3,4,5
3,Bar,3,4,5,6
4,Baz,4,5,6,7
5,Bork,5,6,7,8

The program should start up like this:

There are 5 students in the file.
Record 5 is:

Details of Roll 5:
Name: Bork
Maths: 5
Physics: 6
Chemistry: 7
Biology: 8

Enter the details of 5 students: roll name maths phy chem bio
1: <and here is where you're supposed to enter the first of 5 new students>


来源:https://stackoverflow.com/questions/60726552/how-to-read-a-stream-with-comma-separated-values-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!