我正在尝试将.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 ++方法。
而且仍然所有人都链接到“如何使用C ++读取和解析CSV文件?”,问题出于2009年,至今已有10多年的历史了。大多数答案也很老而且很复杂。因此,也许是时候进行更改了。
在现代C ++中,您有迭代范围的算法。您通常会看到类似“ someAlgoritm(container.begin(),container.end(),someLambda)”的内容。这个想法是我们迭代一些相似的元素。
在您的情况下,我们遍历您输入字符串中的标记,并创建子字符串。这称为标记化。
出于这个目的,我们有std::sregex_token_iterator
。并且因为我们已经为此目的定义了一些东西,所以我们应该使用它。
这是一个迭代器。用于遍历字符串,因此使用sregex。开始部分定义了我们将在什么输入范围上进行操作,然后在输入字符串中有一个std::regex
表示应该匹配的内容或不应该匹配的内容。匹配策略的类型由last参数给出。
因此,既然我们了解迭代器,我们就可以std ::将令牌从迭代器复制到目标,即std::vector
为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
。这是一个模板,作为模板参数,它获取应读取的数据类型,作为构造函数参数,它获取对输入流的引用。不管输入流是std::cin
还是std::ifstream
或std::istringstream
。各种流的行为都相同。
并且由于我们没有SO的文件,因此我使用(在下面的示例中)std::istringstream
存储输入的csv文件。但是当然可以通过定义std::ifstream testCsv(filename)
打开文件。没问题。
并且使用std::istream_iterator
,我们遍历输入并读取相似的数据。在我们的案例中,一个问题是我们要遍历特殊数据而不是某些内置数据类型。
为了解决这个问题,我们定义了一个Proxy类,该类为我们完成内部工作(我们不希望知道如何封装在代理中)。在代理中,我们覆盖类型强制转换运算符,以将结果转换为std::istream_iterator
的预期类型。
还有最后一个重要步骤。 std::vector
具有范围构造器。在定义std::vector
类型的变量时,还可以使用许多其他构造函数。但是出于我们的目的,此构造函数最合适。
因此,我们定义一个变量csv并使用其范围构造函数,并为其指定范围的开始和范围的结束。并且,在我们的特定示例中,我们使用std::istream_iterator
的开始和结束迭代器。
如果我们结合以上所有内容,则读取完整的CSV文件是one-liner
,它是通过调用其构造函数定义的变量。]>请查看结果代码:
#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 ++可以做什么