- 001
- 002
- 003
- 004
- 005
- 006
- 007
- 008
- 009
- 010
- 011
- 012
- 013
- 014
- 015
- 016
- 017
- 018
- 019
- 020
- 021
- 022
- 023
- 024
- 025
- 026
- 027
- 028
- 029
- 030
- 031
- 032
- 033
- 034
- 035
- 036
- 037
- 038
- 039
- 040
- 041
- 042
- 043
- 044
- 045
- 046
- 047
- 048
- 049
- 050
- 051
- 052
- 053
- 054
- 055
- 056
- 057
- 058
- 059
- 060
- 061
- 062
- 063
- 064
- 065
- 066
- 067
- 068
- 069
- 070
- 071
- 072
- 073
- 074
- 075
- 076
- 077
- 078
- 079
- 080
- 081
- 082
- 083
- 084
- 085
- 086
- 087
- 088
- 089
- 090
- 091
- 092
- 093
- 094
- 095
- 096
- 097
- 098
- 099
- 100
#include <iostream>
#include <string>
using namespace std;
class Govnokod {
bool _flag_dot;
bool _flag_mant;
int _index_mant;
bool vetka1(int i, const string stroka) {
for (int j = i++; j < stroka.length(); j++) {
switch (stroka[j]) {
case '.':
if (_flag_dot) return false;
_flag_dot = true;
break;
case '0' ... '9': break;
default:
return false;
break; }}
return true;}
bool vetka2_dalshe(const string stroka) {
for (int j = 1; j < stroka.length(); j++) {
switch (stroka[j]) {
case '0' ... '9': break;
default:
return false;
break; }}
return true; }
bool vetka2(const string stroka) {
switch (stroka[0]) {
case '+':
case '-':
if (stroka.length() < 2) return false;
return vetka2_dalshe(stroka);
break;
case '0' ... '9':
return vetka2_dalshe(stroka);
break;
default:
return false;
break; }}
bool mantissa(const string stroka) {
for (int j = 0; j < stroka.length(); j++) {
switch (stroka[j]) {
case 'e':
case 'E':
if ((_flag_mant) or (j == (stroka.length() - 1))) return false;
_flag_mant = true;
_index_mant = j;
break; }}
return true; }
bool Dalshe(int i, const string stroka) {
_flag_dot = false;
_flag_mant = false;
if (not mantissa(stroka)) return false;
else if (_flag_mant) {
string sub1 = stroka.substr(0, _index_mant);
string sub2 = stroka.substr(_index_mant+1);
return (vetka1(i, sub1) and vetka2(sub2)); }
else return vetka1(i, stroka); }
bool proverka(const string stroka) {
switch (stroka[1]) {
case '0' ... '9':
return Dalshe(1, stroka); break;
default: return false; break; }}
bool general_proverka(const string stroka) {
if (stroka.length() == 0) return false;
switch (stroka[0]) {
case '-':
case '+':
if (stroka.length() > 1) return proverka(stroka);
else return false;
break;
case '0' ... '9':
return Dalshe(0, stroka);
break;
default: return false; break; }}
string cut_incorrect_symbol(const string stroka) {
int j, i;
string buf;
for (j = 0, i = 0; j < stroka.length(); j++) {
switch (stroka[j]) {
case '0' ... '9':
case '-':
case '+':
case '.':
case 'e':
case 'E':
buf.push_back(stroka[j]);
break;
default: i++; break; }}
return buf; }
public:
long double opros(char s) {
string argument;
while (true) {
cout << "Введите значение " << s << ": ";
getline(cin, argument);
if (argument.length() == 0) cout << "Вы не ввели значение!" << endl;
else if (not general_proverka(cut_incorrect_symbol(argument))) cout << "Некорректное значение!" << endl;
else return strtold(argument.c_str(), nullptr); }}};
Модифицированная версия говнокода проверки строки на корректность соответствия символов типу long double: изначально вырезаются все левые символы. А вообще этот модуль "govno.h", я написал для основной проги для решения квадратного уравнения.
Westnik_Govnokoda 25.12.2020 22:58 # 0
1024-- 26.12.2020 14:58 # +2
> opros
> general_proverka
Смесь языков - плохой стиль. Лучше лишнее время провести со словарём и подобрать адекватное имя.
> Dalshe
> proverka
Разный стиль именования вроде бы однотипных сущностей. Плохой стиль.
> vetka1
> vetka2_dalshe
> vetka2
Питушня, которая ничего не говорит читателю кода. Это уже не просто визуально плохой стиль как Dalshe и proverka, а
1. долгий пердолинг читателя кода в попытках понять, что он делает
2. шанс словить баг из-за того, что в голову не укладывается код
Так именовать функции можно только в том случае, если программирование в вузе ведёт какой-то козёл, и хочется заставить его страдать при проверке этого перед тем, как свалишь в другой университет.
Единственное разумное исключение - нумерованные шаги какого-то известного алгоритма, на название которых совсем-совсем вообще полностью не хватает фантазии.
При этом
1. пишется комментарий, что это за алгоритм (название, какая страница какого издания в каком томе у Кнута)
2. делается так, что читатель понимает, что это шаги алгоритма, а не какие-то абстрактные питушни, которые писатель кода почему-то использует.
Что-то такое:
Хотя, это только для примера. Алгоритм из примера и многие другие всё равно можно переписать через функции с понятными именами без номеров!
jojaxon 26.12.2020 15:21 # 0
1024-- 26.12.2020 15:34 # +1
Код смышлёного человека не понимают ни одногруппники(цы), ни преподаватели, и если он соснул в попытках поймать баг, то помочь ему не может абсолютно никто, даже если бы очень захотели. Это отличный момент для того, чтобы тыкнуть его(её) мордой в код и ещё раз напомнить, что так писать не стоит. И тыкать надо чем чаще, тем лучше (говорю как вот такой смышлёный человек), поскольку пока человек вывозит своё говно, он не понимает, насколько глубоко он в нём находится, и питушня, когда никто не знает, как ему помочь, в школьном/универском курсе случается довольно редко и быстро забывается пациентом.
1024-- 26.12.2020 15:24 # +2
То есть подзадача или одна операция - нечто, что выглядит логично для замешанного в теме человека.
Такое разделение
1. Упрощает написание программы. Каждая новая функция строится на основе простых предыдущих и становится синонимом новой комплексной операции.
2. Упрощает чтение программы. Вместо того, чтобы читать код про комбинацию f(x) = 10*x + 4 и g(x) = -x + 1/2, читатель читает код про комбинацию f(x) = kx и g(x) = b.Первая пара имеют какой-то свой конкретный смысл, который нужен только для этой программы, а вторая пара абстрактна. С более общими и стандартными понятиями читатель сталкивается чаще и лучше их понимает. f(x) = kx и g(x) = b проще в усвоении, они более предсказуемые.
3. Упрощает переиспользование кода и дописывание программы через долгое время простоя. Новый программист (или старый через полгода) имеет дело с набором функций, назначение и правила пользования которыми ясны.
Что хорошо, в опубликованном здесь коде ввод-вывод сосредоточен в opros, а general_proverka можно вызвать как для произвольной уже почищенной строки, так и почистить её с помощью cut_incorrect_symbol (надо назвать "cut_incorrect_symbols"). Ввод-вывод, предварительная подготовка строки и валидация как разные абстрактные операции разнесены в коде на разные функции, и это хорошо.
Учиться правильной декомпозиции надо сразу и применять везде, даже в самых простых проектах, где "всё как бы очевидно, зачем стараться". Потому, что потом привычка писать говнокод с говнодекомпозицией задачи перетекает в большие проекты.
В итоге код человека, который не научился писать его аккуратно и логично, очень быстро деградирует в многосвязный непонятный клубок функций, классов и общего состояния, и для его поддержки требуется всё больше и больше ментальных усилий.
1024-- 26.12.2020 16:35 # +2
В ней есть свои преимущества и недостатки.
У нас парсится число вида "+000.000E+000". Соответственно, можно заметить три повторяющихся куска про числа, парсинг знака, парсинг буквы E и точки, необязательность некоторых частей. При парсинге куска числа мы должны понять, удалось ли распарсить его и сколько символов занимает распарсенное значение.
Соответственно, для строки str будем иметь текущую позицию pos в ней, и натравлять функции, которые будут начинать с pos и проверять некоторые свойства строки, двигать pos и возвращать, распарсено или нет.
1. Надо создать парсер точки. У нас есть кроме точки ещё вариативные + и -, E и e, а также 0..9. Соответственно, нам понадобится парсер символа из пользовательского диапазона. Для цифр есть isdigit в ctype.h, поэтому можно реализовать парсинг цифры либо как парсинг любого символа из набора 01234566789, либо через isdigit.
создали match_digit для парсинга цифры, match_symbol - для парсинга пользовательского символа.
2. Знак встречается до двух раз, и оба раза необязательный. Можно вынести в функцию, которая либо парсит +-, либо ничего
3. Число - набор цифр. На основе функции парсинга цифры делаем парсинг числа
4. Думаем над парсингом "000.000", "000.", ".000", "000" и непарсингом ".". Можно просто запомнить pos и пройти по строке, начиная с pos, парсерами из набора наборов - число+точка+число, число+точка, точка+число - какой набор первый пройдёт, либо false. Это самый простой и надёжный способ.
Однако ради производительности я стараюсь дважды не проходить по строке таким наивным образом. Поэтому я комбинирую парсер точки и числа в парсер дробной части match_frac и прогоняю последовательно через парсер числа (если что, откатываюсь к старому pos) и через парсер дробной части. Таким образом, "000.000", ".000", "000" у меня матчатся. Для "000." надо проверить отдельно одинокую точку, что я и делаю.
1024-- 26.12.2020 16:35 # +1
а. если распарсилось, то pos указывает на следующий нераспарсенный символ
б. если не распарсилось, то pos указывает чёрт пойми куда
в. если дошли до конца строки, вернули false и не вылезли за границу массива
Это потенциально полезные свойства. Если в новых комплексных парсерах следим за его выполнением, то все парсеры им обладают, код унифицирован - его легче писать и проверять, а свойство "в" можно явно проверять только в элементарных парсерах, в следующих оно автоматически выполняетя.
6. Строим инфраструктуру сохранения состояния - save|restore. Стек состояний - потому, что парсеры иерархически используют более простые. Для парсинга нужно восстановление только если произошла ошибка - поэтому сохранение безусловное, а восстановление - условное.
6. Самый главный парсер оборачиваем в функцию, которая принимает строку.
Итого,
1. у нас есть парсеры, из которых можно легко кобенировать новые парсеры
2. операции идут от абстрактных, которые пригодятся в других задачах, к более конкретным, которые являются комбинацией абстрактных
3. каждый маленький парсер можно понять (относительно понятное имя) и проверить отдельно
4. код написан по законам своего диалекта
5. в прошлой версии https://ideone.com/2mpH8O не работал для числа "0.0" из-за того, что match_digits раньше не откатывалась назад, когда читала не-цифру, и когда match_digits стала выполнять наши требования, всё заработало
6. вероятно, можно вынести `match_*` в класс parser, который агрегирует parser_state или приватно от него наследуется.
gost 26.12.2020 16:39 # +1
Будешь делать учебник по программированию?
1024-- 26.12.2020 16:45 # +2
Когда он вернётся, мы подготовим ему материалы не только по матану, но и основам проектирования.
gost 26.12.2020 16:47 # +2
1024-- 26.12.2020 17:23 # +2
Хотя, действительно такой парсинг не заменит стандартное решение, где уже обо всём позаботились.
Идея с комбинациями парсеров и откатами при неудаче тут работает.
1. Каждому пункту их списков сопоставим функцию, которая парсит этот кусок.
2. Каждому пункту внутренних списков с optional поставим в соответствие парсер с самооткатом (как мой maybe_match_sign). Каждому внутренних списков без optional - обычный парсер.
3. Каждый пункт внешнего списка - запуск парсеров из подсписков &&.
4. Парсер числа - запуск парсеров для пунктов внешнего списка последовательно (каждый - с начала строки), до первого совпадения.
Моя питушня расширяема, однако "nonempty sequence of decimal digits optionally containing decimal-point character (as determined by the current C locale)" гораздо легче реализуется методом автора с флагами, чем мой парсинг кобенаций "000.000", ".000", "000.", "000" с попыткой натянуть иерархию на мантиссу. Можно переписать матчинг мантиссы со знаком через это: