我正在创建一个处理txt文件数据的应用程序。
这个想法是txt文件可能有不同的格式,应该读入C ++。
一个例子可能是3I2, 3X, I3
,应该这样做:“首先我们有3个长度为2的整数,然后我们有3个空点,然后我们有1个长度为3的整数。
最好迭代文件,产生线条,然后作为字符串迭代线条?什么是一种有效的方法来巧妙地迭代而忽略了3个被忽略的点?
E.g。
101112---100
102113---101
103114---102
为:
10, 11, 12, 100
10, 21, 13, 101
10, 31, 14, 102
答案 0 :(得分:8)
Kyle Kanos给出的链接很好; * scanf / * printf格式字符串很好地映射到fortran格式字符串。使用C风格的IO实际上更容易实现,但使用C ++样式流也是可行的:
#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream fortranfile;
fortranfile.open("input.txt");
if (fortranfile.is_open()) {
std::string line;
getline(fortranfile, line);
while (fortranfile.good()) {
char dummy[4];
int i1, i2, i3, i4;
sscanf(line.c_str(), "%2d%2d%2d%3s%3d", &i1, &i2, &i3, dummy, &i4);
std::cout << "Line: '" << line << "' -> " << i1 << " " << i2 << " "
<< i3 << " " << i4 << std::endl;
getline(fortranfile, line);
}
}
fortranfile.close();
return 0;
}
跑步给出
$ g++ -o readinput readinput.cc
$ ./readinput
Line: '101112---100' -> 10 11 12 100
Line: '102113---101' -> 10 21 13 101
Line: '103114---102' -> 10 31 14 102
这里我们使用的格式字符串是%2d%2d%2d%3s%3d
- %2d
的3个副本(宽度为2的十进制整数)后跟%3s
(宽度为3的字符串,我们读入我们从不使用的变量)后跟%3d
(宽度为3的十进制整数)。
答案 1 :(得分:6)
鉴于您希望 dynamically parse Fortran Format specifier flags ,您应该注意:您已立即进入解析器领域。
除了解析其他人在此处注意到的输入的其他方法之外:
sscanf
streams
我的建议是,如果boost可用,您可以使用它来实现简单的解析器,以便使用Regex和STL容器的组合进行即时操作
根据您所描述的以及在不同地方展示的内容,您可以使用正则表达式捕获构建您希望支持的语法的天真实现:
(\\d{0,8})([[:alpha:]])(\\d{0,8})
使用this reference for the Fortran Format Specifier Flags,您可以实施天真解决方案,如下所示:
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <cstdlib>
#include <boost/regex.hpp>
#include <boost/tokenizer.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>
//A POD Data Structure used for storing Fortran Format Tokens into their relative forms
typedef struct FortranFormatSpecifier {
char type;//the type of the variable
size_t number;//the number of times the variable is repeated
size_t length;//the length of the variable type
} FFlag;
//This class implements a rudimentary parser to parse Fortran Format
//Specifier Flags using Boost regexes.
class FormatParser {
public:
//typedefs for further use with the class and class methods
typedef boost::tokenizer<boost::char_separator<char> > bst_tokenizer;
typedef std::vector<std::vector<std::string> > vvstr;
typedef std::vector<std::string> vstr;
typedef std::vector<std::vector<int> > vvint;
typedef std::vector<int> vint;
FormatParser();
FormatParser(const std::string& fmt, const std::string& fname);
void parse();
void printIntData();
void printCharData();
private:
bool validateFmtString();
size_t determineOccurence(const std::string& numStr);
FFlag setFortranFmtArgs(const boost::smatch& matches);
void parseAndStore(const std::string& line);
void storeData();
std::string mFmtStr; //this holds the format string
std::string mFilename; //the name of the file
FFlag mFmt; //a temporary FFlag variable
std::vector<FFlag> mFortranVars; //this holds all the flags and details of them
std::vector<std::string> mRawData; //this holds the raw tokens
//this is where you will hold all the types of data you wish to support
vvint mIntData; //this holds all the int data
vvstr mCharData; //this holds all the character data (stored as strings for convenience)
};
FormatParser::FormatParser() : mFmtStr(), mFilename(), mFmt(), mFortranVars(), mRawData(), mIntData(), mCharData() {}
FormatParser::FormatParser(const std::string& fmt, const std::string& fname) : mFmtStr(fmt), mFilename(fname), mFmt(), mFortranVars(), mRawData(), mIntData(), mCharData() {}
//this function determines the number of times that a variable occurs
//by parsing a numeric string and returning the associated output
//based on the grammar
size_t FormatParser::determineOccurence(const std::string& numStr) {
size_t num = 0;
//this case means that no number was supplied in front of the type
if (numStr.empty()) {
num = 1;//hence, the default is 1
}
else {
//attempt to parse the numeric string and find it's equivalent
//integer value (since all occurences are whole numbers)
size_t n = atoi(numStr.c_str());
//this case covers if the numeric string is expicitly 0
//hence, logically, it doesn't occur, set the value accordingly
if (n == 0) {
num = 0;
}
else {
//set the value to its converted representation
num = n;
}
}
return num;
}
//from the boost::smatches, determine the set flags, store them
//and return it
FFlag FormatParser::setFortranFmtArgs(const boost::smatch& matches) {
FFlag ffs = {0};
std::string fmt_number, fmt_type, fmt_length;
fmt_number = matches[1];
fmt_type = matches[2];
fmt_length = matches[3];
ffs.type = fmt_type.c_str()[0];
ffs.number = determineOccurence(fmt_number);
ffs.length = determineOccurence(fmt_length);
return ffs;
}
//since the format string is CSV, split the string into tokens
//and then, validate the tokens by attempting to match them
//to the grammar (implemented as a simple regex). If the number of
//validations match, everything went well: return true. Otherwise:
//return false.
bool FormatParser::validateFmtString() {
boost::char_separator<char> sep(",");
bst_tokenizer tokens(mFmtStr, sep);
mFmt = FFlag();
size_t n_tokens = 0;
std::string token;
for(bst_tokenizer::const_iterator it = tokens.begin(); it != tokens.end(); ++it) {
token = *it;
boost::trim(token);
//this "grammar" is based on the Fortran Format Flag Specification
std::string rgx = "(\\d{0,8})([[:alpha:]])(\\d{0,8})";
boost::regex re(rgx);
boost::smatch matches;
if (boost::regex_match(token, matches, re, boost::match_extra)) {
mFmt = setFortranFmtArgs(matches);
mFortranVars.push_back(mFmt);
}
++n_tokens;
}
return mFortranVars.size() != n_tokens ? false : true;
}
//Now, parse each input line from a file and try to parse and store
//those variables into their associated containers.
void FormatParser::parseAndStore(const std::string& line) {
int offset = 0;
int integer = 0;
std::string varData;
std::vector<int> intData;
std::vector<std::string> charData;
offset = 0;
for (std::vector<FFlag>::const_iterator begin = mFortranVars.begin(); begin != mFortranVars.end(); ++begin) {
mFmt = *begin;
for (size_t i = 0; i < mFmt.number; offset += mFmt.length, ++i) {
varData = line.substr(offset, mFmt.length);
//now store the data, based on type:
switch(mFmt.type) {
case 'X':
break;
case 'A':
charData.push_back(varData);
break;
case 'I':
integer = atoi(varData.c_str());
intData.push_back(integer);
break;
default:
std::cerr << "Invalid type!\n";
}
}
}
mIntData.push_back(intData);
mCharData.push_back(charData);
}
//Open the input file, and attempt to parse the input file line-by-line.
void FormatParser::storeData() {
mFmt = FFlag();
std::ifstream ifile(mFilename.c_str(), std::ios::in);
std::string line;
if (ifile.is_open()) {
while(std::getline(ifile, line)) {
parseAndStore(line);
}
}
else {
std::cerr << "Error opening input file!\n";
exit(3);
}
}
//If character flags are set, this function will print the character data
//found, line-by-line
void FormatParser::printCharData() {
vvstr::const_iterator it = mCharData.begin();
vstr::const_iterator jt;
size_t linenum = 1;
std::cout << "\nCHARACTER DATA:\n";
for (; it != mCharData.end(); ++it) {
std::cout << "LINE " << linenum << " : ";
for (jt = it->begin(); jt != it->end(); ++jt) {
std::cout << *jt << " ";
}
++linenum;
std::cout << "\n";
}
}
//If integer flags are set, this function will print all the integer data
//found, line-by-line
void FormatParser::printIntData() {
vvint::const_iterator it = mIntData.begin();
vint::const_iterator jt;
size_t linenum = 1;
std::cout << "\nINT DATA:\n";
for (; it != mIntData.end(); ++it) {
std::cout << "LINE " << linenum << " : ";
for (jt = it->begin(); jt != it->end(); ++jt) {
std::cout << *jt << " ";
}
++linenum;
std::cout << "\n";
}
}
//Attempt to parse the input file, by first validating the format string
//and then, storing the data accordingly
void FormatParser::parse() {
if (!validateFmtString()) {
std::cerr << "Error parsing the input format string!\n";
exit(2);
}
else {
storeData();
}
}
int main(int argc, char **argv) {
if (argc < 3 || argc > 3) {
std::cerr << "Usage: " << argv[0] << "\t<Fortran Format Specifier(s)>\t<Filename>\n";
exit(1);
}
else {
//parse and print stuff here
FormatParser parser(argv[1], argv[2]);
parser.parse();
//print the data parsed (if any)
parser.printIntData();
parser.printCharData();
}
return 0;
}
这是标准的c ++ 98代码,可以按如下方式编译:
g++ -Wall -std=c++98 -pedantic fortran_format_parser.cpp -lboost_regex
奖金
这个基本的解析器也适用于Characters
(Fortran格式标记'A',最多8个字符)。您可以通过编辑正则表达式并对类型一起执行的捕获字符串长度检查来扩展它以支持您可能喜欢的任何标记。
可能的改进
如果您可以使用C ++ 11,则可以在某些地方使用lambdas
,并将auto
替换为迭代器。
如果这是在有限的内存空间中运行,并且你必须解析一个大文件,由于vectors
内部管理内存的方式,向量将不可避免地崩溃。最好使用deques
代替。有关详细信息,请参阅此处讨论的内容:
http://www.gotw.ca/gotw/054.htm
并且,如果输入文件很大,并且文件I / O是瓶颈,则可以通过修改ifstream
缓冲区的大小来提高性能:
How to get IOStream to perform better?
讨论
您将注意到:您要解析的类型必须在运行时知道,并且类声明和定义中必须支持任何关联的存储容器。
正如您所想,支持一个主要类中的所有类型效率不高。但是,由于这是 天真 解决方案,改进的完整解决方案可以专门用于支持这些案例。
另一个建议是使用Boost::Spirit。但是,由于Spirit使用了大量模板,因此当错误发生时,调试此类应用程序不适合胆小的人。
性能
与@Jonathan Dursi's solution相比,此解决方案很慢:
对于使用相同行格式(“3I2,3X,I3”)的10,000,000行随机生成的输出(124MiB文件):
#include <fstream>
#include <cstdlib>
#include <ctime>
using namespace std;
int main(int argc, char **argv) {
srand(time(NULL));
if (argc < 2 || argc > 2) {
printf("Invalid usage! Use as follows:\t<Program>\t<Output Filename>\n");
exit(1);
}
ofstream ofile(argv[1], ios::out);
if (ofile.is_open()) {
for (int i = 0; i < 10000000; ++i) {
ofile << (rand() % (99-10+1) + 10) << (rand() % (99-10+1) + 10) << (rand() % (99-10+1)+10) << "---" << (rand() % (999-100+1) + 100) << endl;
}
}
ofile.close();
return 0;
}
我的解决方案:
0m13.082s
0m13.107s
0m12.793s
0m12.851s
0m12.801s
0m12.968s
0m12.952s
0m12.886s
0m13.138s
0m12.882s
计算12.946s
Jonathan Dursi的解决方案:
0m4.698s
0m4.650s
0m4.690s
0m4.675s
0m4.682s
0m4.681s
0m4.698s
0m4.675s
0m4.695s
0m4.696s
Blazes的平均壁钟时间为4.684s
在O2上,他的速度比我的快至少270%。
但是,因为每次要解析附加格式标志时都不必实际修改源代码,所以此解决方案更加优化。
注意: 您可以实施一个涉及sscanf
/ streams
的解决方案,该解决方案只需要您知道您希望阅读的变量类型(非常类似)但是,额外的检查,例如验证类型会增加开发时间。(这就是为什么我在Boost中提供我的解决方案,因为标记器和正则表达式的便利性 - 这使得开发处理更容易)。
参考
http://www.boost.org/doc/libs/1_34_1/libs/regex/doc/character_class_names.html
答案 2 :(得分:5)
您可以使用scanf格式翻译3I2, 3X, I3
。
答案 3 :(得分:5)
鉴于Fortran很容易从C语言中调用,你可以编写一些Fortran函数来“本机化”。毕竟,Fortran READ函数采用您描述的格式字符串。
如果你想让它工作,你需要在Fortran上稍微简化一下(http://docs.oracle.com/cd/E19957-01/806-3593/2_io.html),并学习如何使用编译器链接Fortran和C ++。以下是一些提示:
extern "C" {}
范围内。答案 4 :(得分:3)
如果您的用户实际上应该以Fortran格式输入它,或者如果您很快适应或编写Fortran代码来执行此操作,我会像John Zwinck和M.S.B那样做。建议。只需编写一个简短的Fortran例程将数据读入数组,并使用“bind(c)”和ISO_C_BINDING类型来设置接口。请记住,数组索引将在Fortran和C ++之间发生变化。
否则,我建议使用scanf,如上所述:
http://en.cppreference.com/w/cpp/io/c/fscanf
如果您不知道需要阅读的每行项目数,您可以改为使用vscanf:
http://en.cppreference.com/w/cpp/io/c/vfscanf
然而,虽然它看起来很方便,但我从未使用过它,所以YMMV。
答案 5 :(得分:2)
今天想到这一点,但没时间写一个例子。 @ jrd1的示例和分析正在进行中,但我试图使解析更加模块化和面向对象。格式字符串解析器可以构建一个项目解析器列表,然后或多或少地独立工作,允许添加新的像浮点数而不改变旧代码。我认为一个特别好的界面是用格式字符串初始化的iomanip,这样ui就像
cin >> f77format("3I2, 3X, I3") >> a >> b >> c >> d;
在实现上我有f77format解析位并按组件构建解析器,因此它将创建3个固定宽度的int解析器,devNull解析器和另一个固定宽度解析器,然后使用输入。
当然,如果你想支持所有的编辑描述符,那将是一项重大工作!通常,它不仅仅是将字符串的其余部分传递给下一个解析器,因为存在需要重新读取该行的编辑描述符。