LINUX.ORG.RU

Парсинг параметров из txt файла C++

 


0

3

Доброго дня всем. Можете пожалуйста подсказать, помочь, направить на путь истинный так сказать. Столкнулся с проблемой, вроде бы как в весьма тривиальной задачке. Это всё разумеется из-за отсутствия знаний, которые стремлюсь улучшить. В общем, есть файл “file.txt”, в нем примерно следующего типа строки: Nik = CK Eth = 1500

И мне нужно найти нужную мне строку и оттуда захватить значение (например, на что равен Nik).

На текущий момент я реализовал через ifstream открытие файла, при помощи getline в цикле я из файла в string закидываю. Как вариант решение предполагал следующее: string через push_back закинуть в vector, потом в векторе каждую строку разделить до “=” и проверять на совпадение (через std::find???) и если совпадение есть, то захватить то, что идёт после “=” (как именно??). Правильное ли это решение? Можете помочь как лучше всего это сделать?

Ответ на: комментарий от paramon

Тогда уж как-то так:

// trim from start
static inline std::string &ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
    return s;
}

// trim from end
static inline std::string &rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
    return s;
}

// trim from both ends
static inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}

//....

std::string key, value;
std::getline(std::cin, key, '=');
std::getline(std::cin, value);
trim(key);
trim(value);
pon4ik ★★★★★ ()

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

Почему решение с разбором прямо из потока лучше? Потому, что там уже буферизованный ввод, этот буфер и служит вместо вектора, ты хочешь буферизовать ещё раз, да ещё и разбор двухпроходный сделать, то, что можно просто сразу разобрать. Более того - с std::cin можно работать как с контейнером, т.е. хоть токенизировать его при желании, см. istream_iterator.

pon4ik ★★★★★ ()
Ответ на: комментарий от pon4ik

код прямо со стековерфлоу? строка с откинутыми пробелами - это string_view у которого начало поставлено туда где не-пробелы начинаются, а конец поставлен туда где не-пробелы заканчиваются.

anonymous ()
Ответ на: комментарий от anonymous

лаконичный и чистый си

Абсолютно непрактичное решение должен заметить. У дело даже не в buffer overruns (если софтинка для внутреннего пользования и конфиг полностью под контролем то это даже не так страшно). А вот любой лишний пробел или табуляция вместо пробела и привет, замучаетесь ловить потом. Плюс это ни на йоту Вас не приближает к самому главному - привязке ключа к конкретной переменной и ее типу. Да и относительно медленное оно (pattern matching на каждой строке как ни крути). Я ОП отвечу как было бы правильнее.

bugfixer ()

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

Вам тут некоторые товарищи насоветовали много чего, типа

Google regex

boost::program_options

Bison, flex

Так вот, не нужно Вам ничего этого (последний совет так вообще шедеврален «я щитаю»). Из пушки по воробьям. Ничего Вам эта универсальность не даст кроме потери времени, раздувания кода, лишних зависимостей и замедления в runtime.

Формат тоже выбран неудачно, если Вам хватает

key1 value1
key2 value2
...

то зачем все эти излишества с дополнительными знаками равенства итд?

Не бойтесь писать ручками - ничего в этом плохого и сложного нет. Типичный парсинг выглядел бы примерно так:

   std::ifstream ifs("myconfigfile.txt");
   std::string line;
   while (getline(ifs, line)) {
      // skip empty lines and comments
      if (line.empty() || *line.data() == '#')
         continue;
      // parse line
      std::istringstream tokens(line);
      std::string key;
      if (!(tokens >> key)) {
         // warning of some kind
         continue;
      }
      if (key == "key1") {
         // процессим остаток строки так
      }
      else if (key == "key2") {
         // процессим остаток строки сяк
      }
      ...
      else {
         // warning of some kind
      }
}

Потом уже, если ключей станет много, можно будет рассмотреть std::map key -> handler, а по началу и linear search по key хватит (плюс что то мне подсказывает что пока Вам в это лезть рановато). В какой то момент возможно появится валидация (как отдельных параметров, так и их комбинаций), итд.

Всегда старайтесь чтобы решение было максимально простым из покрывающих Ваши потребности на данный момент. Усложнится задача - тогда и усложните решение. В конце концов Вы наверняка парсите свой конфиг один раз на старте и всё, и там всего с десяток параметров. Даже если это делается не очень оптимально - простота важнее. Гораздо более существенную часть времени Вы должны тратить на последующую обработку (программа которая только парсит конфиги представляет весьма сомнительную практическую ценность), и я уверен там оптимизировать можно и нужно будет гораздо больше всего.

Удачи в постижении нового :)

bugfixer ()
Ответ на: комментарий от fsb4000

Это что за наркомания?

Молодежь…

front() на non-const string в COW имплементации приведет к дублированию underlying buffer если он случился shared. То же касается operator[]. c_str() потенциально будет писать 0 в конец. data() ничем из вышеприведенного не страдает.

bugfixer ()
Ответ на: комментарий от PRN

Это ещё и работать будет не так как автор задумал с ведущими пробелами

Оно работает именно так как задумал автор и покрывает ‘#’ исключительно в первом символе строки. Хотелось бы отрезать leading spaces я бы это сделал. Но это было бы хорошим примером излишнего усложнения. И комментарии то покрывать было не обязательно, но слишком уж дешев этот «or» (во всех смыслах) что удержаться трудно.

bugfixer ()
Ответ на: комментарий от PRN

Хотелось бы отрезать leading spaces я бы это сделал.

ПыСы. При необходимости leading spaces покрываются одним единственным подобным «if» сразу после вычитывания ключа. При этом пред-проверку лучше оставить так как она позволяет избежать создания относительно «тяжёлого» istringstream когда он гарантировано не нужен. Это как я бы это делал. А так конечно да - существует множество способов различной степени извращённости как добиться того же результата, но приведённый мне представляется наиболее простым и оптимальным.

bugfixer ()
Ответ на: комментарий от bugfixer

Оно работает именно так как задумал автор

Тогда автор задумал, какую-то дичь. «Закомментированные» строки с ведущими пробелами будут обрабатываться в блоке

// warning of some kind

И если # поставить не в начале, надо снова править конфиг.

PRN ()
Ответ на: комментарий от bugfixer

ПыСы. При необходимости leading spaces покрываются одним единственным подобным «if» сразу после вычитывания ключа. При этом пред-проверку лучше оставить так как она позволяет избежать создания относительно «тяжёлого» istringstream когда он гарантировано не нужен. Это как я бы это делал. А так конечно да - существует множество способов различной степени извращённости как добиться того же результата, но приведённый мне представляется наиболее простым и оптимальным.

Простое, оптимальное и работающее решение это:

- if (!(tokens >> key)) {
+ if (!(tokens >> std::skipws >> key)) {

и проверку ниже перенести, а не все вышеописанное. А еще лучше, специализированную либу заюзать и не изобретать велосипедов с квадратными колесами.

PRN ()
Последнее исправление: PRN (всего исправлений: 1)
Ответ на: комментарий от PRN

Простое, оптимальное и работающее решение это …

Читаем https://en.cppreference.com/w/cpp/io/manip/skipws, особенно вот эту часть:

Enables or disables skipping of leading whitespace by the formatted input functions (enabled by default).

Понимаем что ваше изменение это nop, просто буковок добавили и несколько CPU cycles потратили впустую.

и проверку ниже перенести

Я предложил добавить и только если хочется иметь возможность начинать коменты не с первого символа строки. Какова вероятность таких? А не создавать istringstream когда он гарантированно не нужен - мелочь а приятно.

и не изобретать велосипедов с квадратными колесами

«квадратности», при всем уважении, не вижу.

bugfixer ()
Ответ на: комментарий от PRN

Насколько помню, COW для std::string не совместима со стандартом.

Так и есть, начиная с С++11 (емнип).

Все эти потуги излишни.

Открою Вам тайну: есть программные продукты (довольно серьёзные) которые закладываются на то что стринги COW. В том числе и из-за этого в GNU libstdc++ до сих пор ещё есть возможность зафорсить старый ABI (см _GLIBCXX_USE_CXX11_ABI). А некоторые конторы вообще побыстрячку написали свои собственные COW strings, во избежание сюрпризов так сказать…

Ну и так же хотелось бы чтобы Вы продемонстрировали мне 2 вещи:

  • где я потратил ну так прям уж невероятно много усилий?
  • в каких ситуациях (любой компилятор, стандарт итд) предложенный код приведет к менее лаконичному и быстрому asm’у?

А до тех пор позволю себе остаться при своём - не потратив ничего я получил код который никогда не медленнее (а в некоторых ситуациях и существенно быстрее) чем возможные альтернативы. Да и еще и не выходит за рамки C++98. Профит.

bugfixer ()
Ответ на: комментарий от bugfixer

Понимаем что ваше изменение это nop, просто буковок добавили и несколько CPU cycles потратили впустую.

Таки да, есть такое.

«квадратности», при всем уважении, не вижу.

Квадратность будет в месте, где стоят комментарии. При использовании библиотек это работает «из коробки»

PRN ()
Ответ на: комментарий от bugfixer

Открою Вам тайну …

Не надо раскрывать корпоративных секретов))

А некоторые конторы вообще побыстрячку написали свои собственные COW strings, во избежание сюрпризов так сказать

И при использовании их вместо std::string и стандарте выше 11, их может ожидать большой сюрприз, удачного им дебага.

где я потратил ну так прям уж невероятно много усилий?

Где я писал про невероятное количество усилий?

в каких ситуациях (любой компилятор, стандарт итд) предложенный код приведет к менее лаконичному и быстрому asm’у? …….

Мой поинт в том, что в данном контексте *line.data() не дает преимуществ перед, например, line.front()

Да и еще и не выходит за рамки C++98

На дворе 2020, с выпущенным C++20, пора уже закопать стюардессу.

PRN ()
Ответ на: комментарий от PRN

Квадратность будет в месте, где стоят комментарии.

Вы преувеличиваете. Ну завернется всё в какие нибудь

template<typename T>
void parseValue(std::istream&, T&);

void parseValue(std::istream&, MyCustomType&);

делов то…

При использовании библиотек это работает «из коробки»

И опять преувеличиваете - писать придется не меньше. Либо придется обегать какое нибудь DOM-like tree которое выплюнет сторонний парсер, либо писать SAX-like handlers, либо долго разбираться с доками на тему как привязать конкретные переменные к конкретным ключам. Быстрее в runtime точно не будет. За универсальность нужно платить. А главное - ради чего все эти усилия?

bugfixer ()
Ответ на: комментарий от PRN

Мой поинт в том, что в данном контексте *line.data() не дает преимуществ перед, например, line.front()

Не проигрывает точно (даже буковок ровно столько же). Когда выигрывает - я уже писал.

На дворе 2020, с выпущенным C++20, пора уже закопать стюардессу.

Если у Вас есть codebase который измеряется десятками MLOC Вы будете очень и очень консервативны в вопросах выбора компилятора, перевода билдов на новые стандарты etc. Безглючного софта не существует (включая компиляторы и Ваш собственный софт). Каждый такой upgrade может регреснуть что нибудь, и хорошо если Вы это заметите до того так оно в прод выкатиться… Это не означает что нужно приклеиться к С++98 и всё, но «на острие прогресса» Вы точно быть не захотите. Это я к тому что причин выходить за рамки более старого стандарта в каждом конкретном случае нет, если более новый не дает преимущества в скорости в runtime, maintainability etc.

bugfixer ()
Ответ на: комментарий от bugfixer

Если у Вас есть codebase который измеряется десятками MLOC Вы будете очень и очень консервативны в вопросах выбора компилятора, перевода билдов на новые стандарты etc. Безглючного софта не существует (включая компиляторы и Ваш собственный софт). Каждый такой upgrade может регреснуть что нибудь, и хорошо если Вы это заметите до того так оно в прод выкатиться…

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

Старые компиляторы нужны только в одном случае, если нужна поддержка старых операционок, а в новых версиях она удалена. (aka gcc 9.2 вместо 10.2 если нужна поддержка Windows XP, https://jmeubank.github.io/tdm-gcc/)

Плюс у проекта есть тесты, десятки тысяч тестов для больших проектов, которые дают уверенность в любом переезде. Если нет уверенности, то пишите больше тестов.

fsb4000 ★★★★ ()
Ответ на: комментарий от fsb4000

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

Старые баги они такие - свои родные и более менее известные (из тех что конторой уже хитались). А вот новые - реально могут выстрелить. Причём не только баги компилятора, а всякие UB и sleepers в своём коде. Если бы Вы простой оплачивали из своего кармана - Вы бы на такой риск пошли без веской причины?

Старые компиляторы нужны только в одном случае, если нужна поддержка старых операционок.

Очень сильное утверждение, позволю себе не согласиться.

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

Да да, в идеальном мире оно конечно же именно так.

Если нет уверенности, то пишите больше тестов.

Стараемся как можем, всё покрыть невозможно.

bugfixer ()
Ответ на: комментарий от fsb4000

ПыСы

Мега тупняк

Немного грубовато вышло, ну да ладно, оно такое…

Плюс у проекта есть тесты, десятки тысяч тестов для больших проектов

Признайтесь (только честно!)

  • сколько Вам лет (сколько лет Вы в индустрии наверное даже более интересно)
  • самый большой проект с которым лично Вам приходилось иметь дело
  • сколько этот проект заработал
  • сколько (по порядку величины) строк кода вы написали сами

?

Только не надо преувеличивать, ладно? Все свои…

bugfixer ()