首页 > 解决方案 > 如何将 .csv 文件的元素存储到 C++ 中的二维向量中?

问题描述

我正在尝试将 .csv 文件的内容存储到 2D 矢量中。

我能够将 .csv 文件存储到一个单维向量中,我在我的类中使用它来计算行和列的大小。以下是我在该行出现错误的当前代码:

getline(ss,column.at(i),',');

如果我在使用 ___.at() 时使用括号索引并且在内存位置超出范围,则错误表示向量下标超出范围。

我的读取函数运行,我能够获得正确的行和列大小,并将文件的内容存储到向量中。

class database {
public:
int row = 0;
int col = 0;
vector <vector<string>> dataset;
void read(string filename) {
    vector<string> data;
    ifstream file{ filename };
    string line;
    while (file)
    {
        getline(file, line);
        string newline;
        newline = line + "\n";
        data.push_back(newline);
    }
    col = count(data.at(0).begin(), data.at(0).end(), ',') + 1;
    row = data.size() - 1;
}
void write(string filename){
    ifstream file{ filename };
    string line;
    while (getline(file, line))
    {
        stringstream ss(line);
        vector<string> column;
        for (int i = 0; i < col; i++)
        {
            getline(ss, column.at(i), ',');
        }
        cout << column.size();
        dataset.push_back(column);
    }       
}
};

我正在尝试使用 cout << column.size() 来查看它是否与我的数据向量的大小匹配,这是包含 .csv 文件的所有内容的一维向量。

标签: c++csvmultidimensional-arrayvector

解决方案


我会使用“更现代”的 C++ 方法。

仍然所有人都在链接到 How can I read and parse CSV files in C++?,问题是从 2009 年开始,现在已经超过 10 年了。大多数答案也很旧且非常复杂。所以,也许是时候改变了。

在现代 C++ 中,您有迭代范围的算法。你会经常看到类似“someAlgoritm(container.begin(), container.end(), someLambda)”的东西。这个想法是我们迭代一些相似的元素。

在您的情况下,我们遍历输入字符串中的标记,并创建子字符串。这称为标记化。

正是为了这个目的,我们拥有std::sregex_token_iterator. 而且因为我们有为此目的而定义的东西,我们应该使用它。

这东西是一个迭代器。用于迭代字符串,因此是正则表达式。开始部分定义了我们将操作的输入范围,然后std::regex在输入字符串中有一个应该匹配/不应该匹配的内容。匹配策略的类型由最后一个参数给出。

  • 1 --> 给我我在正则表达式中定义的东西和
  • -1 --> 告诉我根据正则表达式不匹配的内容。

所以,既然我们了解了迭代器,我们就可以 std::copy 将令牌从迭代器复制到我们的目标 a std::vectorof std::string。而且由于我们不知道我们有多少列,我们将使用std::back_inserter作为目标。这将添加我们从 获得的所有令牌std::sregex_token_iterator并将其附加到我们的std::vector<std::string>>. 我们有多少列并不重要。

好的。这样的声明可能看起来像

std::copy(                          // We want to copy something
    std::sregex_token_iterator      // The iterator begin, the sregex_token_iterator. Give back first token
    (
        line.begin(),               // Evaluate the input string from the beginning
        line.end(),                 // to the end
        re,                         // Add match a comma
        -1                          // But give me back not the comma but everything else 
    ),
    std::sregex_token_iterator(),   // iterator end for sregex_token_iterator, last token + 1
    std::back_inserter(cp.columns)  // Append everything to the target container
);

现在我们可以理解,这个复制操作是如何工作的。

下一步。我们想从文件中读取。该文件还包含某种相同的数据。相同的数据是行。

如上所述,我们可以迭代相似的数据。如果是文件输入或其他。为此,C++ 具有std::istream_iterator. 这是一个模板,作为模板参数,它获取应该读取的数据类型,作为构造函数参数,它获取对输入流的引用。输入流是 a std::cin、 astd::ifstream还是 a都没关系std::istringstream。所有类型的流的行为都是相同的。

由于我们没有 SO 文件,因此我使用(在下面的示例中)astd::istringstream来存储输入的 csv 文件。当然,您可以通过定义一个std::ifstream testCsv(filename). 没问题。

通过std::istream_iterator,我们遍历输入并读取相似的数据。在我们的例子中,一个问题是我们想要迭代特殊数据而不是一些内置数据类型。

为了解决这个问题,我们定义了一个代理类,它为我们做内部工作(我们不想知道如何,应该封装在代理中)。在代理中,我们覆盖类型转换运算符,以将结果转换为我们预期的std::istream_iterator.

最后一个重要的步骤。Astd::vector有一个范围构造函数。它还有很多其他构造函数,我们可以在定义类型变量时使用std::vector。但是对于我们的目的,这个构造函数最适合。

所以我们定义了一个变量 csv 并使用它的范围构造函数并给它一个范围的开始和一个范围的结束。而且,在我们的具体示例中,我们使用了 的开始和结束迭代器std::istream_iterator

如果我们结合以上所有内容,读取完整的 CSV 文件是单行的,它是调用其构造函数的变量的定义。

请查看生成的代码:

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

std::istringstream testCsv{ R"(0, 6/19/2019, 16:41:33, 33.972622, -117.323482, 24.25, 23.5, 23.25, 24.75, 25.5, 24.25, 25.25, 25.5, 24.5, 24, 24, 24.25, 25.5, 25.75, 25.25, 25, 24.5, 24.75, 24.75, 24.75, 25.25, 24.5, 24.5, 25.5, 23.75, 24.25, 24.75, 24, 24.25, 24, 24.5, 25, 24.25, 24, 24.25, 24.25, 24, 24.25, 24.5, 25.5, 24, 25, 24.5, 24.75, 24.5, 24.75, 24.75, 25.5, 24.5, 24.25, 24.25, 25.25, 25.25, 23.5, 25, 24.75, 24.5, 24.75, 25.5, 24.25, 23.5, 24, 25.25, 25, 605, 597, 515, 514, 509, 511, 508
0, 6/19/ 2019, 16:41:42, 33.972648, -117.323492, 24, 23.5, 23.75, 24.25, 25.5, 25.5, 25.25, 25.25, 25, 24.5, 24.25, 24.5, 25, 25.5, 25.5, 25.75, 24.25, 23.5, 24.75, 24.5, 24.25, 24.25, 24.5, 25.5, 24, 23.75, 24.5, 24, 24.25, 24, 24.75, 25.25, 25, 23.75, 24.75, 25.5, 25.5, 26, 24.75, 25.25, 24.5, 25, 25.25, 25.25, 26, 24.75, 24.5, 25.5, 24.5, 24.5, 25, 24.75, 24.25, 24.25, 25, 25, 24, 24, 24.75, 25, 23.25, 24.25, 25.5, 25.5, 609, 595, 1229, 1227, 1200, 1196, 1171
0, 6/19/2019, 16:41:49, 33.972643, -117.323479, 24.5, 23, 22.75, 24, 25.25, 25.5, 25, 26, 24.75, 24, 24, 24.75, 24.75, 25.25, 25.5, 26, 24.75, 24, 24.75, 25, 24.25, 24.25, 24.75, 26, 24.5, 23.5, 24.5, 24, 24, 24, 25, 25.75, 24.75, 23.25, 24.5, 24.5, 24.5, 25, 25.25, 25.25, 24, 25, 24.5, 25.25, 25.25, 25.25, 25.25, 25.5, 24.5, 24, 25.25, 25, 25, 24.25, 25, 25.25, 24.25, 24, 24.75, 25.25, 23.75, 24.25, 25, 25.5, 621, 601, 706, 725, 703, 707, 704
1, 6/19/2019, 16:41:55, 33.972631, -117.323483, 24.25, 23.75, 23.25, 24, 25.25, 25.25, 25.5, 26, 24.5, 24.25, 23.75, 24.5, 24.75, 25.5, 26, 25.5, 25, 23.75, 24.75, 24.75, 25.25, 25.25, 25, 26.25, 24.5, 23.5, 24.25, 25, 24.25, 24.25, 24.75, 25.75, 24.75, 23.75, 24.25, 24.25, 24.25, 24.5, 25.25, 25.25, 24.5, 24.5, 24.75, 25, 25.25, 26, 25.5, 25.25, 24.5, 24, 24.75, 25, 25, 25.25, 25.5, 25.5, 24.25, 25, 25, 25.75, 24.25, 24.5, 25.25, 25.5, 613, 602, 721, 720, 699, 704, 696
)" };


// Define Alias for Easier Reading
using Columns = std::vector<std::string>;
using CSV = std::vector<Columns>;


// 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();
        std::getline(is, line);

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

        // Split values and copy into resulting vector
        std::copy(std::sregex_token_iterator(line.begin(), line.end(), re, -1),
            std::sregex_token_iterator(),
            std::back_inserter(cp.columns));
        return is;
    }

    // Type cast operator overload.  Cast the type 'Columns' to std::vector<std::string>
    operator std::vector<std::string>() const { return columns; }
protected:
    // Temporary to hold the read vector
    Columns columns{};
};


int main()
{
    // Define variable CSV with its range constructor. Read complete CSV in this statement
    CSV csv{ std::istream_iterator<ColumnProxy>(testCsv), std::istream_iterator<ColumnProxy>() };

    // Print result. Go through all lines and then copy line elements to std::cout
    std::for_each(csv.begin(), csv.end(), [](Columns& c) {
        std::copy(c.begin(), c.end(), std::ostream_iterator<std::string>(std::cout, " ")); std::cout << "\n";   });
}

我希望解释足够详细,可以给你一个想法,你可以用现代 C++ 做什么


推荐阅读