我有一类食谱:
...
#include <list> //I'm using list of the STL
....
class recipe{
private:
list<pair<string,unsigned int>> ing; //A list of the ingredients of one recipe. String for the name of the ingredient and unsigned int for the quantity of each ingredient
public:
....
如何读取包含以下数据的文件,以便对operator >>
进行编程:
Salad;Tomatoe 50;Lettuce 100;Potatoe 60;Onion 10
Macaroni;Macaroni 250;Tomatoe 60;Oil 10
Fish and chips;fish 30;potatoe 30;Oil 40
我认为我可以做到:
istream & operator >> (istream &i, recipe &r){
string data, name;
int quantity;
stringstream s (line); //read a line from the file
getline(s,data," ");
name = data;
getline(s,data," ");
quantity = atoi(data.c_str());
}
但是显然每个食谱都有不同数量的成分,我不能那样做。那么,有什么帮助吗?
答案 0 :(得分:1)
将东西切成小块:
class recipe{
private:
std::string name;
std::list<std::pair<std::string,unsigned int>> ing;
std::pair<std::string,unsigned int> parseIngredient(const std::string& s) {
// do yourself
}
std::istream& readIngredients(istream& input) {
ing.clear();
std::string itemStr;
while (std::getline(input, itemStr, ';')) {
ing.push_back(parseIngredient(itemStr));
}
return input;
}
public:
std::istream& read(istream& input) {
std::string line;
if (std::getline(input, line)) {
std::istringstream lineStream{ line };
if (std::getline(lineStream, name, ';') && readIngredients(lineStream)) {
return input;
}
input.setstate(std::ios::failbit);
}
return input;
}
};
答案 1 :(得分:0)
要解决眼前的问题,有或多或少的标准方法。您要读取csv数据。
在您的情况下,这有点困难,因为您确实嵌套了csv数据。所以首先是“;”分隔列表,然后是空格分隔的列表。第二个有点不太精确,因为我们的配料在量之前有2个空格,例如“红辣椒2”中
现在,该怎么办呢? C ++是一种面向对象的语言。您可以创建由数据和对该数据进行操作的成员函数组成的对象。我们将定义一个“食谱”类,并覆盖插入程序和提取程序操作符。因为只有班级应该知道这是如何工作的。完成此操作后,输入和输出变得容易。
提取器,这就是问题的核心,如前所述,有点棘手。如何才能做到这一点?
在提取器中,我们将首先使用功能std::istream
从std::getline
中读取整行。有了这一行之后,我们看到一个std::string
包含用分号分隔的“数据字段”。 std::string
需要拆分,并且“数据字段”-内容应存储。另外,您需要拆分成分。
拆分字符串的过程也称为标记化。 “数据字段”内容也称为“令牌”。为此,C ++具有标准功能:std::sregex_token_iterator
。
并且因为我们已经为此目的设计了一些东西,所以我们应该使用它。
这是一个迭代器。用于遍历字符串,因此使用sregex。开始部分定义了我们将在什么输入范围上进行操作,然后在输入字符串中有一个std :: regex表示应该匹配的内容或不应该匹配的内容。匹配策略的类型由最后一个参数给出。
1 --> give me the stuff that I defined in the regex and
-1 --> give me that what is NOT matched based on the regex.
我们可以使用此迭代器将令牌存储在std::vector
中。 std::vector
具有范围构造器,该构造器接受2个迭代器一个参数,并将第一个迭代器和第二个迭代器之间的数据复制到std::vector
。
声明
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), separator, -1), {});
定义类型为std::vector<std::string>
的变量“令牌”,拆分std::string
并将令牌放入std::vector
。将数据保存在std::vector
中之后,我们将其复制到我们类的数据成员中。
对于第二次拆分,我们创建2个简单的lambda,并将数据复制到成分列表中。
非常简单。
下一步。我们想从文件中读取。文件内容也包含某种相同的数据。相同的数据是行。
如上所述,我们可以迭代相似的数据。是文件输入还是其他。为此,C ++具有std :: istream_iterator。这是一个模板,作为模板参数,它获取应读取的数据类型,作为构造函数参数,它获取对输入流的引用。不管输入流是std :: cin还是std :: ifstream或std :: istringstream。各种流的行为都相同。
由于我们没有SO的文件,因此我(在下面的示例中)使用std :: istringstream来存储输入的csv文件。但是,当然可以通过定义std :: ifstream csvFile(filename)来打开文件。没问题。
我们现在可以读取完整的csv文件并将其拆分为令牌并获取所有数据,只需定义一个新变量并再次使用range构造函数即可。
std::vector cookBook(std::istream_iterator<Recipe>(sourceFile), {});
这种非常简单的单行代码将读取完整的csv文件并完成所有预期的工作。
请注意:我正在使用C ++ 17,并且可以不使用模板参数定义std::vector
。编译器可以从给定的函数参数中推导出自变量。此功能称为CTAD(“类模板参数推导”)。
此外,您可以看到我没有明确使用“ end()”迭代器。
此迭代器将使用正确的类型从空括号括起来的初始化程序列表构造,因为由于std :: vector构造函数要求与第一个参数的类型相同,因此会推断出它。 >
Ì希望我能回答您的基本问题。请参见下面的完整C ++示例:
#include <iostream>
#include <regex>
#include <string>
#include <list>
#include <vector>
#include <iterator>
#include <sstream>
// Data types for ingredients and quantity
using Ingredients = std::pair<std::string, int>;
// Some helper functions
auto trim = [](const std::string & s) { return std::regex_replace(s, std::regex("^ +| +$"), "$1"); };
auto split = [](const std::string & s) {size_t pos{ s.rfind(' ') }; return Ingredients(s.substr(0, pos), std::stoi(s.substr(pos))); };
std::regex separator{ ";" };
// Our recipe class
struct Recipe {
// data
std::string title{};
std::list<Ingredients> ingredients{};
// Overwrite extractor
friend std::istream& operator >> (std::istream& is, Recipe& r) {
// We will read one line into this temproary
std::string line{};
if (std::getline(is, line)) {
// Tokenize the base string
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), separator, -1), {});
// get the recipe title
r.title = token[0];
// And, get the ingredients
r.ingredients.clear();
std::transform(std::next(token.begin()), token.end(), std::back_inserter(r.ingredients),
[](const std::string& s) { return split(trim(s)); });
}
return is;
}
// Overwrite inserter
friend std::ostream& operator << (std::ostream& os, const Recipe& r) {
// Print one recipe
os << "---- Recipe: " << r.title << "\n-- Ingredients:\n\n";
for (const auto& [ingredient, quantity] : r.ingredients)
os << ingredient << " --> " << quantity << "\n";
return os;
}
};
// Source file with CSV data. I added "Red Pepper 2" to Salad
std::istringstream sourceFile{ R"(Salad;Tomatoe 50;Lettuce 100;Potatoe 60;Red Pepper 2;Onion 10
Macaroni;Macaroni 250;Tomatoe 60;Oil 10
Fish and chips;fish 30;potatoe 30;Oil 40)" };
int main() {
// Read all data from the file with the following one-liner
std::vector cookBook(std::istream_iterator<Recipe>(sourceFile), {});
// Show some debug output
std::copy(cookBook.begin(), cookBook.end(), std::ostream_iterator<Recipe>(std::cout, "\n"));
return 0;
}
可惜没人会读这个。 。