(C++) 将 CSV 文本文件读取为整数向量

(C++) Reading a CSV text file as a vector of integers

我是一名初学者,正在应对 2019 年 C++ 代码问世挑战。

我拼凑的最后一块拼图实际上是让程序读取 input.txt 文件,它本质上是一长串值,形式为 '10,20,40, 23" 等在一条线上。

在之前的拼图中我使用了线条

int inputvalue;
std::ifstream file("input.txt");

while(file >> inputvalue){
    //
}

从文件中抓取行,但它被格式化为连续行的文本文件,没有逗号分隔。

即:

10
20
40
23

如何使用逗号分隔读取文件,特别是如何将这些值读取为整数,而不是字符串或字符,并将它们存储到向量中?

你有选择。在我看来,最straight-forward就是只读取一个字符串,然后转为整数。可以使用std::getline的附加“定界符”参数,遇到逗号就停止:

std::string value;
while (std::getline(file, value, ',')) {
    int ival = std::stoi(value);
    std::cout << ival << std::endl;
}

一个常见的替代方法是读取单个字符,期望它是一个逗号:

int ival;
while (file >> ival) {
    std::cout << ival << std::endl;

    // Skip comma (we hope)
    char we_sure_hope_this_is_a_comma;
    file >> we_sure_hope_this_is_a_comma;
}

如果空格也可能存在,您可能需要一种不太“有希望”的技术来跳过逗号:

    // Skip characters up to (and including) next comma
    for (char c; file >> c && c != ',';);

或者简单地说:

    // Skip characters up to (and including) next comma
    while (file && file.get() != ',');

或者实际上,如果您希望 只有 空格 一个逗号,您可以这样做:

    // Skip comma and any leading whitespace
    (file >> std::ws).get();

当然,以上都是more-or-less笨拙的做法:

    // Skip characters up to (and including) next comma on next read
    file.ignore(std::numeric_limits<std::streamsize>::max(), ',');

所有这些方法都假设输入是一行。如果您希望多行输入具有 comma-separated 值,您还需要处理 end-of-line 出现 而不会 遇到逗号。否则,您可能会错过下一行的第一个输入。除了“充满希望”的方法,它会起作用,但仅在技术上有效。

为了稳健性,我通常建议您使用 std::getline 将 line-based 输入作为整个字符串读取,然后使用 std::istringstream 从该行中读取单个值。

虽然编写一个只从逗号分隔的文件中读取一行的例程,而不是编写一个读取所有行的通用例程(如果您只想要一行,则只取第一行)会很奇怪 --您可以取出用于将多行读入 std::vector<std::vector<int>> 的部分,而只将一行读入 std::vector<int> —— 尽管它只节省了少量代码行。

一般方法是使用 getline(file, line) 读取整行文本,然后创建一个 std::stringstream (line),然后您可以使用 >> 读取每个整数后跟一个getline (file, tmpstr, ',') 读取分隔符。

除了要读取的文件之外,您还可以使用第二个参数,这样您就可以将定界符作为第二个参数的第一个字符传递——这样就没有理由 re-compile 您的代码处理 ';'',' 或任何其他单个字符的分隔符。

您可以将一小段代码放在一起来执行以下操作:

#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 */
        std::cerr << "error: insufficient number of arguments.\n"
                    "usage: " << argv[0] << " <filename>\n";
        return 1;
    }
    
    std::vector<int> v {};              /* vector<int> */
    std::string line {};                /* string to hold each line */
    std::ifstream f (argv[1]);          /* open file-stream with 1st argument */
    const char delim = argc > 2 ? *argv[2] : ',';   /* delim is 1st char in  */
                                                    /* 2nd arg (default ',') */
    
    if (!f.good()) {    /* validate file open for reading */
        std::cerr << "errro: file open failed '" << argv[1] << "'.\n";
        return 1;
    }
    
    if (getline (f, line)) {            /* read line of input into line */
        int itmp;                       /* temporary integer to fill */
        std::stringstream ss (line);    /* create stringstream from line */
        
        while (ss >> itmp) {            /* read integer value from ss */
            std::string stmp {};        /* temporary string to hold delim */
            v.push_back(itmp);          /* add to vector */
            getline (ss, stmp, delim);  /* read delimiter */
        }
    }
    
    for (auto col : v)                  /* loop over each integer */
        std::cout << "  " << col;       /* output col value */
    std::cout << '\n';                  /* tidy up with newline */
}

(注意: 将所有行读入向量的向量所需的更改相对较少,更值得注意的是简单地将 if(getline...) 替换为 while(getline..) 然后填充一个临时向量,如果 non-empty,然后将其推回到您的向量集合中)

示例输入文件

在名为 dat/int-1-10-1-line.txt 的文件中使用一组逗号分隔的整数,例如

$ cat dat/int-1-10-1-line.txt
1,2,3,4,5,6,7,8,9,10

例子Use/Output

您的使用结果将是:

$ ./bin/read_csv_int-1-line dat/int-1-10-1-line.txt
  1  2  3  4  5  6  7  8  9  10

当然,您可以将输出格式更改为您需要的任何格式。检查一下,如果您还有其他问题,请告诉我。

这是另一个使用迭代器的紧凑解决方案。

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


template <char D>
struct WordDelimiter : public std::string
{};

template <char D>
std::istream &
operator>>(std::istream & is, WordDelimiter<D> & output)
{
  // Output gets every comma-separated token
  std::getline(is, output, D);
  return is;
}

int main() {
  // Open a test file with comma-separated tokens
  std::ifstream f{"test.txt"};
  
  // every token is appended in the vector
  std::vector<std::string> vec{ std::istream_iterator<WordDelimiter<','>>{ f },
                                std::istream_iterator<WordDelimiter<','>>{} };

  // Transform str vector to int vector
  // WARNING: no error checking made here
  std::vector<int> vecint;
  std::transform(std::begin(vec),std::end(vec),std::back_inserter(vecint),[](const auto& s) { return std::stoi(s); });

  for (auto val : vecint) {
    std::cout << val << std::endl;
  }

  return 0;
}