如何读取具有不同数字行数的文件
Posted
技术标签:
【中文标题】如何读取具有不同数字行数的文件【英文标题】:How can I read a file with lines of different number of numbers 【发布时间】:2013-10-07 01:30:46 【问题描述】:我正在尝试读取一个数据文件,大约 2000 行,文件看起来像
1.1 1.2 1.3 1.4 1.5
1.6 1.7 1.8 1.9
2.0
2.1 2.2 2.3 2.4 2.5
实际上有一个空格(空格)和1.3/1.7在同一列
我将它设置为存储的方式是一个结构向量,其中
struct num
double d1, d2, d3, d4, d5;
;
我想要实现的是
num A;
vector<num> data
for (int i = 0; i < 4; i++)
File >> A.d1 >> A.d2 >> A.d3 >> A.d4 >> A.d5;
data.push_back(A);
并找到识别第二行中的空白并存储d1 = 1.6,d2 = 0,d3 = 1.7等的逻辑。第三行是d1 = 2.0和d2,d3,d4, d5=0 如果可能的话,我只是对如何测试/获取实现这一点的逻辑感到困惑 我在 C++ VS2010 在查看第一个答案后认为我应该提供更多信息,文件中的每一行都属于一颗卫星,每个数字代表对特定波长的观察,所以如果它是空白的,则意味着它没有对该波长的观察。
详细来说,第一行代表卫星 1 对所有 5 个波长都有观测,第 2 行代表卫星 2,对波长 1、3、4、5 有观测,而对波长 4 没有观测。
这就是为什么我试图将它分成每一行作为一个单独的结构,因为每一行都是一个单独的卫星
【问题讨论】:
我假设2.0
的行后面也有空格?也就是8个?
【参考方案1】:
观察您的数据:
每个数据点都按以下模式存储:数据、空间。 如果数据点不存在,则用空格表示,除非它是所有其他输出被截断为换行符的最后一个不存在的数据点。这是我想出的:
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
#include <cstdlib>
#include <sstream>
#include <iomanip>
#include <cctype>
using namespace std;
//note all the lines are stored WITH newlines at the end of them.
//This is merely an artifact of the methodology I am using,
//as the newline is a flag that truncates output (as per your problem)
vector<string> preparse_input(const std::string& filename)
vector<string> lines;
ifstream ifile;
ifile.open(filename.c_str(), ios::in);
if (!ifile.is_open())
exit(1);
string temp, chars, line;
char ch;
while(getline(ifile, temp))
temp += "\n";//getline removes the newline: because we need it, reinsert it
istringstream iss(temp);
//first read in the line char by char
while(iss >> noskipws >> ch)
chars += ch;
bool replaced_newline = false;
int nargs = 0;
//I could have used iterators here, but IMO, this way is easier to read. Modify if need be.
for (int i = 0; i < chars.size(); ++i)
if (isdigit(chars[i]) && chars[i+1] == ' ')
nargs += 1;
else if(isspace(chars[i]) && isspace(chars[i+1]))
if (chars[i+1] == '\n')
replaced_newline = true;
//this means that there is no value set
//hence, set the value to 0 for the value part:
chars[i+1] = '0';
line += chars[i];
++i;//now, skip to the next character since 1 is for spacing, the other is for the value
nargs += 1;
//now rebuild the line:
line += chars[i];
if(isdigit(chars[i]) && chars[i+1] == '\n')
nargs += 1;
//check nargs:
for (int i = nargs; i < 5; ++i)
line += " 0";
nargs += 1;
if (replaced_newline)
line += '\n';
replaced_newline = false;
lines.push_back(line);
chars.clear();
line.clear();
ifile.close();
return lines;
//this way, it's much easier to adapt to any type of input that you may have
template <typename T>
vector< vector<T> > parse_input (const vector<string>& lines)
vector< vector<T> > values;
T val = 0;
for(vector<string>::const_iterator it = lines.begin(); it != lines.end(); ++it)
vector<T> line;
istringstream iss(*it);
string temp;
while(getline(iss, temp, ' '))
if (istringstream(temp) >> val)
line.push_back(val);
else
line.push_back(0);//this is the value that badly parsed values will be set to.
//you have the option of setting it to some sentinel value, say -1, so you can go back and correct it later on, if need be. Depending on how you want to treat this error - hard or soft (stop program execution vs adapt and continue parsing), then you can adapt it accordingly
//I opted to treat it as a soft error but without a sentinel value - so I set it to 0 (-1 as that is probably more applicable in a general case), and informed the user that an error occurred
//The flipside of that is that I could have treated this as a hard error and have `exit(2)` (or whatever error code you wish to set).
cerr << "There was a problem storing:\"" << temp << "\"\n";
values.push_back(line);
return values;
int main()
string filename = "data.dat";
vector<string> lines = preparse_input(filename);
vector < vector<double> > values = parse_input<double>(lines);
for (int i = 0; i < values.size(); ++i)
for (int j = 0; j < values[i].size(); ++j)
cout << values[i][j] << " ";
cout << endl;
return 0;
总的来说,我通过逐个字符读取每一行来分解字符串,然后通过用0
替换空格来重建每一行,以便于解析。为什么?因为没有这样的值,就无法判断存储或跳过了哪个参数(使用默认的ifstream_object >> type
方法)。
这样,如果我然后使用stringstream
对象来解析输入,我可以正确确定设置了哪个参数,或者没有设置;然后,存储结果,一切都很好。这就是你想要的。
并且,在以下数据上使用它:
1.1 1.2 1.3 1.4 1.5
1.6 1.7 1.8 1.9
2.0
2.0
2.1 2.2 2.3 2.4 2.5
2.1 2.4
给你输出:
1.1 1.2 1.3 1.4 1.5
1.6 0 1.7 1.8 1.9
2 0 0 0 0
2 0 0 0 0
2.1 2.2 2.3 2.4 2.5
2.1 0 0 2.4 0
注意:第 3 行有 8 个空格(1 个表示无数据,1 个表示空格)。第 4 行是来自原始数据的行。第 6 行包含 5 个空格(遵循引用的模式)。
最后,让我说,这是迄今为止,我遇到过的最疯狂的数据存储方法之一。
【讨论】:
我很难找到更多疯狂的格式,我想到了 xml,但人们并不倾向于同意我的观点。 我很感激,这是一个巨大的帮助!至于数据的存储,我不知道任何其他方式来存储它并有效地访问它,因为每条线代表 1 颗卫星,它们是 2000 条线。您对如何存储它以使每行彼此分开有什么建议 @user2840470:如果我的回复对您有用,请考虑接受。 :) 除此之外,最好使用非空格分隔的数据格式,例如 csv。这样,如果前 3 列数据丢失,就不会再进行第二次猜测了:,,,4.1,2.3
。类似的东西。但是,那是另一回事了。总体而言,仅使用 C++ 为您完成所有解析可能是矫枉过正。理想情况下,您可以将另一种语言与 C++ 结合使用,例如 Python,来帮助您进行数据预处理。【参考方案2】:
鉴于您的文件格式是空格分隔的,您可以使用正则表达式提取列。我假设你可以使用 C++11 或者如果不是 Boost 正则表达式。
那么你可以使用下面的函数将一个字符串拆分成tokens。
std::vector<std::string> split(const std::string& input, const std::regex& regex)
// passing -1 as the submatch index parameter performs splitting
std::sregex_token_iterator
first(input.begin(), input.end(), regex, -1),
last;
return std::vector<std::string>(first, last);
例如,假设您的数据在“data.txt”中,我用这种方式获取值:
#include <iostream>
#include <fstream>
#include <string>
#include <regex>
#include <vector>
using namespace std;
std::vector<std::string> split(const string& input, const regex& regex)
// passing -1 as the submatch index parameter performs splitting
std::sregex_token_iterator
first(input.begin(), input.end(), regex, -1),
last;
return vector<std::string>(first, last);
int main()
ifstream f("data.txt");
string s;
while (getline(f, s))
vector<string> values = split(s, regex("\\s"));
for (unsigned i = 0; i < values.size(); ++i)
cout << "[" << values[i] << "] ";
cout << endl;
return 0;
结果如下:
[1.1] [1.2] [1.3] [1.4] [1.5]
[1.6] [] [1.7] [1.8] [1.9]
[2.0] [] [] []
[2.1] [2.2] [2.3] [2.4] [2.5]
请注意,第 4 行缺少一列,但那是因为我不太确定该行有多少空格。如果您知道不超过 5 列,则可以在输出阶段进行更正。
希望这种方法对您有所帮助。
【讨论】:
【参考方案3】:为什么不直接使用std:vector
来保存浮点数组。
要向您使用的向量添加新元素:
std::vector::push_back
当您阅读每个字符时,请查看它是数字还是句点。
如果是,则将其添加到std::string
,然后使用atof
和mystring.c_str()
作为参数将其转换为浮点数。
这也可能有助于将字符串转换为浮点数:
std::string to float or double
所以,读入一个字符串,然后将浮点数推入一个向量,然后重复,跳过不是数字或句点的字符。
在该行的末尾,您的向量包含所有浮点数,如果您想将它们加入到带有自定义分隔符的字符串中,您可以查看此问题的答案:
std::vector to string with custom delimiter
【讨论】:
因为我需要访问信息的方式我需要它分开。每条线代表单个卫星在不同波长上的 5 次观测,如果它是空白的,则表示它没有对该波长的观测。 因此,如果缺少观察,则只需一个空元素,只需使用并浮动观察[5] 并确保在每个循环之前调用 memset(以快速清理数组) .以上是关于如何读取具有不同数字行数的文件的主要内容,如果未能解决你的问题,请参考以下文章
如何在 UITableview 中创建具有 n 行数的 UITextField
Java读取列文件中具有不同数字的txt并将数据存储在arraylist中