LINUX.ORG.RU

Парсинг с помощью std::regex

 , , , ,


0

4

Привет всем! Имеются строки вида

(1i8,3e20.9e3)
(3i8,6e16.9)
(19i8)
Хотелось бы уметь их парсить и получать на выходе список с описанием параметров. Запятыми производится разделение на подстроки, а подстроки в свою очередь имеют описание целого или числа с плавающей точкой. В описании числа с плавающей точкой указание размера дробной и экспоненциальной частей опционально. Прошу помочь разобраться, есть ли элегантный способ распарсить эти строки с помощью std::regexp или проще использовать парселку, написанную на коленке? На выходе хочу получить list<FormatDescription>, где FormatDescriprion примерно такой:
struct FormatDescriprion {
int count;
enum Type{INT, FLOAT}type;
int fieldWidth;
int fractionWidth;
int exponentionWidth;
};

Элегантно это будет выглядеть на пёрле, руби, жс или любом другом ЯП со встроенными регэкспами, но не на C++. Посему делай, как получится. А если хочешь, чтобы сделали за тебя, то надо постить в Job.

Virtuos86 ★★★★★
()

Регекспом не надо, замучаешься.

Никакой буст не нужен. Перл не нужен тем более.

Грамматика автоматная, элементарная, хоть на бейсике парси.

Сначала придумываешь стейты.

Типа, start, openParen, etc. То есть, некоторый enum такой. Хранишь текущее значение стейта где-то.

Потом, придумываешь с какого стейта какая буква ведет на какой следующий стейт, а какая на error. Типа, открывающая скобка в состоянии start переключает текущий стейт на openParen.

Дальше читаешь побуквенно, и выполняешь переходы.

По ходу дела записываешь числа в строки токенов. Все элементарно.

lovesan ★★
()

Это легко сделать и без регулярок.

Deleted
()

Если ты уж делаешь на c++, тебе наверное будет интересно, что парсинг в ручную или на какой нить грамматической либе типа boost::phoenix как минимум уже на разбиении строки на подстроки оказывается примерно в 5 раз быстрее чем скомпилированные регэкспы.

А если тебе это не интересно, то может и правда на пёрле проще, он как раз для этого и задумывался?:)

pon4ik ★★★★★
()

Пара примеров как примерно это сделал бы я.

Получилась простыня, ибо тег cut с тегом code похоже не работают.
Критика приветствуется.

STL без regex:

#include <cstring>
#include <iostream>
#include <list>
#include <string>
#include <vector>

using namespace std;

struct FormatDescriprion {
    string m_text;
    enum Type{UNKNOWN, INT, FLOAT} m_type;

    string text() const {
        return m_text;
    }

    string type() const
    {
        switch (m_type) {
        case INT:
            return "INT";
        case FLOAT:
            return "FLOAT";
        case UNKNOWN:
            return "UNKNOWN";
        }

        return "UNKNOWN";
    }

    FormatDescriprion(const string& data):
        m_text(data),
        m_type(UNKNOWN)
    {
        //TODO: implement parse data
        if (data.find("i") != string::npos) {
            m_type = INT;
        } else if (data.find(".") != string::npos) {
            m_type = FLOAT;
        }
    }
};

list<FormatDescriprion> parseLine(const string& line)
{
    list<FormatDescriprion> result;

    if (line.empty())
        return result;

    if (line.size() < 3 || line[0] != '(' || line[line.size() - 1] != ')') {
        cerr << "Invalid line format" << endl;
        return result;
    }

    string lineWithoutBracket = line.substr(1, line.size() - 2);
    char *token = strtok(const_cast<char*>(lineWithoutBracket.c_str()), ",");
    while (token != NULL) {
        result.push_back(FormatDescriprion(string(token)));
        token = strtok(NULL, ",");
    }

    return result;
}

int main(int argc, char *argv[])
{
    vector<string> lines = { "(1i8,3e20.9e3)", "(3i8,6e16.9)", "(19i8)" };
    vector<list<FormatDescriprion>> data;
    for (const string& line: lines) {
        data.push_back(parseLine(line));
    }

    int i = 1;
    for (const list<FormatDescriprion>& dataList: data) {
        cout << "Line #" << i << ": " << endl;
        ++i;
        for (const FormatDescriprion& data: dataList) {
            cout << "\t" << data.type() << ": " << data.text() << endl;
        }
    }
    return 0;
}
STL с regex:
#include <iostream>
#include <list>
#include <regex>
#include <string>
#include <vector>

using namespace std;

struct FormatDescriprion {
    string m_text;
    enum Type{UNKNOWN, INT, FLOAT} m_type;

    string text() const {
        return m_text;
    }

    string type() const
    {
        switch (m_type) {
        case INT:
            return "INT";
        case FLOAT:
            return "FLOAT";
        case UNKNOWN:
            return "UNKNOWN";
        }

        return "UNKNOWN";
    }

    FormatDescriprion(const string& data):
        m_text(data),
        m_type(UNKNOWN)
    {
        //TODO: implement parse data
        if (data.find("i") != string::npos) {
            m_type = INT;
        } else if (data.find(".") != string::npos) {
            m_type = FLOAT;
        }
    }
};

list<FormatDescriprion> parseLine(const string& line)
{
    list<FormatDescriprion> result;

    regex regEx("[\\die\\.]+");
    auto begin = sregex_iterator(line.cbegin(), line.cend(), regEx);
    auto end = sregex_iterator();
    for (sregex_iterator i = begin; i != end; ++i) {
        smatch match = *i;
        result.push_back(FormatDescriprion(match.str()));
    }

    return result;
}

int main()
{
    vector<string> lines = { "(1i8,3e20.9e3)", "(3i8,6e16.9)", "(19i8)" };
    vector<list<FormatDescriprion>> data;
    for (const string& line: lines) {
        data.push_back(parseLine(line));
    }

    int i = 1;
    for (const list<FormatDescriprion>& dataList: data) {
        cout << "Line #" << i << ": " << endl;
        ++i;
        for (const FormatDescriprion& data: dataList) {
            cout << "\t" << data.type() << ": " << data.text() << endl;
        }
    }
    return 0;
}
Qt без regex:
#include <QtCore>

struct FormatDescriprion {
    QString m_text;
    enum Type{UNKNOWN, INT, FLOAT} m_type;

    QString text() const {
        return m_text;
    }

    QString type() const
    {
        switch (m_type) {
        case INT:
            return "INT";
        case FLOAT:
            return "FLOAT";
        case UNKNOWN:
            return "UNKNOWN";
        }

        return "UNKNOWN";
    }

    FormatDescriprion(const QString& data):
        m_text(data),
        m_type(UNKNOWN)
    {
        //TODO: implement parse data
        if (data.contains("i")) {
            m_type = INT;
        } else if (data.contains(".")) {
            m_type = FLOAT;
        }
    }
};

QLinkedList<FormatDescriprion> parseLine(const QString& line)
{
    QLinkedList<FormatDescriprion> result;

    if (line.isEmpty())
        return result;

    if (line.size() < 3 || line[0] != '(' || line[line.size() - 1] != ')') {
        qInfo() << "Invalid line format";
        return result;
    }

    QStringList tokens = line.mid(1, line.size() -2).split(',');
    for (const QString& token: tokens) {
        result.push_back(FormatDescriprion(token));
    }

    return result;
}


int main(int argc, char *argv[])
{
    QVector<QString> lines = { "(1i8,3e20.9e3)", "(3i8,6e16.9)", "(19i8)" };
    QVector<QLinkedList<FormatDescriprion>> data;

    for (const QString& line: lines) {
        data.push_back(parseLine(line));
    }

    int i = 1;
    for (const QLinkedList<FormatDescriprion>& dataList: data) {
        qInfo() << QString("Line #%1").arg(i);
        ++i;
        for (const FormatDescriprion& data: dataList) {
            qInfo() << QString("    %1: %2").arg(data.type(), data.text());
        }
    }
    return 0;
}
Qt c regex:
#include <QtCore>
#include <QRegExp>

struct FormatDescriprion {
    QString m_text;
    enum Type{UNKNOWN, INT, FLOAT} m_type;

    QString text() const {
        return m_text;
    }

    QString type() const
    {
        switch (m_type) {
        case INT:
            return "INT";
        case FLOAT:
            return "FLOAT";
        case UNKNOWN:
            return "UNKNOWN";
        }

        return "UNKNOWN";
    }

    FormatDescriprion(const QString& data):
        m_text(data),
        m_type(UNKNOWN)
    {
        //TODO: implement parse data
        if (data.contains("i")) {
            m_type = INT;
        } else if (data.contains(".")) {
            m_type = FLOAT;
        }
    }
};

QLinkedList<FormatDescriprion> parseLine(const QString& line)
{
    QLinkedList<FormatDescriprion> result;

    QRegExp regEx("([\\die\\.]+)");
    int pos = 0;

    while ((pos = regEx.indexIn(line, pos)) != -1) {
        result.push_back(FormatDescriprion(regEx.cap(1)));
        pos += regEx.matchedLength();
    }

    return result;
}

int main(int argc, char *argv[])
{
    QVector<QString> lines = { "(1i8,3e20.9e3)", "(3i8,6e16.9)", "(19i8)" };
    QVector<QLinkedList<FormatDescriprion>> data;

    for (const QString& line: lines) {
        data.push_back(parseLine(line));
    }

    int i = 1;
    for (const QLinkedList<FormatDescriprion>& dataList: data) {
        qInfo() << QString("Line #%1").arg(i);
        ++i;
        for (const FormatDescriprion& data: dataList) {
            qInfo() << QString("    %1: %2").arg(data.type(), data.text());
        }
    }
    return 0;
}

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

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

Просто твоя хотелка пишется за 2 часа с >=99% coverage. Или за пол часа на колене, из фекалий и веток.

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

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

Если ты в своём <language name> юзаешь для таких задач регэкспы, то тебе, возможно, стоит задуматься о повышении квалификации? :)

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

А может, начинать оптимизировать regex в свой парсер стоит после того он станет узким местом? Впрочем, в данном конкретном случае руками не сильно сложнее чем regex.

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

я думал, что через regex кода будет меньше

Тут все дело в оптимизации. Если скорость не важна можно regex, если же скорость важна то лучше вручную(если знаешь что делаешь). Вот например в обоих моих примерах с regex он создается каждый раз при парсинге что плохо будет влиять на производительность. Если же сделать его static то уже будет гораздо быстрее так как regex сгенерируется один раз.

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

Скорость это последнее, что интересовало. Там 2-3 строки на файл приходится. Их положения я знаю.

heaven__12
() автор топика
Ответ на: комментарий от Iron_Bug

Не нужно же. Как выше уже сказали, полностью «ручной» вариант - лучшее решение

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

А когда же использовать regex?

В CLR есть System.Text.RegularExpressions.Compiled. Для Плюсов компиляции регексов пока вроде нет.

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

А когда же использовать regex?

Там где скорость не важна. Или парсинг настолько сложный что проще через regex чем вручную. Или когда структура для парсинга меняется. Вот если надо какие-то данные обработать один раз, то лучше взять regex чем тратить время на ручной разбор.

Для Плюсов компиляции регексов пока вроде нет.

Я вот даже и не знаю как оно там внутри, поэтому для такого простого случая и советуют вручную. Так как реализаций STL куча(GNU, LLVM, Microsoft, etc.) и у каждого свои заморочки могут быть, по крайней мере со скоростью работы.

V1KT0P ★★
()

Пацаны поясните, регулярки в Ц++ это вообще сильная боль, и даже питон регулярки и то лучше?

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

Пацаны поясните, регулярки в Ц++ это вообще сильная боль, и даже питон регулярки и то лучше?

Мое мнение такое что надеяться на лучшую производительность regex в STL не стоит. Но если нет ограничений на использование STL-only, то можно взять любую другую реализацию(например re2 или PCRE) и тогда все будет хорошо.

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

регулярки в Ц++ это вообще сильная боль

Мне кажется, текст и локали в целом в рамках STL СИЛЬНАЯ БОЛЬ и стоит брать Qt или ICU

Потому что в плюсах нет как такового понятия «строка», а std::locale вместо стандартных локалей оперирует implementation-defined - то есть оставляет неизвестно с чем и страданиями при смене компилятора/стандартной либы/платформы. Туда же поддержка кодировок в iostreams/fstreams

Uter
()
Последнее исправление: Uter (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.