如何在 C++ 中将 CSV 数据读取到结构向量的指针?

How to read CSV data to pointers of struct vector in C++?

我想将 csv 数据读取到 cpp 中的结构向量,这就是我写的,我想将 iris 数据集存储在结构向量 csv 的指针中 std::vector<Csv> *csv = new std::vector<Csv>;

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

struct Csv{
    float a;
    float b;
    float c;
    float d;
    std::string e;
};

int main(){
    std::string colname;
    
    // Iris csv dataset downloaded from
    // https://gist.github.com/curran/a08a1080b88344b0c8a7
    std::ifstream *myFile = new std::ifstream("iris.csv");
    

    std::vector<Csv> *csv = new std::vector<Csv>;
    
    std::string line;
    
    // Read the column names
    if(myFile->good())
    {
        // Extract the first line in the file
        std::getline(*myFile, line);

        // Create a stringstream from line
        std::stringstream ss(line);

        // Extract each column name
        while(std::getline(ss, colname, ',')){
            
            std::cout<<colname<<std::endl;
            }
    }
    

   // Read data, line by line
    while(std::getline(*myFile, line))
    {
        // Create a stringstream of the current line
        std::stringstream ss(line);

        
    }
        
    return 0;
}

我不知道如何实现这部分输出同时包含浮点数和字符串的代码。

   // Read data, line by line
    while(std::getline(*myFile, line))
    {
        // Create a stringstream of the current line
        std::stringstream ss(line);

        
    }

进化

我们从您的程序开始,并以您当前的程序风格完成它。然后我们分析您的代码并将其重构为更符合 C++ 风格的解决方案。最后,我们展示了使用更多 OO 方法的现代 C++ 解决方案。

首先是您完成的代码:

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

struct Csv {
    float a;
    float b;
    float c;
    float d;
    std::string e;
};

int main() {
    std::string colname;

    // Iris csv dataset downloaded from
    // https://gist.github.com/curran/a08a1080b88344b0c8a7
    std::ifstream* myFile = new std::ifstream("r:\iris.csv");


    std::vector<Csv>* csv = new std::vector<Csv>;

    std::string line;

    // Read the column names
    if (myFile->good())
    {
        // Extract the first line in the file
        std::getline(*myFile, line);

        // Create a stringstream from line
        std::stringstream ss(line);

        // Extract each column name
        while (std::getline(ss, colname, ',')) {

            std::cout << colname << std::endl;
        }
    }


    // Read data, line by line
    while (std::getline(*myFile, line))
    {
        // Create a stringstream of the current line
        std::stringstream ss(line);
        // Extract each column 
        std::string column;
        std::vector<std::string> columns{};

        while (std::getline(ss, column, ',')) {
            columns.push_back(column);
        }
        // Convert
        Csv csvTemp{};
        csvTemp.a = std::stod(columns[0]);
        csvTemp.b = std::stod(columns[1]);
        csvTemp.c = std::stod(columns[2]);
        csvTemp.d = std::stod(columns[3]);
        csvTemp.e = columns[4];
        // STore new row data
        csv->push_back(csvTemp);
    }
    // Show everything
    for (const Csv& row : *csv)
        std::cout << row.a << '\t' << row.b << '\t' << row.c << '\t' << row.d << '\t' << row.e << '\n';


    return 0;
}

关于从 Csv 文件中读取列的问题,可以这样回答:

您需要一个临时向量。然后使用 std::getline 函数拆分 std::istringstream 中的数据并将生成的子字符串复制到向量中。之后,我们使用字符串转换函数并将结果分配到临时 Csv 结构变量中。完成所有转换后,我们将临时文件移动到包含所有行数据的结果 csv 向量中。


程序分析。

首先,也是最重要的一点,在 C++ 中,我们不对自有内存使用原始指针。大多数情况下我们不应该使用 new。如果有的话,应该使用 std::unique_ptrstd::make_unique

但是我们根本不需要在堆上动态分配内存。您可以简单地在函数堆栈上定义 std::vector 。与您的 std::string colname; 行一样,您也可以将 std::vectorstd::ifstream 定义为普通局部变量。例如 std::vector<Csv> csv{};。只是,如果你将这个变量传递给另一个函数,那么使用指针,但是智能指针。

接下来,如果您打开一个文件,就像在 std::ifstream myFile("r:\iris.csv"); 中一样,您不需要使用 if (myFile->good()) 测试文件流条件。 std::fstreams 布尔运算符被覆盖,为您提供准确的信息。请参阅 here

现在,接下来也是最重要的。

你的源文件的结构是众所周知的。有一个包含 5 个元素的 header,然后是 4 个双精度元素,最后是一个没有空格的字符串。这让生活变得非常轻松。

如果我们需要验证输入或者字符串中是否有空格,那么我们需要实现其他方法。但是有了这个结构,我们就可以使用 iostream 中的构建工具。片段

        // Read all data
        Csv tmp{};
        char comma;
        while (myFile >> tmp.a >> comma >> tmp.b >> comma >> tmp.c >> comma >> tmp.d >> comma >> tmp.e)
            csv.push_back(std::move(tmp));

会成功的。很简单。

因此,重构后的解决方案可能如下所示:

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

struct Csv {
    float a;
    float b;
    float c;
    float d;
    std::string e;
};

int main() {

    std::vector<Csv> csv{};
    std::ifstream myFile("r:\iris.csv");
    if (myFile) {
        
        if (std::string header{}; std::getline(myFile, header)) std::cout << header << '\n';

        // Read all data
        Csv tmp{};
        char comma;
        while (myFile >> tmp.a >> comma >> tmp.b >> comma >> tmp.c >> comma >> tmp.d >> comma >> tmp.e)
            csv.push_back(std::move(tmp));

        // Show everything
        for (const Csv& row : csv)
            std::cout << row.a << '\t' << row.b << '\t' << row.c << '\t' << row.d << '\t' << row.e << '\n';
    }
    return 0;
}

这已经紧凑多了。但还有更多。 . .


在下一步中,我们要添加一个更 Object 导向的方法。

关键是数据和操作此数据的方法应封装在 Object / class / 结构中。只有 Csv 结构应该知道如何读写它的数据。

因此,我们覆盖了 Csv 结构的提取器和插入器运算符。我们使用与以前相同的方法。我们只是将读写封装在struct Csv中。

之后main函数会更加紧凑,使用起来也更加符合逻辑

现在我们有:

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

struct Csv {
    float a;
    float b;
    float c;
    float d;
    std::string e;

    friend std::istream& operator >> (std::istream& is, Csv& c) {
        char comma;
        return is >> c.a >> comma >> c.b >> comma >> c.c >> comma >> c.d >> comma >> c.e;
    }

    friend std::ostream& operator << (std::ostream& os, const Csv& c) {
        return os << c.a << '\t' << c.b << '\t' << c.c << '\t' << c.d << '\t' << c.e << '\n';
    }
};

int main() {

    std::vector<Csv> csv{};
    if (std::ifstream myFileStream("r:\iris.csv"); myFileStream) {

        if (std::string header{}; std::getline(myFileStream, header)) std::cout << header << '\n';

        // Read all data
        Csv tmp{};
        while (myFileStream >> tmp)
            csv.push_back(std::move(tmp));

        // Show everything
        for (const Csv& row : csv)
            std::cout << row;
    }
    return 0;
}

好的。已经很不错了。比特还有更多的可能。


我们可以看到源数据有一个header然后是Csv数据

也可以将其建模为结构。我们称之为鸢尾花。并且我们还添加了一个提取器和插入器覆盖来封装所有 IO-operations.

此外,我们现在还使用现代算法、正则表达式和 IO-iterators。我不确定,如果这现在太复杂了。如果您有兴趣,那么我可以为您提供更多信息。但现在,我只向您展示代码。

#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <regex>
#include <iterator>

const std::regex re{ "," };

struct Csv {
    float a;
    float b;
    float c;
    float d;
    std::string e;
    // Overwrite extratcor for simple reading of data
    friend std::istream& operator >> (std::istream& is, Csv& c) {
        char comma;
        return is >> c.a >> comma >> c.b >> comma >> c.c >> comma >> c.d >> comma >> c.e;
    }
    // Ultra simple inserter
    friend std::ostream& operator << (std::ostream& os, const Csv& c) {
        return os << c.a << "\t\t" << c.b << "\t\t" << c.c << "\t\t" << c.d << "\t\t" << c.e << '\n';
    }
};

struct Iris {
    // Iris data consits of header and then Csv Data
    std::vector<std::string> header{};
    std::vector<Csv> csv{};

    // Overwrite extractor for generic reading from streams
    friend std::istream& operator >> (std::istream& is, Iris& i) {
        // First read header values;
        if (std::string line{}; std::getline(is, line)) 
            std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {}, std::back_inserter(i.header));
        
        // Read all csv data
        std::copy(std::istream_iterator<Csv>(is), {}, std::back_inserter(i.csv));
        return is;
    }

    // Simple output. Copy data to stream os
    friend std::ostream& operator << (std::ostream& os, const Iris& i) {
        std::copy(i.header.begin(), i.header.end(), std::ostream_iterator<std::string>(os, "\t")); std::cout << '\n';
        std::copy(i.csv.begin(), i.csv.end(), std::ostream_iterator<Csv>(os));
        return os;
    }
};


// Driver Code
int main() {

    if (std::ifstream myFileStream("r:\iris.csv"); myFileStream) {

        Iris iris{};

        // Read all data
        myFileStream >> iris;

        // SHow result 
        std::cout << iris;
    }
    return 0;
}

看看主要功能,它是多么简单。

如有疑问,请提问。


语言:C++17

使用 MS Visual Studio 2019 社区版编译和测试