Kihagyás

5. gyakorlat

A C++ <regex> header

Az itteni regex motor is támogatja többek közt ezeket a műveleteket:

  • illeszkedik-e egy teljes input szöveg egy regexre (match bool), a std::regex_match függvénnyel
  • illeszkedés esetén capturing groupok tartalmának kigyűjtésére (match groups), a regex_matchnek megadva egy std::smatch objektumot is, melybe az illeszkedés részletei kerülnek, pl. a csoportok;
  • input szövegben az input regexre első illeszkedő substring megkereresése (search), a std::regex_search függvénnyel
  • input szövegben az input regexre illeszkedő substringek, vagy közülük csak az első cseréje valamire (replaceAll, replace), a std::regex_replace függvénnyel

A lenti példák egyben itt vannak.

match

Lássunk egy egyszerű "illik-e vagy sem" kódot: megkeressük (ggmarkk után szabadon), hogy az input stringünkben vajon szerepel-e (külön szóként) a "telefon" szó.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <regex>               // std::regex osztály, std::regex_match függvény, C++11 óta része
#include <string>              // std::string
#include <iostream>            // std::cout, std::endl
// using namespace std;        // ízlés dolga, hogy ki szereti behúzni az egész névteret
// using std::string;          // vagy csak egy-egy osztályt belőle
                               // vagy kiírja mindig, hogy std::string, string helyett
...
// arra a stringekre illik, melyek tartalmazzák a "telefon" szót
std::regex  theRegex(".*\\btelefon\\b.*");        

// c++ alapból használjunk std::stringet, ha lehet   
std::string theText = "alma körte telefon barack";

if( std::regex_match(                              // regex_match: bool, illik-e
      theText,                                     // erre a std::string vagy char* szövegre 
      theRegex ) )                                 // ez a std::regex
{
  std::cout << "a regex illik a szövegre" << std::endl;
} else {
  std::cout << "a regex NEM illik a szövegre" << std::endl;
}

groups

Ha az illesztést úgy szeretnénk elvégezni, hogy utána a capturing groupokkal is szeretnénk kezdeni valamit, adnunk kell egy std::regex_match referenciát a regex_matchnek, második argumentumként. Ez egy template osztály, ugyanazzal a karaktertípussal kell példányosítsuk, mint ami az input text stringnek a karaktertípusa -- de két példányosztály aliasolva van nekünk előre: ha char * a textünk, akkor az std::cmatch osztályt, ha pedig string, akkor az std::smatch osztályt használhatjuk.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// C++ban megadhatunk R"( ... )" delimeterek közt "raw string literálokat" is,
// ezeken belül nincs escapelés, így nem kell \\ -elnünk, mint Javában kellett
std::regex theRegex( R"(\s*((?:\S+(?:\s+\S+)*)?)\s*)" );

string theText = "   a whitespace duplaplusz nemjó   ";

// ebbe fognak kerülni a csoportok
std::smatch results;                                                    

// text, match, regex a sorrend, ha illik, akkor feltölti a match-be a csoportokat
if( std::regex_match( theText, results, theRegex ) )
{
  // a match olyan kb. mint egy vector: van neki mérete  
  cout << "we have a match! Groups count: " << results.size() << endl;

  for( int i = 0; i < results.size(); i++ )
  {
    // és tömbelem-kiválasztás [] operátora is, visszaadja az i. csoportot
    cout << "Group " << i << " is *" << results[i] << "*" << endl;
  }
} else {
  cerr << "Sumthin is wrong"; //should never happen, ez a regex mindenre illeszkedik
}
Ennek a fenti hívásnak az eredményeképp megkapjuk, hogy két csoport van, a nulladikba került a teljes string, az elsőbe pedig a a whitespace duplaplusz nemjó string.

Ha pedig valamiért char* az input:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
std::regex theRegex( R"(\s*((?:\S+(?:\s+\S+)*)?)\s*)" );

// most char* a text
char* theText = "   a whitespace duplaplusz nemjó   ";

//ezért cmatch-ot használunk
std::cmatch results;                                                   

if( std::regex_match( theText, results, theRegex ) )
{
  cout << "we have a match! Groups count: " << results.size() << endl;
  for( int i = 0; i < results.size(); i++ )
  {
    cout << "Group " << i << " is *" << results[i] << "*" << endl;
  }
} else {
  cerr << "Sumthin is wrong"; //should never happen, ez a regex mindenre illeszkedik
}
Az output persze ugyanaz, mint volt az előbb is.

Ha regex_match helyett a regex_search függvényt hívjuk, ez szintén egy boolt ad vissza, hogy van-e az input textben az input regexre illeszkedő substring; ha igen, akkor az első illeszkedő substringgel frissíti a paraméterként kapott match_resultot, ha kapott olyat:

1
2
3
4
5
6
7
regex theRegex( "[+-]?\\d+" );
string theText( "Ebben a szövegben 3 szám és 12 szó van, vagy még több" );
std::smatch results;
if( std::regex_search( theText, results, theRegex ) )
{
  cout << "Egy szám: " << results[0] << endl;            // prints '3'
}
Ha szeretnénk olyan funkcionalitást, amilyen pl. a grepnek van és megkeresni az összes illeszkedő stringet, akkor pl. iterálhatjuk a regex_searchet úgy, hogy minden lépésben az input stringnek a match utáni részére hívjuk (tehát ez nem olyan, mint a Java find(int i) metódusa). Ezt a stringet állítja nekünk elő a match_results osztály suffix() függvénye:

1
2
3
4
5
6
7
8
regex theRegex( "[+-]?\\d+" );
string theText( "Ebben a szövegben 3 szám és 12 szó van, vagy -1 több" );
std::smatch results;
string tempString = theText; // ne rontsuk szét az eredeti szöveget
while( std::regex_search( tempString, results, theRegex ) ) {
  cout << "Egy szám: " << results[0] << endl;            //prints '3', then prints '12', then prints -1
  tempString = results.suffix();
}
Érdemes lehet tudni, hogy a fenti kód nem készít új másolatot a suffix() metódus hívásakor, az így készült tempString ugyanannak stringnek a memóriaterületére fog mutatni. Maga a suffix() metódus nem is egy stringet ad vissza, hanem egy referenciát egy olyan objektumra, melyből pl. az értékadás operátorral egy string objektumot tud készíteni, de másolás nélkül.

replace

A csomag sed-like regex alapú search&replace allt is támogat a std::regex_replace függvényen keresztül. Ennek értelemszerű használatára egy példa:

1
2
3
4
regex theRegex( "[+-]?\\d+" ); // megint számjegyek, possibly előjellel
string theText = "Ebben a szövegben a 27 és a -42 fordulnak elő számként.";
string replaced = std::regex_replace(theText, theRegex, "SZÁM");
cout << replaced << endl; // prints Ebben a szövegben a SZÁM és a SZÁM fordulnak elő számként.
Ha nem pont ez a szándékunk, akkor flagekkel módosítható a működés: pl. a format_first_only flag beállításával csak az első előfordulást cseréljük:

1
2
3
4
5
6
7
8
regex theRegex("[+-]?\\d+"); //megint számjegyek, possibly előjellel, két csoportban
string theText = "Ebben a szövegben a 27 és a -42 fordulnak elő számként.";
string replaced = std::regex_replace(
  theText,   // miben cseréljen
  theRegex,  // a regexre illőt
  "SZÁM",    // mire cseréljen
  std::regex_constants::format_first_only );  // flagek, logikai vagyolva
cout << replaced << endl; // prints Ebben a szövegben a SZÁM és a -42 fordulnak elő számként.
A string, melyre cserélünk, tartalmazhatja még a következőket:

  • a $& jelzi magát a matchelt substringet
  • a $` jelzi a stringnek a match előtti részét (note: replaceAll hívásakor ez az előző match és az aktuális match közti rész lesz)
  • a $' a stringnek a match utáni részét (note: ez aposztróf, az előző meg backtick)
  • a $0, $1, stb. pedig a nulladik, első stb. capturing group tartalmát.

Így pl. ez a kód zárójelbe teszi az input szövegben a számok előjel utáni részét:

1
2
3
4
regex theRegex( "([+-]?)(\\d+)" );
string theText = "Ebben a szövegben a 27 és a -42 fordulnak elő számként.";
string replaced = std::regex_replace( theText, theRegex, "$1($2)" );
cout << replaced << endl; // prints Ebben a szövegben a (27) és a -(42) fordulnak elő számként.

Feladatok

  • Írjunk regexet, mely pontosan akkor illeszkedik egy input sorra, ha benne páros sok a betű szerepel. (Ehhez talán először rajzoljunk egy automatát, aztán ezt konvertáljuk algoritmussal regexbe.)
  • Írjunk regexet, mely pontosan akkor illeszkedik egy input sorra, ha benne páros sok a betű vagy páratlan sok b betű szerepel.
  • Írjunk regexet, mely pontosan akkor illeszkedik egy input sorra, ha benne páros sok a betű és páratlan sok b betű szerepel.
  • Az előzőekben megadott páros-páratlan regexek illesztési sebességét mérjük ki Javában és egreppel is oly módon, hogy
    • generáljunk egy nagy szöveges filet, sok hosszú sorral, melyekben random a, b és c betűk vannak
    • futtassuk le az illesztést a regexünkkel, melyet készítettünk
    • futtassunk le egy olyan programot, mely két egyszerűbb regex egymás utáni illesztésével végzi el a feladatot
    • vonjunk le tanulságot!

Utolsó frissítés: 2021-10-23 18:39:09