Разминка для мозгов: парсер строки

Таким ошибкам я только рад ))

Четвертый! Всё, бригада )))

Осмысленно в обсуждении примут участие три человека

маэстро ошибся )))

Majestio, мне кажется, что выражение Rx = "^\\s*((\\+|-)?[0-9]+)\\s*,?\\s*$"; можно сократить до Rx = "^[+-]?\d+$";. Или я что-то не учёл? Далее вместо std::regex_search(i, Match, Rx); лучше использовать std::regex_match(); с соответствующими изменениями в дальнейшем коде. Не?

PS. (размышления по случаю) Когда я читаю такой код (C++11-С++14, страшно подумать (!) С++17), я уже не совсем понимаю, это С++ или что-то ещё? Когда Страуструп в 83-ем выкатил новый язык, это выглядело как продвинутый C с классами. Но с каждым новым стандартом С++ всё дальше уходит от того лаконичного языка (pure C) и превращается в монстра, за поведением которого всё меньше возможности уследить. Видимо пора переквалифицироваться в управдомы.

Вот на счёт "просто хотел показать, что парсить текст не просто удобно, а естественно, используя регулярки" в отношении C++/STL я не согласен. На Perl — да, но там другая экосфера, совершенно другие приоритеты. Я не очень большой знаток Perl, но вот вариант:

use strict;
use warnings;

sub parse {
    my $str = shift;
    my $msg = "ok";
    my $rcode = 1;
    my @vec = ();

    if ($str =~ /^\s*,/) {
        return (0, "missing number at pos 0", @vec);
    }
    if ($str =~ /,\s*$/) {
        return (0, "missing number at last pos", @vec);
    }

    my @tokens = split(/,/, $str);
    map { s/(^\s+)|(\s+$)//g; } @tokens;
    for (my $i = 0; $i <= $#tokens; $i++) {
        my $tok = $tokens[$i];
        if ($tok =~ /^[+-]?\d+$/) {
            push @vec, int($tok);
        }
        else {
            $rcode = 0;
            $msg = "\"$tok\" isn't valid number at pos $i";
            last;
        }
    }
    return ($rcode, $msg, @tokens);
}

my $str;
my ($retcode, $message, @vector);

while ($str = <STDIN>) {
    chomp $str;
    print "input : [$str]\noutput: ";
    ($retcode, $message, @vector) = parse($str);
    if ($retcode) {
        foreach my $val (@vector) {
            print "[$val] ";
        }
        print "\n";
    }
    else {
        print "$message\n";
    }
}

Алгоритм практически тот же, но на сколько чище код. И, обратите внимание, никаких библиотек. Всё только встроенными средствами языка.

Cranium, по поводу Rx = «^[+-]?\d+$»; неправильно — там еще парсятся пробелы и запятая, поэтому твой этот регэксп не потянет.

На Перловке ... некоторые конструкции делаются иначе. К примеру:

if (выражение) {
  оператор;
}

Нужно оформлять как

оператор if (выражение);

Это по фэншую.

use strict — это параноя от C++ :)

Разбиение токенов

my @tokens = split(/,/, $str);
map { s/(^\s+)|(\s+$)//g; } @tokens;

можно делать одним оператором

my @tokens = split(/\s*,\s*/, $str);

... и сразу же результат передавать не в for, а в foreach ...

Ну это так, мелочи конечно. Просто я в C++ пришел из Perl'а, немножко глаза режет :)

страшно подумать (!) С++17

На самом деле, там ничего страшного или особо нового.
Конечно, радует if constexpr,
но многое еще хотелось бы видеть :(

Ну и стандартная библиотека наконец-то
обзаведется работой с файловой системой :)

Но с каждым новым стандартом С++ всё дальше уходит от того лаконичного языка (pure C)

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

Majestio, вот такой вариант прекрасно работает:

std::vector<int> parse(const std::string &iStr) {
    std::vector<int> Ret;
    std::vector<std::string> Tmp;
    std::string Work(iStr);
    Work.erase(0, Work.find_first_not_of(' '));
    Work.erase(Work.find_last_not_of(' ') + 1);
    if (Work.length() == 0) throw std::invalid_argument("Пустая строка");
    if (Work[0] == ',') throw std::invalid_argument("В первой позиции не число");
    if (Work[Work.length() - 1] == ',') throw std::invalid_argument("В последней позиции не число");
    std::regex Rx("\\s*,\\s*");
    std::sregex_token_iterator Begin(Work.begin(), Work.end(), Rx, -1);
    std::sregex_token_iterator End;
    while (Begin != End) Tmp.push_back((Begin++)->str());
    Rx = "^[+-]?\\d+$";
    int Pos = 0;
    for (const auto &i : Tmp) {
        if (std::regex_match(i, Rx)) {
            try {
                Ret.push_back(std::stoi(i, nullptr, 10));
            }
            catch (...) {
                throw std::invalid_argument("В позиции " + std::to_string(Pos) + " слишком длинное число");
            }
        }
        else 
            throw std::invalid_argument("В позиции " + std::to_string(Pos) + " не число");
        Pos++;
    }
    return Ret;
}

по поводу Rx = "^[+-]?\d+$"; неправильно — там еще парсятся пробелы и запятая, поэтому твой этот регэксп не потянет.

Пробелов и запятой там уже нет. Они грохаются здесь:

    std::regex Rx("\\s*,\\s*");
    std::sregex_token_iterator Begin(Work.begin(), Work.end(), Rx, -1);

и в Tmp попадают строки уже без запятых и начальных/концевых пробелов.

А «неправильно» — совершенно по другому поводу. Корректное выражение Rx = "^[+-]?\\d+$"; или Rx = "^[+-]?[0-9]+$";.

Majestio, первая заповедь перлового феншуя: пиши код так, как считаешь, что код наиболее понятен читающему. Вторая: пиши код так, как тебе комфортно. Эти идеи есть и в Camel book, и в Lama book. Мне, как человеку пришедшему в Перл с Pascal, C, C++, ближе форма, когда сначала указывается условие, а потом действие. Кстати, любители перловой идиоматики могли бы предложить и вариант логическое_выражение && оператор, что тоже вполне в рамках закона, но лично мне кажется нечитаемым.

use strict — это параноя от C++ :)

Если вы не параноик, это ещё не значит, что за вами не следят!

Разбиение токенов ... можно делать одним оператором ...

Да, здесь я немного стормозил ))

и сразу же результат передавать не в for, а в foreach ...

А вот for здесь был использован намеренно ради счётчика «позиций» чисел в строке.

Croessmah, мне кажется, тебе с самого начала понравились шаблоны и STL, и ты с выходом каждого нового стандарта с удовольствием погружаешься в изучение новых фичей. Поэтому ты уже не замечаешь всех этих монстроидальных конструкций типа std::_Vector_iterator<std::_Vector_val<std::_Simple_types<std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >. Да, разобраться можно, но с первого взгляда сказать что это, лично для меня, затруднительно. А сообщения об ошибках компилятора при использовании некошерного аргумента для функции из STL — это отдельная песня. По моему мнению, развитие C++ идёт куда-то не туда... или не тем путём. Ещё пара следующих стандартов в том же направлении, и C++ просто умрёт под собственным весом: нормальный человек уже просто не сможет сказать что и как происходит в конкретной точке его программы. Даже сейчас это уже затруднительно. Как пример, в недавней теме: что происходит внутри std::string и почему оно тормозит относительно C'шного подхода к строкам? — а хрен его знает!

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

Ну да, в сообщениях об ошибках в шаблонах я ориентируюсь достаточно быстро,
а они там именно такие, каракулей строк так на 100-150 )))

развитие C++ идёт куда-то не туда... или не тем путём

В данном случае, как раз туда (например, constexpr if), но медленно.
Новые фишки как раз стремятся оградить всех от монстров типа SFINAE,
получается, но движется всё крайне медленно.
Уйти от этого монстра не получится года эдак до 21-ого, как минимум :)

почему оно тормозит относительно C'шного подхода к строкам? — а хрен его знает!

Строки слишком длинные получаются, как следствие, нужен new, а это накладно.
Если бы строки были короче, то компилятор могу бы оптимизировать их с помощью SSO,
если не ошибаюсь, компилятор в Visual Studio способен это сделать для строк не длиннее 16-ти символов.
Ну и для работы со строкой, например, когда нам нужно обработать (не изменяя) часть строки, но без копирования как раз ввели string_view.
Работа с файловой системой, Any, Option, математические функции и т.д.
Жизнь становиться легче, жизнь становится веселее.
Что касается тяжести, то тут я не согласен.
Язык сложен, но не настолько, чтобы рухнуть.
Надеюсь, что всё-таки полностью откажутся от легаси с Си,
тогда будет проще, а пока это легаси как камень тащит язык вниз.

почему оно тормозит относительно C'шного подхода к строкам? — а хрен его знает!

Строки слишком длинные получаются, как следствие, нужен new, а это накладно.

Я немножко не о том.

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

Когда мы пишем код с использованием примитивного, на фоне всего остального, std::string, оно выходит и короче, и лаконичнее. Но что происходит там, под капотом, сказать невозможно, не влезая в исходники STL. А при виде их, у меня мир тухнет.

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

Даже при использовании библиотечных функций типа strcpy.

Я бы не был в этом так уверен.
Как-то ковырялся в исходниках,
так оно там не так просто устроено.
С точки зрения языка там вообще одни UB,
но это же разрабы компилятора,
они знаю что делают... надеюсь )))

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

Именно в этом вся прелесть — у нас есть интерфейс
и строго заданное поведение.
Придерживаемся интерфейса — поведение не меняется.
Отклонились — ну что ж, пеняйте на себя.

что при использовании STL мы всё меньше понимаем как работает программа

В том то и дело, что мы начинаем понимать как работает программа,
не отвлекаясь на какие-то низкоуровневые вещи.
У меня есть строка, и я хочу работать со строкой,
а не с голым массивом символов, заботясь о нем и лелея его.
Когда мы дергаем new или malloc,
мы тоже не знаем что происходит,
оно же ведь как-то там дергает систему,
мы не знаем как, но нам это никак не мешает -
у нас есть определенный интерфейс с заданным поведением.
Мы бы всё равно написали свой аналог std::string, да,
мы бы знали как оно работает, но нам бы это не помогло,
а лишь забило мозг и отняло время,
да и я не уверен, что написанный велосипед
был бы эффективнее и стабильнее стандартного.
Другими словами, писать каждый раз с нуля — плохая идея.
Это отнимет кучу времени и даст крайне низкий результат.

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

Еще больший цирк начинается, когда ошибка возникает
при использовании более низкоуровневых средств.

Croessmah, тут как бы две темы.

(1) STL, как штука универсальная, не очень эффективна, как показывает практика. Индусы (не как национальность, а как подход к решению проблем) из кубиков STL, на основании формальных спецификаций, выстраивают огромный мегаполис, когда можно было обойтись придорожной бензоколонкой. Это по поводу общей тенденции снижения эффективности ПО и возрастающих требований к аппаратной части и программному окружению. Дистрибутив MS Office 2016 тянет на 68 Гб... 68 гигов кода (минус иконки, градиентные заливки и шаблоны писем) необходимы для реализации заявленной функциональности? «Не верю!» (с) Станиславский.

(2) STL — штука сложная. Сложная как в плане понимания, так и в плане использования. Мне кажется, если бы STL не было, было бы лучше, чесслово! Ну кому мешал старый добрый printf? С его понятным поведением и хорошо изученными (за многие годы) граблями? Нет, надо <iostream>! Пока вывод примитивен — всё в порядке, как надо вывести хотя бы в виде примитивной таблицы — начинаются танцы с бубном (бубен — это <iomanip>).

С одной стороны, теоретической, STL — штука правильная: и надёжность выше, и RAII, и прочие плюшки. С другой стороны, пользоваться ей неудобно. И в синтаксическом плане, и в плане понимания, и в плане отладки. Это ЖОПА. И она случилась.

В тот момент, когда в С++ было решено ввести синтаксическую параметризацию (aka шаблоны), родился монстр: фактически два языка программирования в одном флаконе. Причём эти языки поддерживают разные парадигмы программирования (декларативную и императивную). Т.е. я пишу программу, которая исполняется компилятором, что бы в результате породить программу, которая будет откомпилирована тем же компилятором и будет исполняться процессором. Не слишком ли навороченно получилось?

Наверное я слаб в декларативном программировании. В языке описания шаблонов С++ мне не хватает близких мне императивных операторов: if (целый_тип?(T)) { фрагмент кода для целого типа } else { фрагмент кода для типа с плавающей точкой}. Но даже в этом случае, максимально приближенном к макросам, сложно отследить что же за код получится перед окончательной компиляцией.

С другой стороны, в этом случае мы уже вплотную приближаемся к языкам с динамической типизаций с RTTI и прочими прелестями саморефлексии )) А это, как правило, приводит к снижению быстродействия.

Резюме: развитие С++ (в плане стандартов) сейчас идёт в сторону (1) уменьшения быстродействия/эффективности и (2) малообоснованного применения стандартных функций из STL, только исходя из их формальных спецификаций.

А зачем?? Зачем вся эта боль рождения нового ребёнка из старых чресел, когда есть Ruby, Perl, Python и проч. Если производительность и нативный код пофиг, зачем зря попу морщить?

Я не прав?

Cranium, ну на счет Perl'а (да и не только его) — у меня свой взгляд. Я стараюсь писать компактнее. Лично мне многословность, и/или использование необоснованно бОльших конструкций кажется лишней. Вот мой набросок кода на Перловке с подобной функциональностью, за исключением не формирования выходного вектора чисел (ну это одна строка, которая в выводе не используется, поэтому выкинул)

#!/usr/bin/perl

@Lines = (
  "1, 4, 5, -6, -8 , 3, 1",
  "1, 3333333333333333333333333334, 5, -6, -8 , 3, 1",        
  "       5 , 6, 57, +68 , 18, 32, 4   ",
  "77",
  "",
  "    ",
  "1, 4, 5, 6, 8, 3, 1,",
  " , 6, 57, 68 , 18, 32, 4",
  "5, 6, , 68, 18, 32, 4",
  "5, 6,, 68, 18, 32, 4",
  "5, 6, T, 68, 18, 32, 4",
  "5, 6, 34T, 68, 18, 32, 4",
  "5, 6, 34 55, 68, 18, 32, 4",
  ",",
  ",,",
  ",, ,, ,,,"
);

map { print "\"$_\": ".parse($_)."\n"; } @Lines;

##############################################################

sub parse {
  my ($str,$pos) = (shift,0);
  return "В начале строки не число" if $str =~ /^\s*,/;
  return "В конце строки не число" if $str =~ /,\s*$/;
  map { 
    return (($pos==0) ? "В начале строки" : "В позиции \"$pos\""). " не число" 
      unless (/^[+-]?\d+$/ && $_+1!=$_);
    $pos++; 
  } split(/\s*,\s*/, $str);
  return "Ok";
}

http://ideone.com/BZrbbr

В языке описания шаблонов С++ мне не хватает близких мне императивных операторов: if (целый_тип?(T)) { фрагмент кода для целого типа } else { фрагмент кода для типа с плавающей точкой}.

Ну так к этому и идет :)

Нет, надо <iostream>

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

С другой стороны, в этом случае мы уже вплотную приближаемся к языкам с динамической типизаций с RTTI и прочими прелестями саморефлексии

Динамическая типизация — рантайм,
в C++ же всё это стараются проделывать во время компиляции.
Нужен рантайм, то пилите сами, ну или дергайте готовое.
Что-то типа тормозного boost::any
(std::any, если полностью скомуниздили из буста).

Ну кому мешал старый добрый printf?

Так его запретили чтоли?
Используйте, кто же мешает.
Только не забывайте каждый раз ставить нужную локаль.
Упс, да, уже первое отличие от потоков.
А раз есть различия, значит это не одно и то же :)

Сложная как в плане понимания, так и в плане использования.

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

Cranium, да я с Вами и не спорю так-то.
Просто кому что нравится. Спорить бесполезно :)

Внимание! Это довольно старый топик, посты в него не попадут в новые, и их никто не увидит. Пишите пост, если хотите просто дополнить топик, а чтобы задать новый вопрос — начните новый.

Ответить

Вы можете использовать разметку markdown для оформления комментариев и постов. Используйте функцию предпросмотра для проверки корректности разметки.

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

Либо производите оформление кода вручную, следующим образом:

``` #include <iostream> using namespace std; int main() { // ... } ```

Предпросмотр сообщения

Ваше сообщение пусто.