c++ - 解析CSV文件-C++

标签 c++ csv parsing

C++ 14

通常,大学的工作人员建议我们使用Boost来分析文件,但是我已经安装了它,但没有成功实现它。

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

第一个问题是:文件看起来像这样:

1,1
<HERE WE HAVE A NEWLINE>
<HERE WE HAVE A NEWLINE>

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

第二个问题是:文件看起来像:
1,1
<HERE WE HAVE A NEWLINE>
1,1

这应该是无效的输入,但是我的解决方案将其视为正确的输入-只是跳过了中间的NEWLINE。

也许您可以指导我如何解决这些问题,这对我真的很有帮助,因为我每天早晨到晚上都在为期3天的练习中挣扎。

这是我当前的解析器:
#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;
}

最佳答案

您不需要任何特殊的语法分析.csv文件,C++ 11中的STL容器提供了解析几乎所有.csv文件所需的所有工具。尽管您将需要知道从.csv中读取的值的类型以便应用正确的值转换,但是您无需事先知道要解析的每行的值数。您也不需要任何类似Boost的第三方库。

有很多方法可以存储从.csv文件解析的值。基本的“处理任何类型”方法是将值存储在std::vector<std::vector<type>>中(它实质上提供了包含从各行中解析出的值的 vector 的 vector )。您可以根据所要读取的类型以及如何转换和存储值来根据需要对存储进行特化处理。您的基本存储可以是struct/classstd::pairstd::set,也可以是诸如int的基本类型。一切适合您的数据。

在您的情况下,您的文件中包含基本的int值。基本.csv解析的唯一警告是事实,您可能在值的两行之间有空白行。任何数量的测试都可以轻松解决这一问题。例如,您可以检查读取的行的.length()是否为零,或者具有更大的灵活性(在处理包含多个空格或其他非值字符的行时),可以使用.find_first_of()在行中查找第一个想要的值确定它是否是要分析的行。

例如,在您的情况下,您对值行的读取循环可以简单地读取每行并检查该行是否包含digit。它可以很简单:

    ...
    std::string line;       /* string to hold each line read from file  */
    std::vector<std::vector<int>> values {};    /* vector vector of int */
    std::ifstream f (argv[1]);                  /* file stream to read  */

    while (getline (f, line)) { /* read each line into line */
        /* if no digits in line - get next */
        if (line.find_first_of("0123456789") == std::string::npos)
            continue;
        ...
    }

在上面,每行都被读入line,然后检查line是否包含数字。如果是这样,解析它。如果不是,请转到下一行,然后重试。

如果它是包含值的行,则可以从该行创建一个std::stringstream,并从字符串流中将整数值读取为一个临时int值,然后将该值添加到int的一个临时 vector 中,并使用getline和分隔符','来使用逗号当用尽了要从该行读取的值时,请将int的临时 vector 添加到最终存储中。 (重复直到读取所有行)。

您完整的读取循环可能是:
    while (getline (f, line)) { /* read each line into line */
        /* if no digits in line - get next */
        if (line.find_first_of("0123456789") == std::string::npos)
            continue;
        int itmp;                               /* temporary int */
        std::vector<int> tmp;                   /* temporary vector<int> */
        std::stringstream ss (line);            /* stringstream from line */
        while (ss >> itmp) {                    /* read int from stringstream */
            std::string tmpstr;                 /* temporary string to ',' */
            tmp.push_back(itmp);                /* add int to tmp */
            if (!getline (ss, tmpstr, ','))     /* read to ',' w/tmpstr */
                break;                          /* done if no more ',' */
        } 
        values.push_back (tmp);     /* add tmp vector to values */
    }

每行读取的值数量或每文件读取的值行数量没有限制(最高为虚拟内存的存储限制)

将上述内容放在一起作为一个简短的示例,您可以执行与以下操作类似的操作,即读取输入文件,然后在完成后输出收集的整数:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>

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

    if (argc < 2) { /* validate at least 1 argument given for filename */
        std::cerr << "error: insufficient input.\nusage: ./prog <filename>\n";
        return 1;
    }

    std::string line;       /* string to hold each line read from file  */
    std::vector<std::vector<int>> values {};    /* vector vector of int */
    std::ifstream f (argv[1]);                  /* file stream to read  */

    while (getline (f, line)) { /* read each line into line */
        /* if no digits in line - get next */
        if (line.find_first_of("0123456789") == std::string::npos)
            continue;
        int itmp;                               /* temporary int */
        std::vector<int> tmp;                   /* temporary vector<int> */
        std::stringstream ss (line);            /* stringstream from line */
        while (ss >> itmp) {                    /* read int from stringstream */
            std::string tmpstr;                 /* temporary string to ',' */
            tmp.push_back(itmp);                /* add int to tmp */
            if (!getline (ss, tmpstr, ','))     /* read to ',' w/tmpstr */
                break;                          /* done if no more ',' */
        } 
        values.push_back (tmp);     /* add tmp vector to values */
    }

    for (auto row : values) {       /* output collected values */
        for (auto col : row)
            std::cout << "  " << col;
        std::cout << '\n';
    }
}

示例输入文件

使用一个输入文件,其中包含空白行,每行包含两个整数,其中包含您在问题中描述的值的行:
$ cat dat/csvspaces.csv
1,1


2,2
3,3

4,4



5,5
6,6

7,7

8,8


9,9

示例使用/输出

结果解析:
$ ./bin/parsecsv dat/csvspaces.csv
  1  1
  2  2
  3  3
  4  4
  5  5
  6  6
  7  7
  8  8
  9  9

示例输入未知/不均匀的列数

您不需要知道.csv中每行的值数或文件中的值的行数。 STL容器自动处理内存分配需求,使您可以解析所需的内容。现在,您可能需要对每行或每个文件强制执行一些固定数量的值,但这完全取决于您是否需要添加简单的计数器并检查读取/解析例程以限制按需存储的值。

无需对上面的代码做任何更改,它将处理每行任意数量的逗号分隔值。例如,将数据文件更改为:
$ cat dat/csvspaces2.csv
1


2,2
3,3,3

4,4,4,4



5,5,5,5,5
6,6,6,6,6,6

7,7,7,7,7,7,7

8,8,8,8,8,8,8,8


9,9,9,9,9,9,9,9,9

示例使用/输出

得出每行中每个值的预期解析结果,例如:
$ ./bin/parsecsv dat/csvspaces2.csv
  1
  2  2
  3  3  3
  4  4  4  4
  5  5  5  5  5
  6  6  6  6  6  6
  7  7  7  7  7  7  7
  8  8  8  8  8  8  8  8
  9  9  9  9  9  9  9  9  9

如果您有我未涵盖的问题,或者您对我所做的事情还有其他疑问,我很乐意为您提供进一步的帮助。

关于c++ - 解析CSV文件-C++,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59595581/

相关文章:

scala - 如何在 Spark Scala 中使用 Graph.fromEdgeTuples 从 CSV 文件创建图形

python - 如何将/proc/net/dev 的输出解析为 key :value pairs per interface using Python?

java - 将字符串解析为时间使得 01 :00:00

c++ - 在编译时确定最小共同祖先

c++ - 将 C++ 输出写入 xlsx 文件

python - 通过使用 python 和 pandas 使用 2 个现有列的函数填充新列

java - 如何将字符串中的逗号添加到 csv 中的单个单元格

c++ - 非模板类中的模板化 friend 类,其中 friend 也使用该类

c++ - 何时假定别名

php - 维基百科上的 "edit section"功能是如何工作的?