解析 CSV 文件 - C++

CCP*_*Sup 5 c++ csv parsing

C++14

一般来说,大学的工作人员建议我们使用Boost来解析文件,但我已经安装了它,但没有成功地用它实现任何东西。

所以我必须逐行解析 CSV 文件,其中每行有 2 列,当然用逗号分隔。这两列中的每一列都是一个数字。我必须取这两个数字的整数值并使用它们最后构造我的分形对象。

第一个问题是:该文件可能如下所示:

1,1
<HERE WE HAVE A NEWLINE>
<HERE WE HAVE A NEWLINE>
Run Code Online (Sandbox Code Playgroud)

这种格式的文件没问题。但我的解决方案输出“无效输入”,其中正确的解决方案应该只打印一次相应的分形 - 1,1。

第二个问题是:该文件可能如下所示:

1,1
<HERE WE HAVE A NEWLINE>
1,1
Run Code Online (Sandbox Code Playgroud)

这应该是一个无效的输入,但我的解决方案将其视为正确的输入 - 并且只是跳过中间的换行符。

也许你可以指导我如何解决这些问题,这对我来说真的很有帮助,因为我从早到晚都在努力进行这项练习。

这是我当前的解析器:

#include <iostream>
#include "Fractal.h"
#include <fstream>
#include <stack>
#include <sstream>
const char *usgErr = "Usage: FractalDrawer <file path>\n";
const char *invalidErr = "Invalid input\n";
const char *VALIDEXT = "csv";
const char EXTDOT = '.';
const char COMMA = ',';
const char MINTYPE = 1;
const char MAXTYPE = 3;
const int MINDIM = 1;
const int MAXDIM = 6;
const int NUBEROFARGS = 2;
int main(int argc, char *argv[])
{
    if (argc != NUBEROFARGS)
    {
        std::cerr << usgErr;
        std::exit(EXIT_FAILURE);
    }
    std::stack<Fractal *> resToPrint;
    std::string filepath = argv[1]; // Can be a relative/absolute path
    if (filepath.substr(filepath.find_last_of(EXTDOT) + 1) != VALIDEXT)
    {
        std::cerr << invalidErr;
        exit(EXIT_FAILURE);
    }
    std::stringstream ss; // Treat it as a buffer to parse each line
    std::string s; // Use it with 'ss' to convert char digit to int
    std::ifstream myFile; // Declare on a pointer to file
    myFile.open(filepath); // Open CSV file
    if (!myFile) // If failed to open the file
    {
        std::cerr << invalidErr;
        exit(EXIT_FAILURE);
    }
    int type = 0;
    int dim = 0;
    while (myFile.peek() != EOF)
    {
        getline(myFile, s, COMMA); // Read to comma - the kind of fractal, store it in s
        ss << s << WHITESPACE; // Save the number in ss delimited by ' ' to be able to perform the double assignment
        s.clear(); // We don't want to save this number in s anymore as we won't it to be assigned somewhere else
        getline(myFile, s, NEWLINE); // Read to NEWLINE - the dim of the fractal
        ss << s;
        ss >> type >> dim; // Double assignment
        s.clear(); // We don't want to save this number in s anymore as we won't it to be assigned somewhere else

        if (ss.peek() != EOF || type < MINTYPE || type > MAXTYPE || dim < MINDIM || dim > MAXDIM) 
        {
            std::cerr << invalidErr;
            std::exit(EXIT_FAILURE);
        }

        resToPrint.push(FractalFactory::factoryMethod(type, dim));
        ss.clear(); // Clear the buffer to update new values of the next line at the next iteration
    }

    while (!resToPrint.empty())
    {
        std::cout << *(resToPrint.top()) << std::endl;
        resToPrint.pop();
    }

    myFile.close();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

Arm*_*gny 0

我不会更新你的代码。我看了你的标题Parsing a CSV file - C++,想向你展示如何以更现代的方式读取 csv 文件。不幸的是你仍然使用 C++14。getlines使用 C++20 或范围库,使用和将会非常简单split

在 C++17 中,我们可以使用 CTAD 和if初始化程序等等。

但我们不需要的是 boost。C++ 的标准库就足够了。我们从不使用scanf这样的旧东西。

我的拙见,链接到 10 年前的问题How can I read and parse CSV files in C++? 不应再给予。现在已经是2020年了。应该使用更现代和现在可用的语言元素。但正如所说。每个人都可以自由地做他想做的事。

在C++中我们可以使用std::sregex_token_iterator. 而且它的使用方法非常简单。它也不会显着减慢你的程序速度。双倍std::getline也可以。虽然不是那么灵活。为此必须知道列数。这std::sregex_token_iterator关心列数。

请参阅以下示例代码。在此,我们创建一个代理类并覆盖其提取器运算符。然后我们用std::istream_iterator一小行代码读取并解析整个 csv 文件。

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

// Define Alias for easier Reading
// using Columns = std::vector<std::string>;
using Columns = std::vector<int>;

// The delimiter
const std::regex re(",");

// Proxy for the input Iterator
struct ColumnProxy {
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, ColumnProxy& cp) {
        // Read a line
        std::string line;
        cp.columns.clear();
        if(std::getline(is, line) && !line.empty()) {
            // Split values and copy into resulting vector
            std::transform(
                std::sregex_token_iterator(line.begin(), line.end(), re, -1), {},
                std::back_inserter(cp.columns),
                [](const std::string& s) { return std::stoi(s); });
        }
        return is;
    }
    // Type cast operator overload.  Cast the type 'Columns' to
    // std::vector<std::string>
    operator Columns() const { return columns; }

protected:
    // Temporary to hold the read vector
    Columns columns{};
};

int main() {
    std::ifstream myFile("r:\\log.txt");
    if(myFile) {
        // Read the complete file and parse verything and store result into vector
        std::vector<Columns> values(std::istream_iterator<ColumnProxy>(myFile), {});

        // Show complete csv data
        std::for_each(values.begin(), values.end(), [](const Columns& c) {
            std::copy(c.begin(), c.end(),
                      std::ostream_iterator<int>(std::cout, " "));
            std::cout << "\n";
        });
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

请注意:还有大量其他可能的解决方案。请随意使用您想要的任何东西。


编辑

因为我在这里看到很多复杂的代码,所以我想展示第二个示例来说明如何

解析 CSV 文件 - C++

基本上,代码中不需要超过 2 条语句。您首先定义数字的正则表达式。然后,您使用 C++ 语言元素,该元素专为将字符串标记为子字符串而设计。这std::sregex_token_iterator。而且由于多年来 C++ 中已经提供了这样一个最合适的语言元素,因此可能值得考虑使用它。也许您基本上可以用 2 行而不是 10 行或更多行来完成该任务。而且很容易理解。

当然,有数千种可能的解决方案,有些喜欢继续 C 风格,而另一些则喜欢更现代的 C++ 功能。这取决于每个人个人的决定。

下面的代码按照指定读取 csv 文件,无论它包含多少行(行)以及每行有多少列。即使是外国角色也可以在其中。空行将是 csv 向量中的空条目。这也可以很容易地避免,在 emplace 返回之前使用“if !empty”。

但有些人喜欢这样,另一些人也喜欢这样。人们想要什么就什么。

请看一个一般示例:

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

// Test data. Can of course also be taken from a file stream.
std::stringstream testFile{ R"(1,2
3, a, 4 
5 , 6  b ,  7

abc def
8 , 9
11 12 13 14 15 16 17)" };

std::regex digits{R"((\d+))"};

using Row = std::vector<std::string>;

int main() {
    // Here we will store all the data from the CSV as std::vector<std::vector<std::string>>
    std::vector<Row> csv{};


    // This extremely simple 2 lines will read the complete CSV and parse the data
    for (std::string line{}; std::getline(testFile, line);  ) 
        csv.emplace_back(Row(std::sregex_token_iterator(line.begin(), line.end(), digits, 1), {}));


    // Now, you can do with the data, whatever you want. For example: Print double the value
    std::for_each(csv.begin(), csv.end(), [](const Row& r) { 
        if (!r.empty()) {
            std::transform(r.begin(), r.end(), std::ostream_iterator<int>(std::cout, " "), [](const std::string& s) {
            return std::stoi(s) * 2; }
        ); std::cout << "\n";}});

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

所以,现在,你可能明白了,你可能喜欢它,或者你不喜欢它。任何。随意做任何你想做的事。