使用不同类型的分隔符从.txt文件中读取输入

Reading input from .txt file with different kinds of delimiters

本文关键字:文件 txt 读取 输入 分隔符 同类型      更新时间:2023-10-16

我正在尝试读取.txt文件并检索x和y坐标,城市ID和城市名称。

[1, 1]-3-Big_City
//expected : x = 1,  y = 1, city_ID = 3, city_name = Big_City
[2, 8]-2-Mid_City
//expected : x = 2,  y = 8, city_ID = 2, city_name = Mid_City

如何读取和存储xycity_IDcity_name

我有一个可以打印整行的while循环,但我不知道如何使用不需要的符号来划分以获得所需的值。

while(getline(inputFile, aLine))
{
getline(inputFile, aLine, '[');
x = aLine;
getline(inputFile, aLine, ',');
y = aLine;
// stuck here has getline 3rd parameter only accepts character and not string
getline(inputFile, aLine, ']-');
city_ID = aLine;
....
cout << x << " " << y << " " << city_ID
}

我得到的输出也不是我预期的。第一行将丢失,其他行不显示。

解析一条复杂的行以从中获取所需的信息并不困难,您只需像吃鲸鱼一样接近它 - 一次一个字节......

在您的情况下,由于您正在协调 3 个整数和每条城市数据记录的字符串(例如x, y, idname)声明一个简单的struct将不同类型的数据协调为一个对象是有意义的。这使您可以将所有数据存储在structvector中。在这里,您可以使用简单的东西,例如:

struct data_t {         /* simple struct to coordinate data for each city */
int x, y, id;
std::string name;
};

若要读取数据,最简单的方法是打开文件,然后将每一行数据读入字符串。然后,您就有了从中解析数据的字符串。将字符串转换为stringstream很有帮助,因为它允许您将数据行视为流,利用>>以及各种连续getline从该行获取所需信息。

要从从该行创建的字符串流中获取信息,让我们看一个示例,说明从字符串流中包含的行开始获取所需信息需要执行的操作:

[1, 1]-3-Big_City

在读取循环中,我们将值读取到临时struct中,如果所有内容都正确解析,我们会将该临时结构添加到结构向量中。临时结构可以是:

data_t dtmp;                    /* temp data struct */

要获取x值,您需要删除["1, 1]-3-Big_City"留在字符串流中。关键是验证每个步骤。要删除[您可以使用:

/* read/discard [ */
if (!getline (ss, stmp, '[')) {
std::cerr << "error: invalid format before - x.n";
continue;
}

现在直接从字符串流中读取1dtmp.x验证整数转换是否成功:

if (!(ss >> dtmp.x)) {      /* read x from stringstream */
std::cerr << "error: invalid format - x.n";
continue;
}

由于读取在字符串流中的第一个非整数字符处停止,因此现在只剩下", 1]-3-Big_City".由于>>将跳过前导空格,因此您需要做的就是准备读取y值,删除',',然后将y值读入dtmp.y

/* read/discard , */
if (!getline (ss, stmp, ',')) {
std::cerr << "error: invalid format before - y.n";
continue;
}
if (!(ss >> dtmp.y)) {      /* read y from stringstream */
std::cerr << "error: invalid format - y.n";
continue;
}

(注意:由于您知道只需要删除单个字符,因此您可以使用ss.get()读取字符,但为了保持一致,您可以继续使用getline和分隔符 - 完全取决于您)

读取y值后,字符串流中只剩下"]-3-Big_City",因此下一个任务是剥离'-'以公开id以供读取。在这一点上,很明显,您基本上可以重复我们上面所做的一切,以读取其余的值。对于id,让我们做:

if (!getline (ss, stmp, '-')) { /* read/discard - */
std::cerr << "error: invalid format before - id.n";
continue;
}
if (!(ss >> dtmp.id)) {     /* read id from stringstream */
std::cerr << "error: invalid format - id.n";
continue;
}

离开"-Big_City",再次,要么ss.get()剥离'-',要么继续getline,就像我们上面所做的那样:

if (!getline (ss, stmp, '-')) { /* read/discard - */
std::cerr << "error: invalid format before - name.n";
continue;
}
if (!(ss >> dtmp.name)) {   /* read name from stringstream */
std::cerr << "error: invalid format - name.n";
continue;
}

就是这样。您的数据现在已解析为临时结构dtmp,您只需将数据存储在向量中:

data.push_back(dtmp);   /* add temp struct to vector of struct */

将上述内容放在一个循环中将允许您将文件中的所有数据解析为结构向量。随着编程的进展,并开始对城市数据等对象使用类,您会发现甚至可以重载>>运算符来处理上述所有工作,并且您只需要提供打开的文件流,您的重载输入函数将完成其余的工作。 (留到另一天)

读取数据文件的简短示例可以是:

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
struct data_t {         /* simple struct to coordinate data for each city */
int x, y, id;
std::string name;
};
int main (int argc, char **argv) {
if (argc < 2) { /* validate argument given for filename */
std::cerr << "error: filename required as 1st argument.n";
return 1;
}
std::string line;           /* string to hold each line read from file */
std::vector<data_t> data;   /* vector of struct data_t to hold data */
std::ifstream f (argv[1]);  /* open filename given for reading */
while (getline (f, line)) {         /* read each line of file into line */
data_t dtmp;                    /* temp data struct */
std::string stmp;               /* temp string for parsing */
std::stringstream ss (line);    /* create stringstream from line */
/* read/discard [ */
if (!getline (ss, stmp, '[')) {
std::cerr << "error: invalid format before - x.n";
continue;
}
if (!(ss >> dtmp.x)) {      /* read x from stringstream */
std::cerr << "error: invalid format - x.n";
continue;
}
/* read/discard , */
if (!getline (ss, stmp, ',')) {
std::cerr << "error: invalid format before - y.n";
continue;
}
if (!(ss >> dtmp.y)) {      /* read y from stringstream */
std::cerr << "error: invalid format - y.n";
continue;
}
if (!getline (ss, stmp, '-')) { /* read/discard - */
std::cerr << "error: invalid format before - id.n";
continue;
}
if (!(ss >> dtmp.id)) {     /* read id from stringstream */
std::cerr << "error: invalid format - id.n";
continue;
}
if (!getline (ss, stmp, '-')) { /* read/discard - */
std::cerr << "error: invalid format before - name.n";
continue;
}
if (!(ss >> dtmp.name)) {   /* read name from stringstream */
std::cerr << "error: invalid format - name.n";
continue;
}
data.push_back(dtmp);   /* add temp struct to vector of struct */
}
for (auto& d : data)    /* output all stored data */
std::cout << "x: " << d.x << "  y: " << d.y << "  id: " << d.id
<< "  name: " << d.name << 'n';
}

示例输入文件

$ cat dat/xyid.txt
[1, 1]-3-Big_City
[2, 8]-2-Mid_City

示例使用/输出

$ ./bin/citydata dat/xyid.txt
x: 1  y: 1  id: 3  name: Big_City
x: 2  y: 8  id: 2  name: Mid_City

查看所有内容,并确保您了解所做的事情。如果您有任何疑问,请在下面的评论中提问。

现在是下一个方法。这是使用面向对象的习语和现代C++算法。

我们有数据和方法,它们在某种程度上属于一起。为此,C++中有类(结构)。因此,您可以使用成员变量和方法定义一个类,这些类可以使用类变量和方法。一切都作为一个对象工作。

此外。该类知道如何读取或打印其值。只有全班同学应该知道这一点。这种智慧是概括的。

接下来,我们要搜索嵌入在字符串中某处的有趣数据。字符串始终包含某种模式。在您的情况下,您有 3 个整数和一个字符串作为有趣的数据,以及介于两者之间的一些分隔符,无论它们是什么。

为了匹配这些模式并搜索字符串中有趣的部分,C++std::regex.它们非常强大,因此定义起来有点复杂。

在下面的示例中,我将使用const std::regex re(R"((d+).*?(d+).*?(d+).*?([w_]+))");。这定义了 4 组子匹配项(在括号中)以及介于两者之间的内容。所以任何分隔符、空格或任何可能的东西。

如果要更严格,只需更改模式,就可以检测源数据中的错误。请参阅const std::regex re(R"([(d+), (d+)]-(d+)-([w_]+))");。这是一种更严格的方法。如果出现错误,将不会读取输入文件。或者只是以有效数据开头。

请参阅以下示例:

#include <string>
#include <regex>
#include <iterator>
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <ios>
std::istringstream testFile{ R"([12, 34]-56-Big_City1
[789 , 910 ] - 11 -  Big_City2
(55; 88} + 22 *** Big_City3
[77, 666]-555-Big_City4)" };

const std::regex re(R"((d+).*?(d+).*?(d+).*?([w_]+))");

struct CityData
{
// Define the city's data
int xCoordinate{};
int yCoordinate{};
int cityId{};
std::string cityName{};
// Overload the extractor operator >> to read and parse a line
friend std::istream& operator >> (std::istream& is, CityData& cd) {
// We will read the line in this variable
std::string line{};                 
// Read the line and check, if it is OK
if (std::getline(is, line)) {
// Find the matched substrings
std::smatch sm{};
if (std::regex_search(line, sm, re)) {
// An convert them to students record
cd.xCoordinate = std::stoi(sm[1]);
cd.yCoordinate = std::stoi(sm[2]);
cd.cityId = std::stoi(sm[3]);
cd.cityName = sm[4];
}
else {
is.setstate(std::ios::failbit);
}
}
return is;
}
friend std::ostream& operator << (std::ostream& os, const CityData& cd) {
return os << cd.xCoordinate << ' ' << cd.yCoordinate << ' ' << cd.cityId;
}
};
int main()
{
// Define the variable cityData with the vectors range constructor. Read complete input file and parse data
std::vector<CityData> cityData{ std::istream_iterator<CityData>(testFile),std::istream_iterator<CityData>() };
// Print the complete vector to std::cout
std::copy(cityData.begin(), cityData.end(), std::ostream_iterator<CityData>(std::cout,"n"));
return 0;
}

请注意:main仅包含 2 个与 CityData 相关的声明。全班知道如何操作应该做。

而且,因为我不能在SO上使用文件,所以我从"std::istringstream"读取数据。这与从文件读取相同。

我的回答更像是一种解决方法:

我认为您真的不需要使用多个分隔符。这将为您的程序增加无用的开销来解析它们。 相反,你可以说"第一个参数是x,第二个参数是y,依此类推" 通过此解析,它将导致从该行获取一个数组并使用相应的正确索引访问它(您应该为此使用 enum)。

试试这个片段:

struct city_t {
int x;
int y;
int city_ID;
string city_name;
};
int main()
{
ifstream file("person.txt");
vector<city_t> cities;
if (file.is_open()) {
string line;
while (getline(file, line, 'n')) {  // parse a line at a time
stringstream ss{ line }; // remove expected :
city_t city;
string word;
while (getline(ss, word, ',')) { //parse a line at a time
int fieldidx = 1;
if (word.find("[") != -1)
city.x = std::atoi(word.substr(1).c_str());
else {
stringstream ss2{ word };
string field;
while (getline(ss2, field, '-')) {
field = field;
if (fieldidx == 1) {
city.y = std::atoi(field.c_str());
}
else
if (fieldidx == 2) {
city.city_ID = std::atoi(field.c_str());
}
else
if (fieldidx == 3) {
city.city_name = field;
}
fieldidx++;
}
}
}
cities.push_back(city);
}
file.close();
}
for (auto e : cities) {
cout << e.x << "  " << e.y << "  " << e.city_ID << "  " << e.city_name << "n";
}
}

{

string locationLine;  <br/>
while(getline(inputFile, locationLine))
{
istringstream streamLine(locationLine);  <br/>
string coordinates;  <br/>
string city_ID;  <br/>
string city_name;  <br/>
getline(streamLine, coordinates, '-');  <br/>
getline(streamLine, city_ID, '-');  <br/>
getline(streamLine, city_name, '-');  <br/>
}

}

//需要编写一个引用坐标的函数来//删除"["和","和"]">