如何在 cpp 中解析此文件?

How do I parse this file in cpp?

本文关键字:文件 cpp      更新时间:2023-10-16

我想解析一个包含以下内容的文件:

2 300
abc12 130
bcd22 456
3 400
abfg12 230
bcpd22 46
abfrg2 13

这里,2是行数,300是权重。

每行都有一个字符串和一个数字(价格(。与 3 和 400 相同。

我需要在一个数组中存储 130、456。

目前,我正在读取文件,每行都按std::string处理。我需要帮助才能进一步进步。

法典:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;
//void processString(string line);
void process2(string line);
int main(int argc, char ** argv) {
cout << "You have entered " << argc <<
" arguments:" << "n";
for (int i = 1; i < argc; ++i)
cout << argv[i] << "n";
//2, 4 are the file names
//Reading file - market price file
string line;
ifstream myfile(argv[2]);
if (myfile.is_open()) {
while (getline(myfile, line)) {
//  cout << line << 'n';
}
myfile.close();
} else cout << "Unable to open market price file";
//Reading file - price list file
string line_;
ifstream myfile2(argv[4]);
int c = 1;
if (myfile2.is_open()) {
while (getline(myfile2, line_)) {
// processString(line_);
process2(line_);
}
myfile2.close();
} else cout << "Unable to open price lists file";
//processString(line_);
return 0;
}
void process2(string line) {
string word = "";
for (auto x: line) {
if (x == ' ') {
word += " ";
} else {
word = word + x;
}
}
cout << word << endl;
}

是否有像 Java 中那样的拆分函数,所以我可以拆分并将所有内容存储为令牌?

您的帖子中有 2 个问题:

  1. 如何在 cpp 中解析此文件?
  2. 是否有像 Java 中那样的拆分函数,所以我可以拆分并将所有内容存储为令牌?

我将回答这两个问题并展示一个演示示例。

让我们从将字符串拆分为标记开始。有几种可能性。我们从简单的开始。

由于字符串中的标记由空格分隔,因此我们可以利用提取器运算符 (>>( 的功能。这将从输入流中读取数据,直到空格,然后将此读取数据转换为指定的变量。您知道此操作可以链接。

然后对于示例字符串

const std::string line{ "Token1 Token2 Token3 Token4" };

您可以简单地将其放入std::istringstream,然后从流中提取变量:

std::istringstream iss1(line);
iss1 >> subString1 >> subString2 >> subString3 >> subString4;

缺点是你需要写很多东西,你必须知道字符串中元素的数量。

我们可以通过使用向量作为 taget 数据存储并用其范围构造函数填充它来克服这个问题。向量范围构造函数采用开始和结束交互器,并将数据复制到其中。

作为迭代器,我们使用std::istream_iterator.简单来说,这将调用提取器运算符 (>>(,直到所有数据都被消耗殆尽。无论我们将拥有多少数据。

这将如下所示:

std::istringstream iss2(line);
std::vector token(std::istream_iterator<std::string>(iss2), {});

这可能看起来很复杂,但事实并非如此。我们定义了一个类型为std::vector的变量"令牌"。我们使用它的范围构造函数。

而且,我们可以在没有模板参数的情况下定义 std::vector。编译器可以从给定的函数参数推断参数。此功能称为 CTAD("类模板参数推导",C++需要 17(。

此外,您可以看到我没有明确使用"end(("迭代器。

此迭代器将从具有正确类型的空大括号括起来的默认初始值设定项构造,因为由于 std::vector 构造函数需要它,它将被推导出为与第一个参数的类型相同。


还有一个额外的解决方案。这是最强大的解决方案,因此一开始可能会有点复杂。

这样就可以避免使用 std::istringstream,并使用 std::sregex_token_iterator 直接将字符串转换为令牌。使用起来非常简单。结果是用于拆分原始字符串的单行:

std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

因此,现代C++具有内置功能,该功能正是为标记字符串而设计的。它被称为std::sregex_token_iterator。这是什么东西?

顾名思义,它是一个迭代器。它将遍历一个字符串(因此其名称中的"s"(并返回拆分的令牌。令牌将再次匹配正则表达式。或者,在本地,分隔符将被匹配,其余部分将被视为令牌并返回。这将通过其构造函数中的最后一个标志进行控制。

让我们看一下这个构造函数:

token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});

第一个参数是,它应该在源字符串中开始,第二个参数是迭代器应该工作的结束位置。最后一个参数为:

  • 1、如果你想对正则表达式有一个正匹配
  • -1,将返回与正则表达式不匹配的所有内容

最后但并非最不重要的是正则表达式本身。请阅读网络 abot regex'es。有大量可用的页面。

请在此处查看所有 3 种解决方案的演示:

#include <iostream>
#include <string>
#include <vector>
#include <regex>
#include <sstream>
#include <iterator>
#include <algorithm>
/// Split string into tokens
int main() {
// White space separated tokens in a string
const std::string line{ "Token1 Token2 Token3 Token4" };
// Solution 1: Use extractor operator ----------------------------------
// Here, we will store the result
std::string subString1{}, subString2{}, subString3{}, subString4{};
// Put the line into an istringstream for easier extraction
std::istringstream iss1(line);
iss1 >> subString1 >> subString2 >> subString3 >> subString4;
// Show result
std::cout << "nSolution 1:  Use inserter operatorn- Data: -n" << subString1 << "n"
<< subString2 << "n" << subString3 << "n" << subString4 << "n";

// Solution 2: Use istream_iterator ----------------------------------
std::istringstream iss2(line);
std::vector token(std::istream_iterator<std::string>(iss2), {});
// Show result
std::cout << "nSolution 2:  Use istream_iteratorn- Data: -n";
std::copy(token.begin(), token.end(), std::ostream_iterator<std::string>(std::cout, "n"));

// Solution 3: Use std::sregex_token_iterator ----------------------------------
const std::regex re(" ");
std::vector<std::string> token2(std::sregex_token_iterator(line.begin(), line.end(), re, -1), {});
// Show result
std::cout << "nSolution 3:  Use sregex_token_iteratorn- Data: -n";
std::copy(token2.begin(), token2.end(), std::ostream_iterator<std::string>(std::cout, "n"));

return 0;
}


所以,现在关于如何读取文本文件的答案。

创建正确的数据结构至关重要。然后,覆盖机械臂和提取器运算符,并将上述功能放入其中。

请参阅下面的演示示例。当然,还有许多其他可能的解决方案:

#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iterator>
struct ItemAndPrice {
// Data
std::string item{};
unsigned int price{};
// Extractor
friend std::istream& operator >> (std::istream& is, ItemAndPrice& iap) {
// Read a complete line from the stream and check, if that worked
if (std::string line{}; std::getline(is, line)) {
// Read the item and price from that line and check, if that worked
if (std::istringstream iss(line); !(iss >> iap.item >> iap.price))
// There was an error, while reading item and price. Set failbit of input stream
is.setf(std::ios::failbit);
}
return is;
}
// Inserter
friend std::ostream& operator << (std::ostream& os, const ItemAndPrice& iap) {
// Simple output of our internal data
return os << iap.item << " " << iap.price;
}
};
struct MarketPrice {
// Data
std::vector<ItemAndPrice> marketPriceData{};
size_t numberOfElements() const { return marketPriceData.size(); }
unsigned int weight{};
// Extractor
friend std::istream& operator >> (std::istream& is, MarketPrice& mp) {
// Read a complete line from the stream and check, if that worked
if (std::string line{}; std::getline(is, line)) {
size_t numberOfEntries{};
// Read the number of following entries and the weigth from that line and check, if that worked
if (std::istringstream iss(line); (iss >> numberOfEntries >> mp.weight)) {
mp.marketPriceData.clear();
// Now copy the numberOfEntries next lines into our vector
std::copy_n(std::istream_iterator<ItemAndPrice>(is), numberOfEntries, std::back_inserter(mp.marketPriceData));
}
else {
// There was an error, while reading number of following entries and the weigth. Set failbit of input stream
is.setf(std::ios::failbit);
}
}
return is;
};
// Inserter
friend std::ostream& operator << (std::ostream& os, const MarketPrice& mp) {
// Simple output of our internal data
os << "nNumber of Elements: " << mp.numberOfElements() << "   Weight: " << mp.weight << "n";
// Now copy all marekt price data to output stream
if (os) std::copy(mp.marketPriceData.begin(), mp.marketPriceData.end(), std::ostream_iterator<ItemAndPrice>(os, "n"));
return os;
}
};
// For this example I do not use argv and argc and file streams. 
// This, because on Stackoverflow, I do not have files on Stackoverflow
// So, I put the file data in an istringstream. But for the below example, 
// there is no difference between a file stream or a string stream
std::istringstream sourceFile{R"(2 300
abc12 130
bcd22 456
3 400
abfg12 230
bcpd22 46
abfrg2 13)"};

int main() {
// Here we will store all the resulting data
// So, read the complete source file, parse the data and store result in vector
std::vector mp(std::istream_iterator<MarketPrice>(sourceFile), {});
// Now, all data are in mp. You may work with that now
// Show result on display
std::copy(mp.begin(), mp.end(), std::ostream_iterator<MarketPrice>(std::cout, "n"));
return 0;
}