如何从CSV文件中的数据创建矢量?

问题描述 投票:-1回答:1

“您的程序应使用CSVReader读取students.csv,然后将读取的信息存储到Student的向量中”。我将students.csv文件加载到我的项目中。我研究了向量并了解了最基本的知识。我无法开始理解我应该如何让程序从csv文件读取数据并将其存储在向量中,而无需我手动输入值。是否要求我让程序自动执行?很抱歉,如果我没有道理。它说将其存储到我认为的Student类的向量中?我很迷失。任何建议,将不胜感激。

这是我的代码。我有3个文件。

//main.cpp
#include <iostream>
#include <fstream>
#include <vector>
#include "student.h"
#include <string>
using namespace std;


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

    vector<int> Grades(30);



    ifstream students;
    students.open("students.csv");

    while(students.good()) {
        string line;
        getline(students, line, ',');
        cout << line << endl;   
    }   
}

//students.csv
Sam,Paulson,78,76,80
Mary,Todd,67,78,93
Tim,Paulson,87,67,90
Greg,Todd,76,78,93
Michael,Jones,69,49,90
John,Jones,51,96,76
Mary,Clark,61,51,99
Jessie,Walter,90,99,86
Jacob,Williams,90,95,75
Justin,Steven,87,65,86
//students.h (for student class)
#pragma once
#include <iostream>
using namespace std;

class Student{
    private:
        string firstname;
        string lastname;
        int scores;
        calculateGrade();
    public:
        printInfo();
        getAverageGrade();

};

请帮助。谢谢。

(这些是有关作业的更多详细信息:用学生类开发一个c ++程序。 Student类包含名,姓和分数的私有属性。学生还拥有私有方法calculateGrade,它可以计算给定分数的分数;公共方法printInfo()打印有关学生的信息,而getAverageGrade()返回有关学生平均分数的分数。分数的等级是根据以下所示的评分方案计算的:https://imgur.com/a/Oe35CCR

((1)创建一个students.csv文件,其中包含至少10个以下格式的学生信息

名字,姓氏,分数1,分数2,分数3

  • score1是学生在课程中获得的分数,等等

((2)您的程序应使用CSVReader读取students.csv,然后将读取的信息存储到Student的向量中

(3)您的程序应按照随附程序显示的每个学生的信息进行打印。除了输出的第一行,您的程序应产生与WORD-FOR-WORD完全相同的输出,CHARACTER FOR CHARACTER

c++ csv class vector project
1个回答
0
投票

下面您将找到一个完整而简单的解决方案,而无需使用外部库。

为了进行读写,我们将覆盖学生类的提取器和插入器运算符。 CSV文件的解析将在提取器运算符中完成。 “ printInfo”函数仅调用插入程序运算符。我认为这是不需要的。无论如何。但是,由于有一种以给定格式打印数据的要求,因此您需要根据需要调整插入器运算符功能。

[请学习此代码,并使用Google的所有语言构造以更好地理解。该代码已使用C ++ 17编译(某些功能必需)并经过全面测试。

稍后我将编辑此答案并提供一些解释。


编辑。一些解释

[许多人仍然很难读取CSV文件。即使在此处,也有图书馆,课程和大量答案。但是对于手头上的超简单CSV文件,我们不需要任何外部内容。最终,完整的CSV文件的读取归结为一个简单的单行代码。

[如果我们深入研究问题,那么显然,主要的问题似乎是将带有逗号分隔的带有子字符串或标记的字符串拆分为向量。此过程称为“拆分”或“加标”。由于这是经常需要的标准程序,因此大约10年前在C ++中引入了一种特定的语言构造。它是std::sregex_token_iterator。并且,因为我们有专门为此目的而设计的专用功能,所以我们应该使用它。

不需要其他任何东西。

所以,这是一个迭代器。用于遍历字符串中的标记,因此为“ sregex”。开始/结束部分定义了我们将在什么输入范围上进行操作,然后在输入字符串中有一个std :: regex表示应该匹配的内容或不应该匹配的内容。匹配策略的类型由最后一个参数给出。

  • 1->给我我在正则表达式中定义的内容,然后
  • -1->给我基于正则表达式不匹配的内容。

关于正则表达式,您需要单独阅读。在我们的例子中,正则表达式是“,”。

因此,我们可以定义以下一线:

std::vector token(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {});

这里发生了什么?首先,我们定义一个名称标记为std::vector类型的变量。因此,普通变量或向量定义。您知道类具有构造函数,std::vector也具有。并且我们正在使用其范围构造函数定义令牌向量。请参见here,版本5):“使用范围为[first,last)的内容构造容器。”。因此,它将std::sregex_token_iterator返回的令牌从第一个令牌复制到最后一个令牌。 begin-iterator由“ std :: sregex_token_iterator(line.begin(),line.end(),delimiter,-1)”给出,而end-iterator由默认初始化器“ {}”给出。

Here,您将找到构造函数描述。 1)是结束迭代器:“ 1)默认构造函数。构造序列结束迭代器。”并且我们使用2)作为子匹配为-1的开始迭代器。

另外:我们确实定义了没有模板参数的std::vector。编译器可以从给定的函数参数中推导出自变量。此功能称为CTAD(“类模板参数推导”),是C ++ 17功能。

因此,现在已经使用简单的单线完成了简单的拆分。好。为了将令牌辅助给类数据成员,我们分配了名字和姓氏。简单。然后,我们将数字(以字符串形式)转换为“整数”值。为了将值从一件事转换为另一件事,我们在C ++标准库中有std::transform。因此,我们遍历源区域(令牌)中的字符串并将结果存储在目标中。我们使用std::back_inserter将转换后的数据附加到目标向量。为了进行转换,我们定义了一个简单的lambda函数。这将检查是否有所有数字,如果是,则将其用于标准功能std::stoul

就是一行学生数据。

现在,如何读取和分割所有行。为此,我们使用了已经开始的方法。在“ main”中,我们定义一个std::vector学生并使用其范围构造函数。这次迭代器是std::istream:iterator。这将简单地调用我们的Student类(具有上述所有功能)的extractor-operator(>>),直到读取完整的文件。

这将产生又一个非常优雅的衬里:

std::vector students(std::istream_iterator<Student>(csvFile), {});

此简单语句(变量定义)将读取完整的CSV文件,解析所有以逗号分隔的数据并将结果存储到向量中。

我们真的不需要任何图书馆或其他精美的东西。仅使用10年的C ++语言元素。

对于输出,我们使用类似的方法。被覆盖的插入程序操作员将小心将格式化的Student数据发送到任何ostream。

[请注意:由于我们使用插入器和提取器运算符,因此我们可以读写任何流。功能不会改变。例如,我们可以使用相同的功能写入文件流或std::cout。尝试一下。您可以写:

std::cout << students[2] << "\n";

现在是代码。由于简单,因此此刻无话可说。但是,如果您对细节有疑问,请询问。

#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <regex>
#include <algorithm>
#include <map>
#include <numeric>
#include <cctype>
#include <iomanip>
#include <iterator>

// Student Data
class Student {
private:
    // Name and scores
    std::string firstname{};
    std::string lastname{};
    std::vector<unsigned int> scores{};

    // Conversion function. From Score to grade
    std::string calculateGrade(const unsigned int score) const;
public:
    // Claculate the averade grade
    std::string getAverageGrade() const;
    // Print info to std::cout
    void printInfo() const { std::cout << *this;  }

    // Overwrite extrator and inserter operator for simple input and output
    friend std::istream& operator >> (std::istream& is, Student& s);
    friend std::ostream& operator << (std::ostream& os, const Student& s);  
};


// We use a map, to map scores to grades. This will make the conversion very simple
std::map<const unsigned int, std::string> scoreToGrade{
    {70,"D-"},{71,"D-"},{72,"D "},{73,"D "},{74,"D+"},{75,"D+"},{76,"C-"},{77,"C-"},{78,"C-"},{79,"C "},
    {80,"C "},{81,"C "},{82,"C+"},{83,"C+"},{84,"C+"},{85,"B-"},{86,"B-"},{87,"B-"},{88,"B "},{89,"B "},
    {90,"B "},{91,"B+"},{92,"B+"},{93,"B+"},{94,"A-"},{95,"A-"},{96,"A "},{97,"A "},{98,"A "},{99,"A+"}, {100,"A+"} };


// Calculate a grade from a score
std::string Student::calculateGrade(const unsigned int score) const {

    // Default assumed score is always F
    std::string grade{ "F " };

    // Score cannot be greater than 100. In such case we have an error in the input data
    if (score > 100) {
        std::cerr << "\n*** Error: Invalid score found  (" << score << ")\n\n";
    }
    else {
        // Lookup the score in the map and return the associated grade
        if (score > 69) grade = scoreToGrade[score];
    }
    return grade;
}


// Calculate the average grade
std::string Student::getAverageGrade() const {

    // Defualt grade is an empty string
    std::string grade{ "  " };

    // Prevent devision by 0. We must have at least one score in oreder to calculate an average value.
    if (scores.size() > 0) {
        // Calculate sum and devide by number of entries
        grade = calculateGrade(static_cast<unsigned int>(std::round(std::accumulate(scores.begin(), scores.end(), 0.0) / scores.size())));
    }
    return grade;
}

// Read Student in CSV format from stream
std::istream& operator >> (std::istream& is, Student& s) {

    // Definition of some helpers ----------------------------------------------
    // Lambda to remove leading and trailing spaces
    auto trim = [](const std::string& s) -> std::string { return std::regex_replace(s, std::regex("^ +| +$"), "$1"); };

    // Lambda to convert string to unsigned int
    auto convertToUInt = [&trim](const std::string& c) -> unsigned int { std::string s{ trim(c) }; unsigned int result{ 0U };
        // Make sure that we have all digits 
        if (std::all_of(c.begin(), c.end(), isdigit)) result = static_cast<unsigned int>(std::stoul(s)); 
        // Limit values to meaningful boundaries
        return std::clamp(result, 70U, 100U); };

    // Delimiter for our CSV file
    const std::regex delimiter{ "," };

    // CSV Data conversion ----------------------------------------------
    // Read a complete line from the input stream and check, if that worked
    if (std::string line{}; std::getline(is, line)) {

        // Split the string into parts (tokens). This is the CSV Parser
        std::vector token(std::sregex_token_iterator(line.begin(), line.end(), delimiter, -1), {});

        // Check, if at least 2 parts, so, first name and last name is available
        if (token.size() > 1) {

            // Assign data
            s.firstname = trim(token[0]);
            s.lastname = trim(token[1]);
            s.scores.clear();
            // Transform the score values to integers and store them
            std::transform(token.begin() + 2, token.end(), std::back_inserter(s.scores), convertToUInt);
        }
    }
    return is;
}

// Inserter operator: Write Student data to any ostream
std::ostream& operator << (std::ostream& os, const Student& s) {

    // ******* This is your turn. Format the data in your required way. *******
    os << "First Name: " << std::left << std::setw(10) << s.firstname << "  Last Name: " << std::setw(10) << s.lastname << "   Scores and grades: ";
    for (unsigned int score : s.scores)
        os << std::setw(3) << std::right << score << std::left << " (" << s.calculateGrade(score) << ")  ";
    return os << " Average Grade: " << s.getAverageGrade();
}



int main() {

    // Open source file and check, if it could be opened
    if (std::ifstream csvFile("r:\\student.csv"); csvFile) {

        // Ok, file is open. Read all students from csv file (amd parse the csv data) into a vector
        std::vector students(std::istream_iterator<Student>(csvFile), {});

        // Print header
        std::cout << "\nStudent list with data:\n\n";

        // Print all student information
        std::copy(students.begin(), students.end(), std::ostream_iterator<Student>(std::cout, "\n"));
    }
    else {
        std::cerr << "\n*** Error: Could not open CSV file\n\n";
    }

    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.