RE в C++
Для работы с RE в С++ используется класс regex, которому в качестве аргумента передается строка, содержащая RE. Так как RE может содержать много символов \, которые в обычной строке C++ необходимо удваивать, для RE можно использовать синтаксическую конструкцию R"`r`( )`r`", где `r` – одна и та же последовательность символов (возможно пустая), без символов скобок, пробелов и \. Символом завершения строки является не ", а целая последовательность, которую можно выбирать самостоятельно. Например, R"@@(\n")")@@" соответствует строке \n")", которая при использовании обычного синтаксиса записывается как "\\n\")\"".
Вторым аргументом конструктора можно указать набор флагов, среди которых формат регулярного выражения, флаг icase для игнорирования регистра букв, флаг nosubs для интерпретации подвыражения ( ) как (?: ), флаг оптимизации optimize (если разработчики компилятора предусмотрели такую возможность), флаг collate для учета текущих национальных настроек (локали) при указании диапазона символов (например, множество [а-я] включает букву ё). По умолчанию используется формат ECMAScript (более известном как JavaScript), остальные варианты: basic, extended, awk, grep, egrep – имеют меньше возможностей и предназначены для обеспечения совместимости со стандартами POSIX и некоторыми приложениями. Все флаги объявлены в пространстве имен std::regex_constants.
В RE для ECMAScript можно делать отсылки к подвыражениям в ( ) вида \№, где № – номер подвыражения. Например, RE (a+)b\1 требует, чтобы строка содержала одинаковое количество букв a до и после b. Эта возможность не может быть реализована с помощью конечного автомата, но полезна для обработки сложных текстов, поэтому в С++ применяется рекурсивный перебор.
Для работы с RE в C++ используются следующие функции и итераторы.
Функция regex_match проверяет, соответствует ли вся строка заданному RE, а функция regex_search проверяет, есть ли подстрока, соответствующая заданному RE. Для обоих функций первым аргументом нужно указать строку, далее нужно указать либо RE, либо переменную класса smatch, куда будет помещен результат сопоставления, а затем RE. Методы класса smatch позволяют получить подстроки, соответствующие подвыражениям, их позицию и длину (см. пример). В качестве аргумента этим методам нужно указать номер подвыражения (по умолчанию номер равен 0 – вся подстрока, соответствующая RE).
string x="12/7";
string z="color: #ff0000;";
regex re(R"((\d+)/(\d+))");
regex color_re("#[a-f0-9]{6}");
if(regex_match(x,re))
cout<<"строка содержит рациональное число\n";
else
cout<<"строка содержит что-то другое\n";
smatch parts;
if(regex_match(x,parts,re))
{
cout<<"числитель:"<<parts.str(1)<<" знаменатель :"<<parts.str(2)<<"\n";
//
cout<<parts.position(2)<<" "<<parts.length(2)<<"\n";
}
if(regex_search(z,parts,color_re))
{ cout<<"найден цвет:"<<parts.str()<<" в позиции:"<<parts.position()<<"\n";
}
Функция regex_replace позволяет заменить текст,
соответствующая заданному RE. Первым аргументом нужно указать строку с текстом, вторым – RE, третьим – строку с правилом замены. В опциональном четвертом аргументе можно указать флаги format_first_only (заменять только первое соответствие) и format_no_copy (не помещать в результат части текста, не соответвущие RE). В правиле замены используются следующие обозначения: $$ – символ $; $& – подстрока, соответствующая всему RE; $№ (где № от 1 до 99) – подстрока, соответствующая №-му подвыражению RE; $` – часть строки до соответствия; $' – часть строки после соответствия.
string y="Christmas holiday from 12/21/2015 to 01/03/2016";
regex date_re(R"((\d+)/(\d+)/(\d+))");
//
cout<<regex_replace(y,date_re,"$2-$1-$3")<<"\n";
Для более сложной обработки текста используются классы-итераторы sregex_iterator и sregex_token_iterator.
Первым и вторым аргументом конструктора обоих классов указывается начало и конец обрабатываемой последовательности, третьим – RE. Для класса sregex_token_iterator четвертым аргументом указывается номер анализируемого подвыражения RE, по умолчанию 0 – соответствие всему RE. Можно указать –1 – для обработки частей строки, не соответствующих RE. Конструкторы без аргументов для обоих классов используется для проверки на завершение обработки (см. пример). Основное отличие между классами – на что указывает итератор. Итератор sregex_iterator указывает на значение класса smatch и можно обрабатывать подстроки, соответствующие подвыражениям RE, а итератор sregex_token_iterator указывает на значение класса ssub_match, в котором определена только операция преобразования к string и метод str() с тем же эффектом (т.е. можно считать это значением типа string).
//
//
regex kw_re(R"((begin|end|for|do|var)|([a-z_]\w*))",regex_constants::icase);
int p=0;
string s("var I:integer;begin i:=1; End."),r;
for(auto t=sregex_iterator(s.begin(),s.end(),kw_re);
t!=sregex_iterator();++t)
{ r+=t->prefix().str();
for(p=t->position();p<t->position()+t->length();++p)
if(t->length(1)) //
r+=toupper(s[p]);
else //
r+=tolower(s[p]);
}
r+=s.substr(p);
cout<<r;
//
regex space_re(R"(\s+)"); //
for(auto t=sregex_token_iterator(txt.begin(),txt.end(),space_re,-1);
t!=sregex_token_iterator();++t)
{ cout<<*t<<"\n";
}
//
regex ref_re(R"!(<a\s+href="([^"]+)">)!");
for(auto t=sregex_token_iterator(htm.begin(),htm.end(),ref_re,1);
t!=sregex_token_iterator();++t)
{ cout<<*t<<"\n";
}
В стандарте C++ почему-то отсутствует вариант regex_replace, где в качестве 3-го аргумента можно было бы указать функцию или лямбда-выражение, которая производит замену по более сложным правилам, чем простая подстановка, хотя эта возможность присутствует в библиотеке boost и других языках программирования. Можно воспользоваться следующим вариантом:
template <typename Function>
string regex_replace(const string &s, const regex &re, Function f)
{ string r;
sregex_iterator t(s.begin(),s.end(),re),end;
int last=0;
for(;t!=end;++t) {
r+=t->prefix().str()+f(*t);
last=t->position()+t->length();
}
return r+=s.substr(last);
}
//
string s("Artificial Intelligence in Computer Science is an ideal "intelligent" machine.");
regex ab(R"(\b[A-Z][a-z]+( [A-Z][a-z]+)+\b)");
cout<<regex_replace(s,ab,[](const smatch &m){
return m.str()+" ("+regex_replace(m.str(),regex(R"([a-z ]+)"),"")+")"; })<<"\n";
Для небольших строк можно использовать в цикле regex_search с заменой строки на суффикс строки после найденного RE (задача Abbreviation с NEERC 2016):
int main()
{ string s;
regex ab(R"(\b[A-Z][a-z]+( [A-Z][a-z]+)+\b)"),na(R"([a-z ]+)");
smatch m;
while(getline(cin,s))
{ while(regex_search(s,m,ab))
{ cout<<m.prefix().str()+regex_replace(m.str(),na,"")+" ("+m.str()+")";
s=m.suffix();
}
cout<<s<<"\n";
}
}
Упражнения