Алгоритм парсинга строки для нахождения определенного поля и его значения
У меня есть вот такой код:
int main(int argc, char *argv[])
{
Client client;
client.setAddr("192.168.0.16");
client.setPass("testPassword");
client.setName("testHostName");
if (client.init()) {
std::string cpuInfo = client.getCpuInfo();
// Do something ...
client.term();
}
return EXIT_SUCCESS;
}
Значение cpuInfo:
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 43 bits physical, 48 bits virtual
CPU(s): 12
On-line CPU(s) list: 0-11
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
NUMA node(s): 1
Vendor ID: AuthenticAMD
CPU family: 23
Model: 1
Model name: AMD Ryzen 5 1600 Six-Core Processor
Stepping: 1
CPU MHz: 2482.474
CPU max MHz: 3200.0000
CPU min MHz: 1550.0000
BogoMIPS: 6387.25
Virtualization: AMD-V
L1d cache: 32K
L1i cache: 64K
L2 cache: 512K
L3 cache: 8192K
NUMA node0 CPU(s): 0-11
После получения информации о процессоре, мне нужно будет сохранить ее по частям в базу данных. И тут появляется проблема.
Какой вариант является самым быстрым и удобным, для парсинга строки. То и есть, если я хочу получить значения поля CPU MHz, как мне лучше всего получить это значение. Известно, что колонки слева - константные.
Первоначально, у меня было что то подобное:
std::istringstream stream(cpuInfo);
std::string word;
while (stream >> word) {
if (word == "CPU") {
stream >> word;
if (word == "MHz") {
std::string res;
stream >> res;
// Do something ...
}
}
}
Но я уверен, что это не самое лучшее решение, да и для парсинга всех полей, займет очень много времени и места.
Ответы (4 шт):
Можно воспользоваться регулярными выражениями. Вот пример:
#include <iostream>
#include <regex>
using namespace std;
const std::string CpuInfo =
"Architecture: x86_64\n"
"CPU op-mode(s): 32-bit, 64-bit\n"
"Byte Order: Little Endian\n"
"Address sizes: 43 bits physical, 48 bits virtual\n"
"CPU(s): 12\n"
"On-line CPU(s) list: 0-11\n"
"Thread(s) per core: 2\n"
"Core(s) per socket: 6\n"
"Socket(s): 1\n"
"NUMA node(s): 1\n"
"Vendor ID: AuthenticAMD\n"
"CPU family: 23\n"
"Model: 1\n"
"Model name: AMD Ryzen 5 1600 Six-Core Processor\n"
"Stepping: 1\n"
"CPU MHz: 2482.474\n"
"CPU max MHz: 3200.0000\n"
"CPU min MHz: 1550.0000\n"
"BogoMIPS: 6387.25\n"
"Virtualization: AMD-V\n"
"L1d cache: 32K\n"
"L1i cache: 64K\n"
"L2 cache: 512K\n"
"L3 cache: 8192K\n"
"NUMA node0 CPU(s): 0-11";
int main() {
std::regex reg_ex("CPU MHz:\\s+([^\\n]+)\\n?");
std::smatch reg_match;
std::string result = (std::regex_search(CpuInfo, reg_match, reg_ex)) ?
reg_match[1] : std::string("Not found!");
std::cout << "CPU MHz: " << result << std::endl;
return 0;
}
Найдет и выведет: CPU MHz: 2482.474
EDIT: Регулярку поправил, чтобы захватывались значения с пробелами, типа AMD Ryzen 5 1600 Six-Core Processor.
Можно использовать std::regex_iterator чтобы спарсить все поля/значения и поместить их в словарь типа std::unordered_map.
#include <iostream>
#include <iterator>
#include <functional>
#include <regex>
#include <unordered_map>
using namespace std;
int main()
{
const auto cpu_info = string(R"__(Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
Address sizes: 43 bits physical, 48 bits virtual
CPU(s): 12
On-line CPU(s) list: 0-11
Thread(s) per core: 2
Core(s) per socket: 6
Socket(s): 1
NUMA node(s): 1
Vendor ID: AuthenticAMD
CPU family: 23
Model: 1
Model name: AMD Ryzen 5 1600 Six-Core Processor
Stepping: 1
CPU MHz: 2482.474
CPU max MHz: 3200.0000
CPU min MHz: 1550.0000
BogoMIPS: 6387.25
Virtualization: AMD-V
L1d cache: 32K
L1i cache: 64K
L2 cache: 512K
L3 cache: 8192K
NUMA node0 CPU(s): 0-11
)__");
const auto re = regex(R"__(^([^:]+):\s*([^\r\n]+)$)__");
auto data = unordered_map<string, string>();
transform(sregex_iterator(begin(cpu_info), end(cpu_info), re),
sregex_iterator(),
inserter(data, end(data)),
[](const sregex_iterator::value_type& m) -> pair<string, string> {
cout << "parsed '" << m[1].str() << "' = '" << m[2].str() << "'\n";
return make_pair(m[1].str(), m[2].str());
});
cout << "CPU MHz = " << data["CPU MHz"] << endl;
return 0;
}
Если написать свой insert_iterator, то можно сохранять значения сразу в БД.
Вариантов много. Вот несколько, практичных решение, которые можно использовать в тех или иных случаях:
Построчный парсинг потоком. По сути всё как и в вопросе, но читать текст не словами, а строками. Это упрощает логику:
void foo() { std::istringstream stream(cpuInfo); std::string line; while (stream.getline(line)) { if (line.rfind("CPU MHz:", 0) == 0) { // сравнение наяала строки double mhz = getValue<double>(line.c_str() + sizeof ("CPU MHz:") -1); // do something } else if (line.rfind("Vendor ID:", 0) == 0) { std::string vendor = getValue<std::string>(line.c_str() + sizeof ("CPU MHz:") -1); // do something else } } // ... } template<typename T> T getValue(const char *str) { T rv; std::strstream valueStream(str); rv << valueStream; if(valueStream.fail()) throw std::runtime_error("parse failed"); return rv; } template<std::string> std::string getValue(const char *str) { return trim(str); // Обрезать пробелы. }Если нужно достать конкретное значение, то можно воспользоваться регулярным выражением:
std::regex re("CPU MHz:\\s*(\\S+)\\s*\n"); std::smatch re_match; if (std::regex_search (CpuInfo, re_match, re)) { double mhz = getValue<double>(re_match[1]); } else { throw std::runtime_error("parse failed"); }Или просто найти его вручную:
size_t mhzPos = cpuInfo.find("CPU MHz:"); if (n != std::string::npos)) { mhzPos += sizeof("CPU MHz:"); double mhz = getValue<double>(cpuInfo.c_str() + mhzPos); // do something } else { throw std::runtime_error("parse failed"); }Само собой, регулярками можно парсить и построчно:
std::istringstream stream(cpuInfo); std::string line; while (stream.getline(line)) { std::smatch re_match; if (std::regex_search (line,std::regex("CPU MHz:\\s*(\\S+)\\s*"), re_match)) { double mhz = getValue<double>(re_match[1]); // do something } else if (std::regex_search (line,std::regex("Vendor ID:\\s*(.*\\S)\\s*"), re_match)) { std::string vendor = getValue<std::string>(line.c_str() + sizeof ("CPU MHz:") -1); // do something else } }Замечание: здесь нужно убедится, что компилятор додумается не перекомпилировать регулярные выражения на каждой итерации. В противном случае это может вызвать значительны оверхед.
Если нужно распарсить все или почти все значения, то т.к. текст представляет собой формат вида ключ-значение, то довольно естественным будет создать промежуточный словарь:
std::unordered_map<std::string, std::string> cpuInfoHash; std::istringstream stream(cpuInfo); std::string line; while (stream.getline(line)) { size_t sepPos = line.find(':'); if (n != std::string::npos)) { std::string key = line.substr(0, sepPos); std::string val = trim(line.substr(sepPos+1)); cpuInfoHash.emplace(key, value); } } double mhz = getValue<double>(cpuInfoHash.at("CPU MHz")); std::string vendor = cpuInfoHashat.at("Vendor ID"); // ^ осторожно, at() кидается исключениями
Общие замечания
- Этот списов вариантов не исчерпывающий. Это базовые варианты, пришедшие на ум.
- Использование потоков для парсинга числовых значений из строки — хорошая практика т.к. позволяет использовать шаблоны, добится универсальности при обработке пробелов и т.п., а также позволяет обрабатывать ошибки.
- Набор функций по обработке строк в C++ из коробки, объективно говоря, довольно ущербный. Так что, если используешь только stl, то не стоит чураться писать свои велосипеды для элементарных операций вроде
trim(). - В C++17 операции с голыми сишными строками выше стоит обернуть в
std::string_view.
Не могу оставить комментарий, поэтому напишу в ответы:
@bbdd, если скорость действительно важна и решение с регулярными выражениями подходит, то нужно помнить, что std::regex работает очень и очень медленно по сравнению с re2 или же библиотекой от boost.