Maqolada biz RegEx(Regular expression) haqida va undan C++ da foydalanish borasida gaplashamiz. Regex ning haqiqiy kuchini kashf qilamiz va bir necha namunalarda ko'rib o'tamiz.

Xo'sh o'zi regex nima? Menimcha bu hozir ko'pchiligimiz uchun yangilik. Regex berilgan belgilar to'plamini ichidan siz bergan ifoda bo'yicha belgilar to'plamini olish. Unchalik tushunarsiz bo'ldimi? Shoshilmang, hozir uni amaliyotda ko'ramiz va u o'ylaymanki sizga yoqib qoladi :) Oddiy bir misol, sizga gap berilgan, o'sha gap ichidan e-mail manzillarini ko'rsatib berishingiz kerak. Qoyil, siz buni qiynalmasdan bajara olasiz! Lekin qanday? Siz beixtiyor harflar va raqamlar aralashgan ifoda(username) va undan keyin keladigan kuchukcha belgisi va domen yozilgan belgilar to'plamini e-mail manzil deb ko'rsatasiz(masalan, cppcoder24@gmail.com) va siz e-mail ni topishda o'z qolipingizdan foydalandingiz(<username>@<domen>). Ya'ni e-mail manzilni qanday formada bo'lishini bilasiz. Endi shuni dasturimizga tatbiq qilib ko'ramiz. Agar siz qidirish tizimlaridan, so'zni qidirish va uni boshqasi bilan almashtirish funksiyalaridan, oddiy matn muxarrirlaridan foydalangan bo'lsangiz, demak siz uni amaliyotda ko'rgansiz! Shuningdek, regex dan parollarni kuchli-kuchsiz tuzilganini tekshirishda ham ishlatishadi. Regex da qidirish belgilardan iborat ifoda yordamida amalga oshiriladi, bu ifoda qolip vazifasini bajaradi.

C++ da kod yozib ko'rganlar juda yaxshi bilishadi, unda ma'lumotlarni to'g'ri ekanligini tahlil qilish bir muncha murakkab jarayon. Masalan, foydalanuvchidan son kiritishni so'radingiz, kiritiladigan narsani siz string da olib, uni haqiqatda sonmi-yo'qmi bilmoqchisiz. Har bir dasturchi bu jarayonda o'zining tekshirish algoritmini yozib chiqishga majbur bo'ladi. Lekin C++ 11 da regex kirib keldi va bu endi muammo emas! C++ 11 ECMAScript, awk, grep va yana bir nechta regular expression grammatikasini qo'llab-quvvatlaydi, biz ECMAScript sintaksisini ko'rib o'tamiz. Regex kutubxonasini kodimizda import qilamiz:

#include <regex>

bu orqali biz regex dan kodimizda foydalana olishimiz mumkin. Lekin bu C++11 va undan yuqorida ishlaydi.

DIQQAT! Namunadagi kodlar main funksiyasi ichiga yoziladi:

#include <iostream>

#include <regex>

using namespace std;

int main()

{

//Namunadagi kodlar

}

Endi c++ da kod yozib, uni sinab ko'rish uchun biror muhitni oching :)

C++ 11 ni sozlash. Sozlashni Code::Blocks muhitida ko'rib o'tamiz. Birorta konsol proyekt ochib, yuqorida turgan paneldan Settings->Compiler ni tanlaymiz. Va uni quyidagi rasmdagidek sozlab olamiz:

Ok ni bosamiz. Tayyor.
Dev muhitini ishlatadiganlar diqqatiga. Yuqoridagi paneldan Tools->Compiler Options ga kiramiz, bizga kompilyator sozlamalari oynasi ochiladi:

Va rasmda ko'rsatilgan bo'limdan ISO C++ 11 ni tanlab, OK ni bosamiz, Tayyor

Regex da ifodalar tuzish.

Regex da belgilar to'plamini ichidan o'zimizga kerakli qismini olish uchun ifoda tuzishni o'rganib chiqishimiz kerak. Ifodani belgilar yordamida tuzamiz. Biz tuzgan ifoda o'ziga mosini o'sha belgilar to'plami ichidan topib beradi. Ifodani belgilar to'plamidan tuzamiz, o'z navbatida belgilarimiz oddiy va maxsus(metacharacter) bo'ladi. Oddiy belgilardan foydalansak, ular berilgan matn ichidan o'ziga o'xshaganlarni topib beradi. Maxsus belgilar biroz boshqacharoq ishlaydi. Biz ifodani tuzayotganda uni Raw String qilib tuzganimiz ma'qul. Nima ekanligini keyinroq tushuntirib beraman. Maqolani shu joyiga kelganda, mavzudan biroz chetga chiqib, raw string ni ozroq tushuntirib bersam,
Raw string o'zi anglatib turgandek "chala" string. Uni oddiy string tipida qo'shtirnoqlarni boshlanishiga R harfini qo'yib hosil qilamiz, qo'shtirnoq ochilgandan keyin ochiq qavs va yopilishidan oldin yopiq qavs qo'yish shart. Keling, namuna ko'raylik:

string s="Salom!\n Havo ajoyib-a?!";
string s1=R"(Salom!\n Havo ajoyib-a?!)";
cout<<s<<endl;
cout<<"=====\n";
cout<<s1<<endl;

Natija:

Salom!

 Havo ajoyib-a?!

=====

Salom!\n Havo ajoyib-a?!

Ko'rib turganingizdek, \n da enter tashlanishi kerak edi, raw string esa shunaqa joylarini inkor qila oladi

\ - bu shunchaki backslash va uni raw stringda shunchaki ishlatish mumkin, stringda backslash ni ishlatish uchun \\ yozishimiz kerak.

 

Ifodamiz albatta regex tipida bo'lishi kerak. Ifoda yozishdan oldin ba'zi metacharacterlar(maxsus belgilar) bilan tanishib chiqsak:

. - nuqta har qanday belgini topib beradi

\d - har qanday raqamni topib beradi(decimal). Ushbu metacharacterga ekvivalent: [[:digit:]]

\D - \d ni teskarisi, raqam bo'lmagan belgini topib beradi. Ushbu metacharacterga ekvivalent [^[:digit]]

\s - bo'shliqni topadi(space, tab)

\S - \s ni teskarisi.

\w - alphanumeric(harf yoki raqam) va tagchiziq belgisini topadi.

\W - \w ni teskarisi.

+ biror x belgidan keyin ushbu belgini qo'yish orqali ushbu x belgi 1 yoki ko'p marta qatnashishi kerakligini bildirgan bo'lamiz.

| - or(yoki), buni biz 2 ta belgi orasiga qo'llash orqali ulardan biri kelganini aniqlashimiz mumkin bo'ladi.

[belgilar to'plami] - ushbu burchakli qavslar ichida belgilar kategoriyasini kiritamiz. Ichidagi belgilardan duch kelganini topadi. Masalan, agar [abc] desak, unda a, b, yoki c belgisini topadi.

X dan Y gacha bo'lgan oraliqlarni [X-Y] tarzida belgilaymiz. Masalan: [a-z] - a, b, c, d, ... z belgilar ichidan uchraganini topib beradi, agar [0-9] desak, unda raqamlarni topadigan bo'ladi.

regex_match(string s, match m, regex rgx) - matndan ifodaga mos qismini qidiradi, topilsa true, aks holda false qaytaradi. Parametrlariga ortiqcha izoh berishim shartmas deb o'ylayman - 3 ta argument oladi, biri string tipida, 2-si regex bo'lishi kerak. m argumenti haqida keyinroq gaplashamiz, hozircha u unchalik muhimmas va argument berilishi shartmas.

Maxsus belgilar bilan biroz bo'lsa-da tanishib oldik. Ho'p, bu yaxshi, lekin men matn ichidan aytaylik nuqta(.) ni qidirmoqchiman, lekin u metacharacter bo'lsa, unda buni qanday qilaman? Biz maxsus belgilarni oldiga backslash qo'yish orqali ularni maxsus qobilyatidan maxrum qilib qo'yamiz, javob: \. orqali nuqtani topishimiz mumkin.

Maxsus belgilardan qanday foydalanishni esa quyidagi misolda ko'rib o'tamiz:

Aytaylik, sizga string tipida ma'lumot berilgan, ushbu ma'lumotni int tipiga o'girishda xatolik bo'lish-bo'lmasligini topishingiz kerak. Kiritiladigan ifodalar masalan 007, +07, 7 yoki -007, -7 ko'rinishida bo'lishi mumkin. +-12 kiritsak xatolik beradi. Endi asosiy fokusni kod yozishga qaratamiz, regex da o'rgangan ayrim metacharacterlarimiz yetarli:

regex ifoda(R"((\+|-)?[0-9]+)");
string input;
cout<<"\nSon kiriting: ";
cin>>input;
if(regex_match(input, ifoda))
{
  cout<<"Integer!";
}else{
   cout<<"Xatolik!";
  }

Menimcha ko'pchilik regex tipidagi ifodamizga unchalik tushunmadi, uni bo'laklab tahlil qilamiz. Raw string ligi uchun biz R"()" ichiga ifodani yozamiz. Endi, son oldida + yoki - kelishi kerak, buni qavs ichida (+|-) deb yozishimiz mumkin edi, lekin biz + ni metacharacter ekanligini hisobga olib, uni maxsus qobilyatidan maxrum qilamiz: (\+|-) bo'ldi. ? belgisi 0 yoki 1 marta qatnashsin degani, uni boyagi tuzgan ifodamizdan keyin qo'llasak, + yoki - belgisi 0 yoki 1 marta qatnashsin degan bo'lamiz. [0-9] esa 0 dan 9 gacha kelgan raqam degani, undan keyin agar + qo'ymasak, u faqat bir xonali sonlar uchun ishlaydi. [0-9]+ ifodamiz 0 dan 9 gacha oraliqdagi raqamlar 1 yoki ko'p marta qatnashsin deganini ifodalaydi. Endi barchasini umumlashtiramiz: (\+|-)?[0-9]+ --> + yoki - belgisi 0 yoki 1 marta qatnashgan holda 0 dan 9 gacha bo'lgan raqam 1 yoki ko'p marta bo'lsin. Hozir bularni o'qib, bu AbraKadabraga tushunmadingizmi? Unda o'zingiz metacharacterlar tasnifiga qarab boshqattan hijjalab tahlil qilib ko'ring.

Metacharacterlarga yaxshi e'tibor berganlar fahmlab ulgurishgan: [0-9] ni [\d] bilan almashtirishimiz mumkin, bu natijaga ta'sir qilmaydi. \d nima qilishini yuqoridan topishingiz mumkin.

ifoda o'zgaruvchisiga regex da ifodamizni yozmasdan, uni shundayligicha regex_match funksiyamizga berib qo'ysak ham bo'laveradi:

regex_match(input, regex(R"((\+|-)?[0-9]+)"))

Endi biroz kirishib oldik deb o'ylayman, shu joyida raw string ni nega qo'llayotganimizga qisqacha izoh berib ketsam. C++ da shunchaki oddiy string ga regex ifodasini kiritsak u xunuklashib ketadi. Keling, ifoda o'zgaruvchisidagi scriptimizni oddiy stringga o'giramiz: (\\+|-)?[0-9]+ Nega 2 ta backslash? Yuqorida aytganimdek, backslashni o'zini stringga berish uchun \\ ni yozish kerak. Ho'p, bunda bitta backslashga farq qilyabdi ifodamiz, lekin agar ko'p marta maxsus belgilarni oddiy belgi sifatida ifodamizga kiritmoqchi bo'lsak-chi? Unda ularni oldiga backslash qo'yishimiz kerak ularni maxsus qobilyatini bekor qilish uchun, keyin shu backslashni o'ziga ham yana bitta backslash qo'llashimiz kerak, bu o'qishni birmuncha qiyinlashtiradi, tinchgina raw stringdan foydalanib qo'yaveraylik ;)

Endi e-mail uchun regex ifoda yozib ko'ramiz:

regex pattern_email(R"(([\w\.-]+)@([\w\.-]+))"

Endi tahlil qilib ko'rsak: ([\w\.-]+)@([\w\.-]+) ifodamizni to'lig'i. 1-qavsda kelgan burchakli qavs ichida biz belgilar kategoriyamizni tuzdik: \w - harf yoki raqam, \. - nuqta(agar oddiy nuqta bo'lsa maxsus qobiliyatidan foydalanib qo'yadi :) ), chiziqcha(- bir o'zi oddiy belgi bo'lib keladi, oraliqlarda boshqacha), Shulardan keyin kuchukcha va yana boyagi ifodamiz. Bu orqali yuqorida o'ylab qo'ygan <username>@<domen> qolipimizni yaxshiroq tushuntirdik.

Regex da ba'zi funksiyalar.

Ifoda tuzishni biroz o'rganib oldik, endi ba'zi funksiyalarni ko'rib o'tsak.

regex_match() - ushbu funksiya haqida yuqorida aytib o'tgandim. m argumentiga kelsak, u berilayotgan belgilar to'plami string yoki char massiv shaklida bo'lishiga qarab smatch yoki cmatch tipida bo'ladi, o'ziga ifoda bo'yicha topilgan ma'lumotni oladi, masalan:

string mystr;
 regex pattern_email(R"(([\w\.-]+)@([\w\.-]+))");
 smatch mch;
 cin>>mystr;
 if(regex_match(mystr, mch, pattern_email))
   for(auto x: mch) cout<<x;

agar mystr char massiv bo'lsa, mch o'zgaruvchisining tipi cmatch bo'lishi kerak.

smatch/ cmatch ni shunchaki chop etolmaysiz, buning uchun chiqarish operatorini qayta yuklashga to'g'ri keladi. Yaxshisi, sikldan foydalaning, yoki nom.str() qilib ko'ring, bunda nom smatch/cmatch tipidagi o'zgaruvchi

regex_search(s, m, rgx) - bu ham xuddi match ga o'xshaydi, berilgan satr ichidan rgx ifodaga mosini qidiradi, agar bo'lsa uni m ga beradi.

regex_replace() - 3 ta argument oladi: birorta matn, regex ifoda, va almashtiriladigan so'z. Matnda ifodaga mos belgilar bo'lsa, ularni almashtiriladigan so'z bilan almashtirib qaytaradi. Namuna:

string mystr="998901234567 2532 24462334 998907777777";
 regex regexp(R"([0-9]{9,12})");
 cout<<regex_replace(mystr, regexp, "telefon raqam");

Natija:

telefon raqam 2532 24462334 telefon raqam

ifodamizda [0-9] ni tushundingiz, {9,12} - shakli: {x, y}, o'zidan oldin kelgan belgini minimal x marta, maksimal y marta takrorlanishi kerakligini bildiradi, ifodamizda esa o'sha 0-9 gacha raqamlar minimal 9 marta, maksimal 12 marta qatnashsin degani. Agar biz uni shunchaki {9,} qilsak, unda kamida 9 marta qatnashsin degani bo'ladi.

Yuqoridagi namuna telefon raqamlar uchun yana ozroq mukammallashtirishingiz mumkin bo'ladi, masalan \+?9989[01349][0-9]{7} qilib. E'tibor qilgan bo'lsangiz, yuqorida {9,} ifodasi birorta belgi kamida 9 marta qatnashsin deganini bildiradi degandik. bunda esa {7} faqat 7 marta qatnashsin degani, vergulga e'tibor bering.

regex_iterator() - keling, buni ko'rishdan oldin quyidagi matnni tahlil qilib ko'ramiz: "Men kecha o'zimning e-mailim emailnom@gmail.comdan support@gmail.comga xat yubordim" matnidagi barcha email larni oladigan dastur tuzaylik. Buni regex_search bilan eplasa bo'ladi dersiz, lekin u qolipga tushadigan ma'lumotlarni birinchisini oladi-da, bizga hammasi kerak! Balkim, sikl yordamida regex_search, regex_match va regex_replace larni qo'llamoqchidirsiz, yomonmas. Lekin regex_iterator bilan kodlash bu ishda oson (iterator - takrorlovchi degani)! regex_iterator char massiv bilan ishlash uchun, string bilan esa sregex_iterator ishlaydi. Biz string da ish ko'ramiz, sababi string bilan ishlashda C-string(const char *) ga qaraganda bir qancha ustunlik tomonlari bor, shuning uchun namunani sregex_iterator da ko'rib o'tamiz. Biz har bir topilgan ma'lumotni chop etishimiz uchun sikldan foydalanamiz, faqat bunda sanagichimiz sregex_iterator turida bo'ladi. Har doimgidek namuna kodini ko'rsataman, so'ng uni tahlil qilamiz:

string mystr="Men kecha o'zimning e-mailim emailnom@gmail.comdan support@gmail.comga xat yubordim";
regex regexp(R"(([\w\.-]+)@([\w\.-]+))");
sregex_iterator it(mystr.begin(), mystr.end(), regexp);
sregex_iterator it_end;
while(it!=it_end)
{
  smatch i=*it;
  cout<<i.str()<<endl;
  ++it;
}

3-qatordan boshlasak. Tahlil qilinishi kerak bo'lgan matn stringda, shuning uchun sregex_iterator dan foydalanamiz. Shu tipda it degan o'zgaruvchi olyabmiz, u matnimiz boshidan oxirigacha regexp qolipimiz bo'yicha ma'lumot qidiradi. Umuman olganda, iteratorni pointer bilan umumiy xususiyatlari ko'p, lekin bir muncha murakkab. Siz uni xuddi pointer kabi oshirishingiz mumkin. sregex_iterator va regex_iterator ning o'ziga xos xususiyati shunda-ki, ushbu tiplarda o'zgaruvchi e'lon qilsangiz va unga hech narsani o'zlashtirmasangiz, u avtomatik ravishda oxiriga o'tdi deb qaraladi. 3-qatordagi iteratorga qarshi 4-qatordagisini sinab ko'ramiz: ular bir-biriga teng bo'lguncha it o'zgaruvchisini smatchga o'zlashtirib chop etamiz, it ni bittaga oshiramiz, va ekranda e-mail manzillarni ko'rishimiz mumkin!

Foydalanilgan manbalar:

https://en.cppreference.com/w/cpp/regex/

https://www.informit.com/articles/article.aspx?p=2079020#:~:text=The%20new%20C%2B%2B11,few%20lines%20of%20C%2B%2B%20code.

http://www.cplusplus.com/reference/regex/ECMAScript/