парсинг — перебор текстового файла с использованием фортрановского формата в переполнении стека

Я делаю приложение, которое имеет дело с данными TXT-файла.

Идея заключается в том, что TXT-файлы могут иметь разные форматы, и их следует читать на C ++.

Одним из примеров может быть 3I2, 3X, I3, что должно быть сделано как: «сначала у нас есть 3 целых числа длины 2, затем у нас есть 3 пустых пятна, затем у нас есть 1 целое число длины 3.

Лучше всего перебирать файл, получая строки, а затем перебирать строки как строки? Каков будет эффективный подход для умного повторения, оставляя три точки, которые следует игнорировать?

Например.

101112---100
102113---101
103114---102

чтобы:

10, 11, 12, 100
10, 21, 13, 101
10, 31, 14, 102

8

Решение

Ссылка, данная Кайлом Каносом, является хорошей; Строки формата * scanf / * printf довольно хорошо отображаются на строки формата fortran. На самом деле это проще сделать с помощью ввода-вывода в стиле C, но также возможно использование потоков в стиле 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 — 3 копии %2d (десятичное целое число ширины 2) с последующим %3s (строка ширины 3, которую мы читаем в переменную, которую мы никогда не используем) с последующим %3d (десятичное целое число ширины 3).

8

Другие решения

Учитывая, что вы хотите динамический анализ флагов спецификатора формата Fortran, Вы должны отметить, что: Вы сразу вошли в царство парсеров.

В дополнение к другим методам парсинга такие входные данные, которые другие отметили здесь:

  • Используя привязки Fortran и CC / ++ для выполнения анализа за вас.
  • Используя чистый C ++, чтобы разобрать его для вас, написав синтаксический анализатор, используя комбинацию:
    • sscanf
    • streams

Мое предложение таково, что если увеличение доступен для вас, вы можете использовать его для реализации простого парсера для оперативных операций с использованием комбинации контейнеров Regexes и STL.

Из того, что вы описали, и что показано в разных местах, Вы можете построить наивную реализацию грамматики Вы хотите поддерживать, используя регулярные выражения:

(\\d{0,8})([[:alpha:]])(\\d{0,8})
  1. Где первая группа число этого типа переменной.
  2. Второе — это тип переменной.
  3. и третий это длина переменного типа.

С помощью эта ссылка для флагов спецификатора формата Fortran, Вы можете реализовать наивный Решение, как показано ниже:

#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 Format Flag ‘A’, до 8 символов). Вы можете расширить это, чтобы поддерживать любые флаги, которые вам нравятся, отредактировав регулярное выражение и выполнив проверку длины захваченных строк в тандеме с типом.

ВОЗМОЖНЫЕ УЛУЧШЕНИЯ

Если вам доступен C ++ 11, вы можете использовать lambdas в некоторых местах и ​​заменить auto для итераторов.

Если это выполняется в ограниченном пространстве памяти, и вам нужно проанализировать большой файл, векторы неизбежно вылетят из-за того, как vectors управляет памятью изнутри. Будет лучше использовать deques вместо. Подробнее об этом смотрите здесь как обсуждено:

http://www.gotw.ca/gotw/054.htm

И, если входной файл большой, а файловый ввод / вывод является узким местом, вы можете улучшить производительность, изменив размер ifstream буфер:

Как заставить IOStream работать лучше?

ОБСУЖДЕНИЕ

Что вы заметите, так это: типы, которые вы анализируете, должны быть известны во время выполнения, и любые связанные контейнеры хранения должны поддерживаться в объявлении и определении класса.

Как вы можете себе представить, поддержка всех типов в одном основном классе неэффективна. Тем не менее, так как это наивный Решение, улучшенное полное решение может быть специализированным для поддержки этих случаев.

Другое предложение заключается в использовании Повысьте :: Дух. Но, поскольку Spirit использует множество шаблонов, отладка такого приложения Не для слабонервных когда ошибки могут возникать.

СПЕКТАКЛЬ

По сравнению с Решение @Джонатана Дурси, это решение медленное:

Для 10 000 000 строк случайно сгенерированного вывода (файл размером 124 МБ), использующего этот же формат строки («3I2, 3X, I3»):

#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

Решение Джонатана Дурси:

0m4.698s
0m4.650s
0m4.690s
0m4.675s
0m4.682s
0m4.681s
0m4.698s
0m4.675s
0m4.695s
0m4.696s

Блейз со средним временем 4.684s

Его быстрее чем у меня как минимум на 270% с обоими на O2.

тем не мение, так как вам не нужно фактически изменять исходный код каждый раз, когда вы хотите проанализировать дополнительный флаг формата, тогда это решение является более оптимальным.

Замечания: Вы можете реализовать решение, которое включает в себя sscanf / streams это требует только того, чтобы вы знали, какой тип переменной вы хотите прочитать (очень похоже на мою), но дополнительные проверки, такие как проверка типа (ов), увеличивают время разработки. (Вот почему я предлагаю свое решение в Boost, из-за удобства токенизаторов и регулярных выражений — что облегчает процесс разработки).

РЕКОМЕНДАЦИИ

http://www.boost.org/doc/libs/1_34_1/libs/regex/doc/character_class_names.html

6

Вы могли бы перевести 3I2, 3X, I3 в формате scanf.

5

Учитывая, что Fortran легко вызывается из C, вы могли бы написать небольшую функцию Fortran, чтобы сделать это «изначально». В конце концов, функция Fortran READ принимает строку формата, которую вы описываете.

Если вы хотите, чтобы это работало, вам нужно немного освежить фортран (http://docs.oracle.com/cd/E19957-01/806-3593/2_io.html), а также узнайте, как связать Fortran и C ++ с помощью вашего компилятора. Вот несколько советов:

  • Символы Фортрана могут неявно дополняться символом подчеркивания, поэтому MYFUNC может вызываться из C как myfunc_ ().
  • Многомерные массивы имеют противоположный порядок размеров.
  • Объявление функции Fortran (или C) в заголовке C ++ требует помещения ее в extern "C" {} объем.
5

Если ваш пользователь должен ввести его в формате Fortran, или если вы очень быстро адаптируете или пишете код Fortran для этого, я бы поступил так же, как John Zwinck и M.S.B. предложить. Просто напишите короткую подпрограмму на Фортране для чтения данных в массив и используйте типы «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.

3

Думал об этом сегодня, но не время писать пример. Пример и анализ @ jrd1 находятся на правильном пути, но я постараюсь сделать анализ более модульным и объектно-ориентированным. Синтаксический анализатор строки формата мог бы создать список анализаторов элементов, которые затем работали более или менее независимо, позволяя добавлять новые, например с плавающей запятой, без изменения старого кода. Я думаю, что особенно хорошим интерфейсом был бы iomanip, инициализированный строкой формата, чтобы пользовательский интерфейс был чем-то вроде

cin >> f77format("3I2, 3X, I3") >> a >> b >> c >> d;

При реализации я хотел бы, чтобы формат f77 анализировал биты и собирал синтаксический анализатор по компонентам, поэтому он создал бы 3 синтаксических анализатора с фиксированной шириной int, синтаксический анализатор devNull и другой синтаксический анализатор фиксированной ширины, который затем использовал бы входные данные.

Конечно, если вы хотите поддерживать все дескрипторы редактирования, это будет большая работа! И вообще это не будет просто передавать оставшуюся строку следующему парсеру, так как есть дескрипторы редактирования, которые требуют перечитывания строки.

2
По вопросам рекламы [email protected]