Expresii rapide și regulate Sintaxă

1. Introducere

Pur și simplu puse, expresiile regulate (regexes sau regexps pe scurt) sunt o modalitate de a specifica modele de șir. Sunteți fără îndoială familiarizați cu funcția de căutare și înlocuire în editorul dvs. de text preferat sau IDE. Puteți căuta cuvinte și expresii exacte. Puteți, de asemenea, să activați opțiuni, cum ar fi insensibilitatea cazurilor, astfel încât o căutare pentru cuvântul "culoare" să găsească și "Culoare", "COLOR" și "CoLoR". Dar dacă ați fi vrut să căutați variantele de ortografie ale cuvântului "color" (ortografie americană: culoare, ortografie engleză: culoare) fără a fi nevoie să efectuați două căutări separate?

Dacă acest exemplu pare prea simplu, ce zici dacă ați fi vrut să căutați toate variantele de ortografie ale numelui englez "Katherine" (Catherine, Katharine, Kathreen, Kathryn etc. pentru a numi câteva). Mai general, puteți căuta un document pentru toate șirurile de caractere care seamănă cu numerele hexazecimale, datele, numerele de telefon, adresele de e-mail, numerele cărților de credit etc..

Expresiile regulate reprezintă o modalitate puternică de a aborda (parțial sau integral) aceste (și multe alte) probleme practice care implică text.

Contur

Structura acestui tutorial este după cum urmează. Voi introduce conceptele de bază pe care trebuie să le înțelegeți prin adaptarea unei abordări utilizate în manualele teoretice (după îndepărtarea oricărei rigorii sau a pedantriei). Prefer această abordare, deoarece vă permite să vă înțelegeți probabil 70% din funcționalitatea de care veți avea nevoie, în contextul câtorva principii de bază. Restul de 30% sunt caracteristici mai avansate pe care le puteți învăța mai târziu sau puteți săriți, cu excepția cazului în care intenționați să deveniți un regizor maestru.

Există o cantitate abundentă de sintaxă asociată cu expresii regulate, dar cea mai mare parte este doar acolo pentru a vă permite să aplicați ideile de bază cât mai succint posibil. Voi introduce aceste incrementale, mai degrabă decât aruncând o masă sau o listă mare pentru memorare.

În loc să săriți direct într-o implementare Swift, vom explora elementele de bază printr-un instrument online excelent, care vă va ajuta să proiectați și să evaluați expresiile regulate cu cantitatea minimă de frecare și bagaje inutile. Odată ce vă simțiți confortabil cu ideile principale, scrierea codului Swift este, în esență, o problemă de cartografiere a înțelegerii dvs. în API-ul Swift.

Pe parcurs, vom încerca să păstrăm o mentalitate pragmatică. Regexurile nu sunt cel mai bun instrument pentru fiecare situație de procesare a șirului. În practică, trebuie să identificăm situațiile în care regexurile funcționează foarte bine și situațiile în care nu există. Există, de asemenea, un teren intermediar în care regexurile pot fi folosite pentru a face o parte a locului de muncă (de obicei, unele preprocesare și filtrare), iar restul lucrării rămân în logica algoritmică.

Concepte de baza

Expresiile regulate au temeiurile lor teoretice în "teoria computării", unul dintre subiectele studiate de știința calculatoarelor, în care acestea joacă rolul inputului aplicat unei anumite clase de mașini computerizate abstracte numite automate finite.

Relaxați-vă, totuși, nu sunteți obligat să studiați fundalul teoretic pentru a folosi expresiile regulate practic. Le menționez doar pentru că abordarea pe care o voi utiliza pentru a iniția inițial expresii regulate de la bază, reflectă abordarea folosită în manualele de informatică pentru a defini expresii regulate "teoretice".

Presupunând că aveți o anumită familiaritate cu recursiunea, aș vrea să rețineți ce funcții recursive sunt definite. O funcție este definită în termeni de versiuni mai simple ale acesteia și, dacă trasează printr-o definiție recursivă, trebuie să ajungeți la un caz de bază care este definit explicit. Eu aduc acest lucru, deoarece definiția noastră de mai jos va fi recursivă.

Rețineți că atunci când vorbim despre șiruri în general, implicit avem un caracter în minte, cum ar fi ASCII, Unicode etc. Să ne prefacem pentru moment în care trăim într-un univers în care șirurile sunt compuse din cele 26 de litere ale literelor mici alfabet (a, b, ... z) și nimic altceva.

reguli

Începem prin a afirma că fiecare caracter din acest set poate fi privit ca o expresie regulată care se potrivește ca un șir. Asa de A ca expresie regulată, se potrivește cu "a" (considerată ca un șir), b este un regex care se potrivește cu șirul "b", etc. Să spunem, de asemenea, că există o expresie regulată "goală" ɛ care se potrivește cu sirul gol "". Astfel de cazuri corespund cazurilor triviale de bază ale recursului.

Acum, considerăm următoarele reguli care ne ajută să facem noi expresii regulate față de cele existente:

  1. înlănțuire (adică "strângerea împreună") a oricăror două expresii regulate este o nouă expresie regulată care se potrivește cu concatenarea oricăror două șiruri care corespund expresiilor regulate originale.
  2. alternanţă din două expresii regulate este o nouă expresie regulată care se potrivește cu oricare dintre cele două expresii regulate originale.
  3. Starul Kleene a unei expresii regulate se potrivește cu zero sau mai multe instanțe adiacente, indiferent de potrivirea expresiei regulate originale.

Să facem acest lucru concret cu câteva exemple simple cu șirurile noastre alfabetice.

Exemplul 1

Din regula 1, A și b fiind expresii regulate care se potrivesc cu "a" și "b" ab este o expresie regulată care se potrivește cu șirul "ab". De cand ab și c sunt expresii regulate, abc este o expresie regulată care se potrivește cu șirul "abc" și așa mai departe. Continuând astfel, putem face expresii regulate lungi arbitrare care se potrivesc cu un șir cu caractere identice. Nimic interesant nu sa întâmplat încă.

Exemplul 2

Din regula 2, o și A fiind expresii regulate, o | o se potrivește cu "o" sau "a". Bara verticală reprezintă alternanța. c și T sunt expresii regulate și, combinate cu regula 1, putem afirma acest lucru c (o | a) T este o expresie regulată. Parantezele sunt utilizate pentru grupare.

Ce se potrivește?? c și T se potrivesc doar ei înșiși, ceea ce înseamnă că regexul c (o | a) T se potrivește cu "c", urmată de un "a" sau un "o" urmat de "t", de exemplu, șirul "pisică" sau "pătuț". Rețineți că se întâmplă nu meci "haina" ca o | o se potrivește doar cu "a" sau "o", dar nu simultan. Acum lucrurile încep să devină interesante.

Exemplul 3

Din regula 3, A* se potrivește cu zero sau mai multe exemple de "a". Se potrivește cu șirul gol sau cu șirurile "a", "aa", "aaa" și așa mai departe. Să exersăm această regulă împreună cu celelalte două reguli.

Ce face Fierbinte Meci? Se potrivește cu "ht" (cu zero cazuri de "o"), "hot", "hoot", "hooot" și așa mai departe. Ce ziceti b (o | a) *? Se poate potrivi cu "b" urmat de orice număr de cazuri "o" și "a" (inclusiv nici unul dintre acestea). "b", "boa", "baa", "bao", "baooaoaoaoo" sunt doar câteva din numărul infinit de șiruri de caractere care se potrivesc cu această expresie regulată. Rețineți din nou că parantezele sunt folosite pentru a grupa împreună o parte a expresiei regulate la care se face * este aplicată.

Exemplul 4

Să încercăm să descoperim expresii regulate care se potrivesc cu șiruri de caractere pe care le avem deja în minte. Cum am face o expresie regulată care recunoaște osteneala, pe care o voi considera ca orice repetare a sunetului de bază "baa" ("baa", "baabaa", "baabaabaa" etc.)

Dacă tu ai spus, (BAA) *, atunci ești aproape corect. Dar observați că această expresie regulată s-ar potrivi și cu șirul gol, pe care nu-l dorim. Cu alte cuvinte, dorim să ignorăm oile care nu se vântură. BAA (BAA) * este expresia regulată pe care o căutăm. În mod similar, ar putea fi vorba despre o vacă moo (moo) *. Cum putem recunoaște sunetul fiecărui animal? Simplu. Utilizați alternanța. BAA (BAA) * | moo (moo) *

Dacă ați înțeles ideile de mai sus, felicitări, sunteți bine în drum.

2. Probleme ale sintaxei

Reamintim că am pus o restricție prostie asupra corzilor noastre. Ele puteau fi compuse numai din litere mici ale alfabetului. Vom renunța acum la această restricție și vom lua în considerare toate șirurile compuse din caractere ASCII.

Trebuie să ne dăm seama că, pentru ca expresiile regulate să fie un instrument convenabil, ei înșiși trebuie să fie reprezentați ca șiruri de caractere. Deci, spre deosebire de cele de mai devreme, nu mai putem folosi caractere asemănătoare *, |, (, ), etc. fără a semnaliza cumva dacă le folosim ca personaje "speciale" reprezentând alternanță, grupare etc., sau dacă le trateazăm ca personaje obișnuite care trebuie să fie potrivite literal.

Soluția este de a trata aceste și alte "metacaractere" care pot avea un înțeles special. Pentru a comuta între o utilizare și cealaltă, trebuie să fim capabili să scăpăm de ei. Acest lucru este similar cu ideea de a folosi "\ n" (a scăpa de n) pentru a indica o nouă linie dintr-un șir. Este ceva mai complicat prin faptul că, în funcție de caracterul context care este în mod obișnuit "meta", ar putea reprezenta sinele său literal fără scăpare. Vom vedea exemple din aceasta mai târziu.

Un alt lucru pe care îl prețuim este concisitatea. Multe expresii regulate, care pot fi exprimate doar prin notarea secțiunii anterioare, ar fi cu totul greoaie. De exemplu, să presupunem că doriți să găsiți toate șirurile de caractere formate dintr-o literă mică, urmată de o cifră (de exemplu, șiruri precum "a0", "b9", "z3" etc.). Folosind notația discutată mai devreme, aceasta ar avea ca rezultat expresia regulată următoare:

(A | b | c | d | e | f | g | h | i | j | k | l | m | n | o | p | q | r | s | t | u | v | w | x | y | z) (0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9)

Doar tastând acel monstru mă șterge.

nu-i [Abcdefghijklmnopqrstuvwxyz] [0123456789] arata ca o reprezentare mai buna? Rețineți metacaractele [ și ] care semnifică un set de caractere, dintre care unul dă un rezultat pozitiv. De fapt, dacă luăm în considerare faptul că literele a la z, iar cifrele de la 0 la 9 apar în ordine în setul ASCII, putem scădea regexul până la o temperatură răcoroasă [A-z] [0-9].

În limitele unui set de caractere, liniuța, -, este un alt metacaracter care indică o gamă. Rețineți că puteți împinge mai multe intervale în aceeași pereche de paranteze pătrate. De exemplu, [0-9A-zA-Z] poate potrivi orice caracter alfanumeric. 9 și A (și  z și A)stoarse unul împotriva celuilalt ar putea părea amuzant, dar amintiți-vă că expresiile regulate se referă la scurtătate și înțelesul este clar.

Vorbind de coincidență, există și modalități mai clare de a reprezenta anumite clase de personaje înrudite așa cum vom vedea într-un minut. Rețineți că bara de alternanță, |, este încă o sintaxă valabilă și folositoare așa cum o vom vedea într-un moment.

Mai multă sintaxă

Înainte de a începe să practicăm, să aruncăm o privire la o sintaxă mai mică.

Perioadă

Perioada, ., potrivește un singur caracter, cu excepția rupturilor de linie. Aceasta înseamnă că CT se poate potrivi cu "cat", "crt", "c9t", "c% t", "c.t", "ct" Dacă vrem să potrivim perioada ca un caracter obișnuit, de exemplu, pentru a se potrivi cu șirul "c.t", am putea să scăpăm de el (CT) sau pune-l într-o clasă de caracter proprie (CT).

În general, aceste idei se aplică și altor metacaractere, cum ar fi [, ], (, )*, și altele pe care nu le-am întâlnit încă.

parantezele

Parentheses (( și )) sunt utilizate pentru grupare așa cum am văzut mai devreme. Vom folosi cuvântul jeton înseamnă fie un singur caracter, fie o expresie paranteză. Motivul este că se pot aplica și numeroși operatori de regex.

Parentheses sunt de asemenea folosite pentru a defini grupuri de capturare, permițându-vă să aflați care parte a meciului a fost capturat de către un anumit grup de captare în regex. Voi vorbi mai târziu despre această funcționalitate foarte utilă.

La care se adauga

A + urmând un simbol este unul sau mai multe instanțe ale acelui simbol. În exemplul nostru de ovăz, BAA (BAA) * ar putea fi reprezentate mai succint (BAA)+. Amintiți-vă că * înseamnă zero sau mai multe apariții. Rețineți că (BAA)+ este diferit de behehe+, pentru că în primul + se aplică la behehe token, în timp ce în ultimul caz se aplică numai A înainte de. În cel de-al doilea, se potrivește cu șiruri precum "baa", "baaa" și "baaaa".

Semnul întrebării

A ? urmând un simbol înseamnă zero sau o instanță a acestui simbol.

Practică

RegExr este un instrument excelent online pentru a experimenta cu expresii regulate. Când sunteți confortabil să citiți și să scrieți expresii regulate, va fi mult mai ușor să utilizați API expresie regulată din cadrul Fundației. Chiar și atunci, va fi mai ușor să testați expresia dvs. regulată în timp real pe site-ul web.

Vizitați site-ul și concentrați-vă asupra părții principale a paginii. Aceasta este ceea ce veți vedea:

Introduceți o expresie regulată în caseta din partea de sus și introduceți textul în care căutați potrivirile.

"/ G" la sfârșitul casetei de expresie nu face parte din expresia regulată per se. Este un drapel care afectează comportamentul global de potrivire al motorului regex. Prin adăugarea expresiei obișnuite "/ g", motorul caută toate potrivirile posibile ale expresiei regulate din text, care este comportamentul dorit. Indicatorul albastru indică o potrivire. Plasarea cu mouse-ul peste expresia obișnuită este un mod la îndemână de a vă reaminti semnificația părților sale constitutive.

Aflați că expresiile regulate vin în diferite arome, în funcție de limba sau biblioteca pe care o utilizați. Nu numai că aceasta înseamnă că sintaxa poate fi un pic diferită printre diferitele arome, dar și capabilitățile și caracteristicile. Swift, de exemplu, utilizează sintaxa de model specificată de UTI. Nu sunt sigur ce aromă este folosită în RegExr (care rulează pe JavaScript), dar în cadrul acestui tutorial, acestea sunt destul de similare, dacă nu identice.

Vă încurajez, de asemenea, să explorați panoul din stânga, care prezintă o mulțime de informații prezentate în mod concis.

Primul nostru exemplu practic

Pentru a evita confuzia potențială, ar trebui să menționez că, atunci când vorbim de potrivire expresie regulată, am putea însemna fie două lucruri:

  1. în căutarea oricăror (sau toate) subrețele ale unui șir care se potrivesc cu un regex
  2. verificând dacă șirul complet se potrivește cu expresia obișnuită sau nu

Semnificația implicită cu care funcționează motoarele regex este (1). Despre ce am vorbit până acum este (2). Din fericire, este ușor de implementat sensul (2) prin metacaractere care vor fi introduse mai târziu. Nu vă faceți griji pentru asta acum.

Hai să începem simplu, încercând exemplul nostru de amețire a oilor. Tip (BAA)+ în caseta de expresie și câteva exemple pentru a testa meciurile, după cum se arată mai jos.

Sper că înțelegeți de ce meciurile care au reușit au reușit și de ce celelalte au eșuat. Chiar și în acest exemplu simplu, există câteva lucruri interesante de subliniat.

Meciuri lacomă

Are șirul "baabaa" două potriviri sau unul? Cu alte cuvinte, este fiecare individ "baa" un meci sau este întreaga "baabaa" un singur meci? Acest lucru se referă la faptul că se caută sau nu un "joc lacom". O potrivire lacomă încearcă să se potrivească cât mai mult cu un șir posibil.

În momentul de față, motorul regex se potrivește cu lăcomie, ceea ce înseamnă că "baabaa" este un singur meci. Există modalități de a face potrivire leneș, dar acesta este un subiect mai avansat și, de vreme ce deja avem plăcile noastre pline, nu vom acoperi asta în acest tutorial.

Instrumentul RegExr lasă un decalaj mic, dar discertabil, în evidențierea dacă două părți adiacente dintr-un șir, fiecare individual (dar nu colectiv) se potrivesc cu expresia regulată. Vom vedea un exemplu de acest comportament într-un pic.

Majuscule și minuscule

"Baabaa" eșuează din cauza majusculă "B". Spuneți că doriți să permiteți doar primului "B" să fie majusculă, care ar fi expresia regulată corespunzătoare? Încearcă să-ți dai seama mai întâi de tine.

Un răspuns este (B | b) aa (BAA) *. Îți ajută dacă o citești cu voce tare. O majusculă sau majusculă "b", urmată de "aa", urmată de zero sau mai multe cazuri de "baa". Acest lucru este posibil, dar rețineți că acest lucru ar putea deveni rapid incomod, mai ales dacă vrem să ignorăm cu totul capitalizarea. De exemplu, ar trebui să specificăm alternativ pentru fiecare caz, ceea ce ar duce la ceva greu de genul ([Bb] [Aa] [Aa])+.

Din fericire, motoarele cu expresii regulate au de obicei opțiunea de a ignora cazul. În cazul RegExr, faceți clic pe butonul care afișează "steaguri" și bifați caseta de selectare "caz ignorat". Observați că litera "i" este prefixată la lista de opțiuni de la sfârșitul expresiei regulate. Încercați câteva exemple cu litere mici, precum "bAABaa".

Alt exemplu

Să încercăm să proiectăm o expresie regulată care să capteze variante ale numelui "Katherine". Cum ați aborda această problemă? Aș scrie mai multe variante, văd părțile comune și apoi încerc să exprim în cuvinte variațiile (cu accent pe scrisorile alternative și opționale) ca o secvență. Apoi, aș încerca să formulez expresia regulată care asimilează toate aceste variații.

Să încercăm cu această listă de variații: Katherine, Katharine, Catherine, Kathreen, Kathleen, Katryn și Catrin. Vă voi lăsa să vă scrieți mai multe dacă doriți. Privind aceste variații, pot spune în mod grosolan că:

  • numele începe cu "k" sau "c"
  • urmat de "la"
  • urmată eventual de un "h"
  • eventual urmată de un "a" sau "e"
  • urmat de un "r" sau "l"
  • urmată de una dintre "i", "ee" sau "y"
  • și cu siguranță urmată de un "n"
  • eventual un "e" la sfârșit

Având în vedere această idee, pot veni cu următoarea expresie regulată:

[Kc] ath [ae]? (R | l) (i | ee | y) ne?

Rețineți că primul rând "KatherineKatharine" are două meciuri fără nici o separare între ele. Dacă te uiți atent la acesta în editorul de text al RegExr, poți observa scurta pauză în evidențierea dintre cele două meciuri, despre care vorbeam mai devreme.

Rețineți că expresia regulată de mai sus se potrivește și cu nume pe care nu le-am considerat și care nu ar putea exista, de exemplu, "Cathalin". În contextul actual, acest lucru nu ne afectează negativ deloc. În unele aplicații, cum ar fi validarea prin e-mail, doriți să fiți mai specific cu privire la șirurile pe care le potriviți și pe cele pe care le respingeți. Aceasta, de obicei, adaugă la complexitatea expresiei regulate.

Mai multe sintaxe și exemple

Înainte de a merge la Swift, aș dori să discut mai multe aspecte ale sintaxei expresiilor regulate.

Concluzii reprezentative

Mai multe clase de caractere legate au o reprezentare concisă:

  • \ w alfanumeric, inclusiv sublinierea, echivalentă cu [A-zA-Z0-9_]
  • \ d reprezintă o cifră, echivalentă cu [0-9]
  • \ s reprezintă spațiul alb, adică spațiu, file sau linie

Aceste clase au, de asemenea, clase negative corespunzătoare:

  • \ W reprezintă un caracter non-alfanumeric, fără subliniere
  • \ D o cifră non-numerică
  • \ S un caracter non-spațial

Amintiți-vă clasele necapitalizate și amintiți-vă că capitalizarea corespunzătoare corespunde clasei necapitalizate. Rețineți că acestea pot fi combinate prin includerea unor paranteze pătrate, dacă este necesar. De exemplu, [\ S \ S] reprezintă orice caracter, inclusiv pauze de linie. Amintiți-vă că perioada . se potrivește cu orice caracter, cu excepția pauzelor de linie.

ancore

^ și $ sunt ancore care reprezintă începutul și sfârșitul unui șir respectiv. Amintiți-vă că am scris că ați putea dori să potriviți un șir întreg, mai degrabă decât să căutați potriviri subdirectori? Acesta este modul în care faceți asta. ^ C [OUA] t $ se potrivește cu "pisica", "pătuț" sau "tăiat", dar nu, să zicem, "prinde" sau "recuta".

Limite de cuvânt

\ b reprezintă o limită între cuvinte, cum ar fi spațiul sau punctuația, precum și începutul sau sfârșitul șirului. Rețineți că este ceva diferit în sensul că se potrivește mai degrabă cu o poziție decât cu un caracter explicit. S-ar putea să vă gândiți la o limită de cuvânt ca la un divizor invizibil care separă un cuvânt de cel precedent / următor. După cum v-ați aștepta, \ B reprezintă "nu o limită de cuvânt". \ Bcat \ b găsește meciuri în "pisică", "pisică", "pisică", dar nu în "acat" sau "captură".

Negare

Ideea negării poate fi făcută mai specifică folosind ^ metacaracter în interiorul unui set de caractere. Aceasta este o utilizare complet diferită ^ de la "începutul ancorării șir". Aceasta înseamnă că, pentru negare, ^ trebuie să fie folosit într-un set de caractere chiar la început. [^ A] se potrivește cu orice caracter alături de litera "a" și [^ A-z] se potrivește cu orice caracter, cu excepția unei litere mici.

Poți să reprezinți \ W folosind negarea și intervalele de caractere? Raspunsul este [^ A-Za-z0-9_]. Tu ce crezi [A ^] chibrituri? Răspunsul este un caracter "a" sau "^", deoarece nu a avut loc la începutul setului de caractere. Aici "^" se potrivește literalmente.

În mod alternativ, am putea să o scăpăm explicit în felul următor: [\ ^ A]. Sperăm că începeți să dezvoltați o anumită intuiție cu privire la modul în care funcționează evadarea.

cuantificatori

Am văzut cum * (și +) poate fi utilizat pentru a se potrivi cu un jeton zero sau mai mult (și unul sau mai multe) de ori. Această idee de potrivire a unui token de mai multe ori poate fi făcută mai specifică folosind cuantificatorii în bretele curbate. De exemplu, 2, 4  înseamnă două-patru meciuri ale jetonului precedent. 2 înseamnă două sau mai multe meciuri și 2 înseamnă exact două meciuri.

Vom analiza exemple detaliate care utilizează majoritatea acestor elemente în următorul tutorial. Dar, de dragul practicării, vă încurajez să vă compilați propriile exemple și să testați sintaxa pe care tocmai am văzut-o cu instrumentul RegExr.

Concluzie

În acest tutorial, ne-am concentrat în primul rând pe teoria și sintaxa expresiilor regulate. În tutorialul următor, adăugăm Swift la mix. Înainte de a continua, asigurați-vă că înțelegeți ce am acoperit în acest tutorial jucând în jur cu RegExr.

Cod