8.3 Regulární výrazy

Jednoduché úpravy řetězců popsané výše a hledání, jaké je obvyklé v programech typu Word nebo Excel nestačí, když řešíme nějakou složitější situaci. Řekněme, že potřebujeme v textu najít každé telefonní číslo a převést je na nějaký standardní tvar. Každé telefonní číslo se však skládá z jiných číslic a navíc mohou být tyto číslice i různě oddělené: telefonní číslo 123456789 je totéž, co 123 456 789+420 123 456 789 nebo +420-123-456-789. Podobné problémy nastávají i při zjišťování data: stejné datum může být např. zadané jako 1.6.2006, 1. 6. 20061. června 2006. K hledání a úpravám takto volně definovaných skupin znaků slouží regulární výrazy. Regulární výraz je řetězec, který obsahuje vzor (pattern), který popisuje určitý objem textu. Řetězec se pak prochází a hledají se ty jeho části, které splňují daný vzor.

Bohužel se regulární výrazy implementované v různých programech od sebe mírně liší. Z historických důvodů se dnes v R používají tři různé typy regulárních výrazů: základní funkce v R používají regulární výrazy standardu POSIX 1003.2 nebo (na požádání) regulární výrazy podle standardu jazyka Perl. Balík stringr používá regulární výrazy založené na ICU. Naštěstí se od sebe různé standardy liší jen v různých nadstavbách. Zde se podíváme na základ, který je společný všem třem výše zmíněným formám regulárních výrazů. (Plné definice těchto standardů najdete v dokumentaci funkcí: help("regex", package = "base") pro POSIX a help("stringi-search-regex", package = "stringi") pro ICU.)

Regulární výraz je řetězec. Některé znaky v něm se berou doslovně, jiné mají speciální význam, který může záviset na kontextu, ve kterém jsou použité. Prakticky používají regulární výrazy čtyři operace:

  • spojování – znaky zapsané za sebe se spojí do jednoho výrazu; např. abcd je spojení čtyř znaků, které fungují jako celek, jako řetězec “abcd”
  • logické “nebo” označené symbolem | vybírá jednu z možností; např. ab|cd znamená řetězec “ab” nebo řetězec “cd”
  • opakování – umožňuje říct, kolikrát se má nějaký řetězec v textu vyskytovat; k tomu poskytují regulární výrazy kvantifikátory, viz níže
  • seskupování – znaky zapsané do obyčejných závorek ( ) tvoří skupinu, tj. jeden celek, který se může např. opakovat, měnit priority vyhodnocování výrazů apod.; skupiny mají i speciální význam při vybírání částí regulárních výrazů, viz níže

Všechny znaky kromě [\^$.|?*+(){} se berou doslovně, tj. např. a znamená písmeno “a”, 1 znamená číslici “1” atd. Regulární výraz "jak" tedy najde v textu všechny výskyty slova “jak” všechně výskytů v jiných slovech, např. “jakkoli”; nenajde však “Jak”, protože regulární výraz chápe malá a velká písmena jako různé znaky.

Znaky [\^$.|?*+(){} mají speciální význam. To znamená, že výraz cože? nenajde doslovně řetězec “cože?” (ve skutečnosti najde řetězec “což” nebo řetězec “cože”). Stejně tak Ano. neznamená doslovně “Ano.”, nýbrž jakýkoli řetězec, ve kterém za “Ano” následuje jeden znak (např. “Ano!”).

8.3.1 Jakýkoli symbol

Tečka . označuje libovolný znak. Výraz ma.ka najde slova jako “matka”, “maska” nebo “marka”, ale ne “maka”.

8.3.2 Rozsahy

Hranaté závorky určují výčet platných znaků (character classes). Tak např. [Aa] najde všechny výskyty malého “a” a velkého “A”. Takže výrazu [Aa]dam vyhoví jak “Adam”, tak “adam”.

V případě číslic a ASCII znaků mohou hranaté závorky obsahovat i rozsah zadaný pomocí pomlčky. Pro nalezení číslic 0 až 4 např. není třeba psát [01234], nýbrž stačí [0-4]. Pokud se má pomlčka chápat v rozsahu doslovně, je třeba ji napsat jako první znak.

Hranaté závorky mohou obsahovat i negaci, ke které slouží znak ^ uvedený hned za otevírající hranatou závorkou. Výraz [^Aa] splňují všechny znaky mimo malého “a” a velkého “A”.

Pro některé často užívané výčty existují speciální symboly:

symbol význam výčtem
\d číslice [0-9]
\D není číslice [^0-9]
\w písmeno, číslice nebo podtržítko [a-zA-z0-9_]
\W cokoli, co není \w [^a-zA-z0-9_]
\s prázdný znak [ \t\n\r\f]
\S není prázdný znak [^ \t\n\r\f]
\b hranice slova
\B není hranice slova
\h horizontální mezera
\H není horizontální mezera
\v vertikální mezera
\V není vertikální mezera

Regulární výrazy v R mohou obsahovat i POSIXové rozsahy, které mají zvláštní tvar [:jméno:] a mohou se vyskytovat jen ve vnějším rozsahu, tj. jako [[:jméno:]] nebo např. [a[:jméno:]b] apod.

rozsah význam
[:lower:] malá písmena v locale počítače
[:upper:] velká písmena v locale počítač
[:alpha:] malá i velká písmena v locale počítač
[:digit:] číslice, tj. 0, 1, …, 9
[:alnum:] alfanumerické znaky, tj. [:alpha:] a [:digit:]
[:blank:] mezera a tabelátor
[:cntrl:] řídící znaky
[:punct:] !, ", #, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, >, ?, @, [, \, ], ^, _, `,{,

Rozdíl mezi \w a [:alnum:] spočívá v tom, že [:alnum:] obsahuje v českém locale i písmena s háčky a čárkami, zatímco \w obsahuje jen písmena ASCII. Pokud tedy uvažujete písmena, doporučuji používat vždy POSIXové rozsahy.

8.3.3 Kvantifikátory

Znaky *+?{ jsou kvantifikátory, které řídí počet opakování předchozího znaku, rozsahu nebo skupiny.

znak význam
* libovolný počet opakování (tj. vůbec nebo víckrát)
+ aspoň jeden výskyt (tj. jednou nebo víckrát)
? maximálně jeden výskyt (tj. vůbec nebo jednou)
{5} právě pětkrát
{3,5} nejméně třikrát a nejvýše pětkrát
{3,} nejméně třikrát
{,5} maximálně pětkrát

Příklady:

  • .* jakýkoli znak opakovaný libovolně krát, tj. jakýkoli řetězec včetně ""
  • \w+ aspoň jedno písmeno
  • [+-]?\d+ celé číslo (najde např. “+12”, “1”, “-3” apod.; z “3.14” najde “3” a “14”)

Kvantifikátory jsou “hladové”. To znamená, že najdou řetězec s maximální délkou, která vyhovuje vzoru. To se nemusí vždy hodit. Např. pokud chceme najít všechny přímé řeči, regulární výraz ".*" nebude fungovat, protože najde celý výraz mezi první a poslední uvozovkou:

s1 <- '"Už je to dobré," řekl Josef. "Pojdme si zaplavat."'
str_extract_all(s1, '".*"')  # jeden řetězec od první po poslední uvozovku
## [[1]]
## [1] "\"Už je to dobré,\" řekl Josef. \"Pojdme si zaplavat.\""

Problém vyřeší “líný” kvantifikátor, který najde řetězec s minimální délkou, který splňuje zadaný vzor. Z kvantifikátoru uděláte líný tak, že za něj připojíte otazník. Např. kvantifikátor pro nula až nekonečný počet výskytů * je hladový; jeho líná verze je *?:

str_extract_all(s1, '".*?"')  # vektor dvou řetězců, v každém je jedna přímá řeč
## [[1]]
## [1] "\"Už je to dobré,\""     "\"Pojdme si zaplavat.\""

8.3.4 Začátek a konec řetězce

Znak ^ uvedený na začátku regulárního výrazu znamená začátek řetězce (nebo ve speciálním případě řádku, viz dále).

Znak $ uvedený na konci regulárního výrazu znamená konec řetězce (nebo ve speciálním případě řádku, viz dále).

Výraz ^-+$ splní pouze řádky, které obsahují pouze pomlčky.

8.3.5 Skupiny

Skupiny ovlivňují priority při alternaci. Např. regulárnímu výrazu abc|def vyhoví řetězce “abc” a “def”. Naproti tomu výrazu ab(c|d)ef vyhoví řetězce “abcef” a “abdef”. (Normálně má spojení prioritu před alternací.)

Stejně tak ovlivňují priority při použití kvantifikátorů. Např. výrazu abc* vyhoví řetězce “ab”, “abc”, “abcc” atd. Naproti tomu výrazu (abc)* vyhoví prázdný řetězec "", “abc”, “abcabc” atd. (Normálně má kvantifikátor přednost před spojením.)

Skupiny navíc dávají regulárním výrazům “paměť”. První skupině odpovídá \1, druhé skupině \2 atd. Pokud jsou do sebe skupiny vložené, pak se jejich čísla počítají podle pořadí levé závorky. Tuto paměť je možné využít jak ve vyhledávacím řetězci, tak při náhradě regulárního výrazu (viz dále). Např. k nalezení pětiznakového palindromu (tj. slova, které je stejné, ať ho čteme zleva nebo zprava, např. kajak nebo madam) stačí regulární výraz (.)(.).\2\1.

8.3.6 Doslovný význam speciálních znaků

Pokud chcete zadat přímo speciální znak, je třeba jej zabezpečit pomocí zpětného lomítka. Skutečná tečka je tedy .\, hranatá závorka je \[ atd.

Bohužel je v R zpětné lomítko samo aktivním znakem. To znamená, že je třeba jej zabezpečit dalším zpětným lomítkem. Místo \d je tak třeba v R zapsat "\\d", místo \. je třeba zapsat "\\." atd.

8.3.7 Příklad: telefonní číslo

Regulární výraz, který pozná všechny výše uvedené formáty telefonního čísla, vypadá takto: (\+420)?[\s-]*\d{3}[\s-]*\d{3}[\s-]*\d{3}. V R však musí být zapsán jako

r <- "(\\+420)?[-\\s]*\\d{3}[-\\s]*\\d{3}[-\\s]*\\d{3}"
cisla <- c("Leoš: 777 666 555 444",
           "Lída: domů +420 734 123 456, práce 777-666-555",
           "Leona: nevím")
str_extract_all(cisla, r)
## [[1]]
## [1] " 777 666 555"
## 
## [[2]]
## [1] "+420 734 123 456" " 777-666-555"    
## 
## [[3]]
## character(0)

8.3.8 Příklad: datum

Regulární výraz, který pozná všechny výše uvedené formáty data:

r <- "\\d{1,2}\\.\\s*(\\d{1,2}\\.|leden|únor|březen|duben|červen|červenec|srpen|žáří|říjen|listopad|prosinec)\\s*\\d{4}"
dat <- c("1. 6. 2001", "1.1.2012", "1. červen 2016", "ahoj")
str_detect(dat, r)
## [1]  TRUE  TRUE  TRUE FALSE

8.3.9 Další zdroje k regulárním výrazům

Regulární výrazy obsahují mnohem více než to, co jsem zde ukázal. Plný výčet detailů je na těchto (podle mého mínění velmi nepřehledných) stránkách: http://www.regular-expressions.info/reference.html.

Vyzkoušet si, co regulární výraz najde a co ne, si můžete interaktivně na stránce http://regexr.com/.

8.3.10 Rady Hadleyho Wickhama

“When writing regular expressions, I strongly recommend generating a list of positive (pattern should match) and negative (pattern shouldn’t match) test cases to ensure that you are matching the correct components.”