--- title: "Práce s řetězci" author: "Michal Kvasnička" documentclass: article output: html_document: theme: cerulean toc: yes toc_float: yes pdf_document: default fontsize: 10pt classoption: a4paper --- # Práce s řetězci {#kap:strings} Analýza dat nezahrnuje jen práci s\ čísly, ale i\ s\ řetězci (texty). Když pomineme textovou analýzu jako takovou, velmi často jsou data zabalena v\ textovém balastu a je třeba je z\ něj extrahovat. R\ má v\ základním balíku **base** mnoho užitečných funkcí pro práci s\ řetězci. Tyto funkce však mají často složité a vzájemně nekonzistenční rozhraní. Proto se zde místo nich podíváme na funkce implementované v\ balíku **stringr** (a\ také jednu funkci z\ balíku **glue**), který výrazně zjednodušuje práci s\ řetězci a stále pokrývá velkou většinu toho, co člověk potřebuje. (Balík **stringr** je uživatelsky přívětivý wrapper nad funkcemi balíku **stringi**; proto často vypisuje chyby ze **stringi** a stejně tak část dokumentace je třeba hledat ve **stringi**.) Pro práci s\ touto kapitolou je tedy nezbytné načíst balík **stringr** do paměti počítače: ```r library(stringr) ``` Jména všech funkcí z\ balíku **stringr** začínají `str_`. V\ této kapitole se naučíte - základy práce se řetězci - jak zjistit délku řetězce, - jak řetězce spojovat, duplikovat a zalamovat, - jak řetězce setřídit, - jak nahrazovat části řetězců, - jak pracovat s\ regulárními výrazy a - jak měnit chování regulárních výrazů a mnoho dalšího. ## Základy: řetězce v\ R R ukládá řetězce ve vektorech datového typu *character*. Řetězec se zadává mezi dvěma uvozovkami nebo dvěma apostrofy. Uvozovky a apostrofy nejde míchat, ale je možné uzavřít apostrof mezi uvozovky nebo naopak: ```r "Petr řekl: 'Už tě nemiluji.'" 'Agáta odpověděla: "Koho to zajímá?"' ``` Kromě uvozovek a apostrofů má v\ řetězcích zvláštní význam i\ zpětné lomítko `\`. To slouží ke dvěma účelům: 1)\ uvozuje speciální znaky jako např.\ `"\n"` (konec řádku), `"\t"` (tabelátor) apod., a\ 2)\ zbavuje speciální znaky jejich zvláštního významu. To provedeme tak, že zvláštnímu znaku předřadíme zpětné lomítko. Tímto způsobem je možné zbavit zvláštního významu i\ uvozovky, apostrofy a zpětná lomítka a uvést je uvnitř řetězce: ```r s <- "Petr na to odvětil: \"Je to proto, že vypadáš jako \\.\"" ``` Podobně můžete v\ řetězcích zadat i\ znaky Unicode pomocí `\u` a příslušného čísla. Znak velká omega ($\Omega$) je např.\ možné zadat jako `\u03a9`. (Tabulku Unicode najdete na adrese https://unicode-table.com/.) Funkce `print()`, která se používá při implicitním vypsání obsahu proměnné, vypisuje na začátku i\ na konci řetězců uvozovky a všechny speciální znaky vypisuje se zpětnými lomítky. Pokud chcete vypsat řetězec "normálně", můžete použít funkci `cat()`: ```r print(s) ``` ``` ## [1] "Petr na to odvětil: \"Je to proto, že vypadáš jako \\.\"" ``` ```r cat(s) ``` ``` ## Petr na to odvětil: "Je to proto, že vypadáš jako \." ``` Kromě funkcí `print()` a `cat()` existuje mnoho dalších funkcí pro výpis řetězců. Nejvýznamnější z\ nich uvádí tabulka\ \@ref(tab:strings-prints). Na detaily použití těchto funkcí se podívejte do dokumentace. Table: (\#tab:strings-prints) Vybrané funkce pro výpis řetězců. funkce | účel ------------ | ----------------------------------------- `print()` | generická funkce pro tisk objektů `noquote()` | tisk bez uvozovek `cat()` | tisk obsahu řetězců; spojuje více řetězců `format()` | formátování proměnných před tiskem `toString()` | konverze na řetězec `sprintf()` | formátování řetězců ve stylu jazyka\ C V\ R\ je potřeba rozlišovat mezi prázdným řetězcem (tj.\ řetězcem `""`) a prázdným vektorem řetězců: ```r s1 <- "" # vektor délky 1 obsahující prázdný řetězec length(s1) ``` ``` ## [1] 1 ``` ```r s2 <- character(0) # vektor typu character, který má nulovou délku, length(s2) # tj. neobsahuje žádné řetězce ``` ``` ## [1] 0 ``` ```r s3 <- character(5) # vektor pěti prázdných řetězců length(s3) ``` ``` ## [1] 5 ``` ```r s3 ``` ``` ## [1] "" "" "" "" "" ``` Všimněte si, že funkce `length()` vrací délku vektoru, ne délku řetězce, který vektor snad obsahuje. R má dva speciální vektory, které obsahují písmena anglické abecedy: ```r letters # malá písmena anglické abecedy ``` ``` ## [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" ## [20] "t" "u" "v" "w" "x" "y" "z" ``` ```r LETTERS # velká písmena anglické abecedy ``` ``` ## [1] "A" "B" "C" "D" "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S" ## [20] "T" "U" "V" "W" "X" "Y" "Z" ``` Pokud potřebujete porovnat, zda jsou dva řetězce stejné, můžete použít operátor\ `==` i\ funkci `all.equal()`. (Samozřejmě můžete použít i\ `>`, `<` apod. pro zjištění, který řetězec by by byl umístěn ve slovníku dříve; později v\ této kapitole však uvidíte funkce, které vám dají nad řazením řetězců větší kontrolu.) ```r "abc" == "abc" ``` ``` ## [1] TRUE ``` ```r "abc" == "ABC" ``` ``` ## [1] FALSE ``` ```r all.equal("abc", "abc", "abc") ``` ``` ## [1] TRUE ``` Poznámka: Počítač dokáže zpracovávat jen sekvence jedniček a nul. Aby bylo jasné, jak binární čísla interpretovat jako text, musí být jasné kódování tohoto textu. Každý řetězec může mít v\ R jiné kódování. Nativně umí R dvě kódování: ISO Latin\ 1 a UTF-8. Kromě toho zvládá i\ nativní kódování operačního systému počítače, na kterém běží. Většinou se o\ kódování nemusíte nijak starat. Problém může nastat jen v\ případě přenosu dat mezi různými operačními systémy (v\ rámci Windows i\ mezi různými lokalizacemi). Naštěstí umí R změnit kódování i\ převést text z\ jednoho kódování do jiného. Pokud potřebujete data přenášet mezi počítači, zvažte použití standardního kódování UTF-8. Základní dokumentaci ke kódování poskytuje `help("Encoding")`. Ke konverzi kódování řetězců je možné použít funkci `iconv()` z\ balíku **base** nebo funkci `str_conv()` z\ balíku **stringr**. Detaily použití obou funkcí najdete v\ dokumentaci. ## Základní operace Nejdříve se podíváme na základní operace s\ řetězci: na to, jak řetězce spojovat, třídit, replikovat, nahrazovat, srovnávat a zjišťovat jejich délku. Ve zbytku kapitoly se pak budeme věnovat pokročilejší práci s\ řetězci pomocí regulárních výrazů. ### Zjištění délky řetězce Často potřebujeme zjistit délku jednotlivých řetězců. To nemůžeme udělat pomocí funkce `length()` protože ta nevrací délku řetězce, ale délku vektoru řetězců. Nebude to fungovat ani v\ případě jediného řetězce, protože i\ to je vektor o\ délce\ 1. (Pamatujte, že R\ nemá skalár: to, co vypadá jako jeden řetězec, je ve skutečnosti vektor řetězců o\ délce\ 1.) Ke zjištění délky řetězce slouží funkce `str_length()`, která vrací vektor délek jednotlivých řetězců ve vektoru: ```r s1 <- "Ahoj!" s2 <- c("A", "Bb", "Ccc", NA) length(s1) # délka (= počet prvků) vektoru s1 ``` ``` ## [1] 1 ``` ```r length(s2) # délka (= počet prvků) vektoru s2 ``` ``` ## [1] 4 ``` ```r str_length(s1) # vektor délek (= počtu znaků) řetězce s1 ``` ``` ## [1] 5 ``` ```r str_length(s2) # vektor délek (= počtu znaků) řetězců ve vektoru s2 ``` ``` ## [1] 1 2 3 NA ``` Pozor: Technicky vzato vrací funkce `str_length()` počet tzv.\ "code points". Ty většinou odpovídají jednotlivým znakům, ne však vždy. Např.\ znak\ `á` může být v\ paměti počítače reprezentován jako jeden znak nebo dva znaky (`a` a akcent). Ve druhém případě vrátí funkce `str_length()` délku\ 2. V\ takovém případě je bezpečnější počítat počet znaků pomocí funkce `str_count()`, viz dále. Příklad najdete v\ dokumentaci funkce. Pokud jsou však anglické i\ české znaky zadané obvyklým způsobem, pak bude funkce `str_length()` vracet počet znaků v\ jednotlivých řetězcích. ### Spojení řetězců Ke spojení více řetězců do jednoho slouží funkce `str_c(..., sep = "", collapse = NULL)`. Funkce bere libovolný počet řetězců, vypustí z\ nich prázdné vektory řetězců a `NULL` (ale ne prázdné řetězce `""`) a zbylé řetězce spojí do jednoho řetězce. Funkce `str_c()` implicitně odděluje jednotlivé řetězce prázdným řetězcem, tj.\ spojí jednotlivé řetězce těsně za sebe. Oddělovací řetězec je možné nastavit pomocí pojmenovaného parametru `sep`. (Všimněte si, že prázdný řetězec `""` funkce nevypustila.) ```r str_c("Jednou", "", "budem", character(0), "možná", NULL, "dál!") ``` ``` ## [1] "Jednoubudemmožnádál!" ``` ```r str_c("Jednou", "", "budem", character(0), "možná", NULL, "dál!", sep = " ") ``` ``` ## [1] "Jednou budem možná dál!" ``` ```r str_c("Jednou", "", "budem", character(0), "možná", NULL, "dál!", sep = "-") ``` ``` ## [1] "Jednou--budem-možná-dál!" ``` Někdy potřebujeme spojit vektory řetězců. Funkce `str_c()` spojí odpovídající prvky jednotlivých vektorů (s\ obvyklou recyklací kratších vektorů, při které funkce vypíše varování) a vrátí vektor (rozumnější použití uvidíte dále): ```r s1 <- c("a", "b", "c", "d") s2 <- 1:3 # automatická koerze převede čísla na řetězce str_c(s1, s2) ``` ``` ## Warning in stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE): ## longer object length is not a multiple of shorter object length ``` ``` ## [1] "a1" "b2" "c3" "d1" ``` Pokud navíc chceme výsledný vektor spojit do jednoho řetězce, můžeme použít parametr `collapse`, kterým se nastavuje řetězec oddělující jednotlivé dílčí vektory (funguje i\ prázdný řetězec\ `""`): ```r str_c(s1, s2, collapse = "-") ``` ``` ## Warning in stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE): ## longer object length is not a multiple of shorter object length ``` ``` ## [1] "a1-b2-c3-d1" ``` ```r str_c(s1, s2, collapse = "") ``` ``` ## Warning in stri_c(..., sep = sep, collapse = collapse, ignore_null = TRUE): ## longer object length is not a multiple of shorter object length ``` ``` ## [1] "a1b2c3d1" ``` Celkově funguje funkce `str_c()` takto: své parametry chápe jako vektory řetězců. Tyto vektory sestaví do matice, kde každý vstupní vektor tvoří jeden sloupec této matice (přitom prodlouží kratší vektory recyklací jeho prvků). Pak vloží mezi jednotlivé sloupce řetězec `sep` a spojí každý řádek do jednoho řetězce, takže výsledkem je jeden vektor řetězců. Pokud je navíc zadán řetězec `collapse` (tj.\ není\ `NULL`), funkce vloží tento řetězec mezi jednotlivé prvky tohoto vektoru a spojí je do jednoho řetězce. Tabulka\ \@ref(tab:strings-strc) ukazuje, co udělá výraz `str_c(s1, s2, sep = "-")`. Table: (\#tab:strings-strc) Ukázka toho, jak funguje výraz `str_c(s1, s2, sep = "-")`. `s1` | (sep) | `s2` | výsledek ---- | ----- | ---- | -------- a | - | 1 | a-1 b | - | 2 | b-2 c | - | 3 | c-3 d | - | 1 | d-1 Pokud má kterýkoli řetězec ve spojovaných vektorech hodnotu `NA`, pak je výsledné spojení také `NA`: ```r s1 <- c("a", "b", NA) s2 <- 1:3 str_c(s1, s2) ``` ``` ## [1] "a1" "b2" NA ``` ```r str_c(s1, s2, collapse = "-") ``` ``` ## [1] NA ``` Některé zajímavé příklady použití funkce `str_c()` jsme vybrali z\ dokumentace funkce: ```r str_c("Letter", letters, sep = ": ") ``` ``` ## [1] "Letter: a" "Letter: b" "Letter: c" "Letter: d" "Letter: e" "Letter: f" ## [7] "Letter: g" "Letter: h" "Letter: i" "Letter: j" "Letter: k" "Letter: l" ## [13] "Letter: m" "Letter: n" "Letter: o" "Letter: p" "Letter: q" "Letter: r" ## [19] "Letter: s" "Letter: t" "Letter: u" "Letter: v" "Letter: w" "Letter: x" ## [25] "Letter: y" "Letter: z" ``` ```r str_c(letters[-26], " comes before ", letters[-1]) ``` ``` ## [1] "a comes before b" "b comes before c" "c comes before d" "d comes before e" ## [5] "e comes before f" "f comes before g" "g comes before h" "h comes before i" ## [9] "i comes before j" "j comes before k" "k comes before l" "l comes before m" ## [13] "m comes before n" "n comes before o" "o comes before p" "p comes before q" ## [17] "q comes before r" "r comes before s" "s comes before t" "t comes before u" ## [21] "u comes before v" "v comes before w" "w comes before x" "x comes before y" ## [25] "y comes before z" ``` ```r str_c(letters, collapse = ", ") ``` ``` ## [1] "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" ``` ### Doplnění hodnot do řetězců Někdy potřebujeme spojit několik řetězců tak, že na vybraná místa do textu "vlepíme" hodnoty z\ vybraných proměnných\ -- např.\ když chceme sestavit výpis z\ účtu: ```r cislo_uctu <- 854397 zustatek <- 12365.30 str_c("Na účtu číslo ", cislo_uctu, " je aktuální zůstatek ", format(zustatek, big.mark = ".", decimal.mark = ",", nsmall = 2), " Kč.") ``` ``` ## [1] "Na účtu číslo 854397 je aktuální zůstatek 12.365,30 Kč." ``` Využít ke "vlepení" hodnot do řetězce funkci `str_c()`, jak to ukazuje výše uvedený příklad, je možné, ale výsledek není právě přehledný. Místo toho můžeme použít speciální funkci `glue()` z\ balíku **glue**: ```r library(glue) hezky_zustatek <- format(zustatek, big.mark = '.', decimal.mark = ',', nsmall = 2) glue("Na účtu číslo {cislo_uctu} je aktualni zustatek {hezky_zustatek} Kč.") ``` ``` ## Na účtu číslo 854397 je aktualni zustatek 12.365,30 Kč. ``` Funkce `glue()` vezme jeden nebo více řetězců, slepí je dohromady a na místa ve složených závorkách vloží hodnoty proměnných z\ aktuálního prostředí\ R, přesněji výsledek výpočtu, takže předchozí kód bychom mohli napsat i\ takto: ```r glue("Na účtu číslo {cislo_uctu} je aktualni zustatek ", "{format(zustatek, big.mark = '.', decimal.mark = ',', nsmall = 2)} Kč.") ``` ``` ## Na účtu číslo 854397 je aktualni zustatek 12.365,30 Kč. ``` Hodnoty vložených proměnných můžeme zadat přímo jako parametry funkce `glue()`: ```r glue("Na účtu číslo {cislo_uctu} je aktualni zustatek {zustatek} Kč.", cislo_uctu = 24683, zustatek = 123) ``` ``` ## Na účtu číslo 24683 je aktualni zustatek 123 Kč. ``` Funkce `glue()` má několik zajímavých vlastností: zahazuje počáteční a koncová bílá místa (mezery apod.) včetně prvního a posledního zalomení řádku, ostatní zalomení a mezery však respektuje: ```r glue(" Toto je jakýsi text. A zde pokračuje... ") ``` ``` ## Toto je jakýsi text. ## A zde pokračuje... ``` ```r glue(" Toto je jakýsi text. A zde pokračuje... ") ``` ``` ## ## Toto je jakýsi text. ## A zde pokračuje... ``` Zalomení řádků je možné zabránit pomocí (zdvojeného) lomítka na jeho konci: ```r glue("Zde nějaký text začíná, \\ a zde pokračuje.") ``` ``` ## Zde nějaký text začíná, a zde pokračuje. ``` Pokud z\ nějakého důvodu potřebujeme v\ textu použít složené závorky, máme dvě možnosti: buď je zdvojit, nebo pomocí parametrů `.open` a `.close` nastavit jiný počátek a konec nahrazované oblasti na jiný znak: ```r glue("Zde je {{jméno}}.") ``` ``` ## Zde je {jméno}. ``` ```r glue("Jméno výherce soutěže je {\\bf <>}.", .open = "<<", .close =">>", name = "Joe") # vhodné např. při exportu do LaTeXu ``` ``` ## Jméno výherce soutěže je {\bf Joe}. ``` Balík **stringr** nabízí podobnou funkcionalitu ve funkcích `str_glue()` a `str_interp()`. Funkce `str_glue()` je vpodstatě jen wrapper nad funkcí `glue()` a používá se stejně: ```r str_glue("Jmenuji se {name} a příští rok mi bude {age + 1} let.", name = "Jana", age = 40) ``` ``` ## Jmenuji se Jana a příští rok mi bude 41 let. ``` Naproti tomu funkce `str_interp()` má poněkud jiné použití. V\ jejím případě musí mít nahrazovaný text buď podobu `${}`, kde ve složených závorkách je nějaký výraz, nebo `$[]{}`, kde v\ hranatých závorkách je formát a v\ složených výraz. Formát určuje typicky formátování čísel\ -- např.\ `.2f` znamená, že se vytisknou dvě desetinná místa (detaily formátu viz nápověda k\ funkci `sprintf()`). Pokud chceme zadat vkládané hodnoty přímo v\ této funkci, je třeba je zabalit do seznamu. ```r str_interp("Na účtu číslo ${cislo_uctu} je aktualni zustatek ${zustatek} Kč.") ``` ``` ## [1] "Na účtu číslo 854397 je aktualni zustatek 12365.3 Kč." ``` ```r str_interp("Na účtu číslo $[d]{cislo_uctu} je aktualni zustatek $[.2f]{zustatek} Kč.") ``` ``` ## [1] "Na účtu číslo 854397 je aktualni zustatek 12365.30 Kč." ``` ```r str_interp("Na účtu číslo $[d]{cislo_uctu} je aktualni zustatek $[.2f]{zustatek} Kč.", list(cislo_uctu = 51349, zustatek = 17)) ``` ``` ## [1] "Na účtu číslo 51349 je aktualni zustatek 17.00 Kč." ``` ### Řazení řetězců Pro setřídění řetězců většinou stačí základní funkce `sort()` a `order()`. Pro složitější případy, kdy je např.\ třeba třídit v\ cizím *locale*, nabízí balík **stringr** dvě funkce: - `str_order(x, decreasing = FALSE, na_last = TRUE, locale = "en", numeric = FALSE, ...)` vrací celé číslo, které odpovídá pořadí daného řetězce ve vektoru\ `x` (podobně jako funkce `order()`) - `str_sort(x, decreasing = FALSE, na_last = TRUE, locale = "en", numeric = FALSE, ...)` setřídí vektor řetězců\ `x` Přitom `x` je vektor řetězců, který má být setříděn. `decreasing` je logická hodnota (implicitní hodnota je `FALSE`; pak třídí od nejnižšího k\ nejvyššímu; `TRUE` třídí od nejvyššího k\ nejnižšímu). `na_last` je logická hodnota (implicitní hodnota je `TRUE`, při které funkce umístí hodnoty `NA` na konec vektoru; `FALSE` je umístí na začátek na začátek vektoru; `NA` je vyhodí). `locale` označuje v\ jakém *locale* se má třídit (implicitně v\ systémovém). Pokud je logická hodnota `numeric` nastavena na `TRUE`, pak číslice řadí jako čísla, ne jako řetězce; implicitní hodnota je `FALSE`. `...` označují další parametry přidané do `stri_opts_collator`. ```r str_order(letters, locale = "en") ``` ``` ## [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 ## [26] 26 ``` ```r str_sort(letters, locale = "en") ``` ``` ## [1] "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o" "p" "q" "r" "s" ## [20] "t" "u" "v" "w" "x" "y" "z" ``` ### Výběr a náhrada pomocí indexů Někdy je třeba z\ řetězce vybrat jeho část. Pokud známe pozici prvního a posledního znaku, který chceme vybrat, můžeme použít funkce `str_sub(string, start = 1L, end = -1L)`, kde `string` je řetězec, ze kterého vybíráme, `start` je pozice znaku začátku výběru a `end` je pozice konce výběru (včetně). Implicitní hodnota `start` je\ 1 (tj.\ od začátku vektoru), implicitní hodnota `end` je\ $-1$ (tj.\ do konce vektoru). Pozice znaků mohou být zadány i\ jako záporná čísla\ -- ta se počítají od konce vektoru, takže např.\ $-1$ je poslední znak vektoru. ```r s1 <- "Sol 6: Katastrofa na Marsu." str_sub(s1, start = 8, end = str_length(s1)) ``` ``` ## [1] "Katastrofa na Marsu." ``` ```r str_sub(s1, start = 8, end = -1) # totéž ``` ``` ## [1] "Katastrofa na Marsu." ``` ```r str_sub(s1, 8) # totéž ``` ``` ## [1] "Katastrofa na Marsu." ``` ```r str_sub(s1, end = 5) ``` ``` ## [1] "Sol 6" ``` Funkce `str_sub()` recykluje všechny své parametry. Pokud chceme např.\ vybrat stejné pozice z\ každého vektoru v řetězci: ```r s2 <- c(s1, "Sol 7: Nová naděje.") str_sub(s2, start = c(8, 8)) ``` ``` ## [1] "Katastrofa na Marsu." "Nová naděje." ``` ```r str_sub(s2, 8) # totéž ``` ``` ## [1] "Katastrofa na Marsu." "Nová naděje." ``` Stejně tak je však možné recyklovat i\ řetězec a vybrat z\ něj naráz dva pod-řetězce: ```r str_sub(s1, start = c(8, 22), end = c(17, 26)) ``` ``` ## [1] "Katastrofa" "Marsu" ``` Pokud je řetěz kratší než výběr znaků, vrátí funkce `str_sub()` tolik znaků, kolik může (někdy i\ prázdný řetězec): ```r str_sub(s1, start = 25, end = 50) ``` ``` ## [1] "su." ``` ```r str_sub(s1, start = 40, end = 50) ``` ``` ## [1] "" ``` Funkci `str_sub()` je možné použít i\ k\ náhradě části řetězce: ```r str_sub(s1, 22, 26) <- "rudé planetě" s1 ``` ``` ## [1] "Sol 6: Katastrofa na rudé planetě." ``` ### Replikace řetězců Někdy je potřeba nějaký řetězec "zmnožit". K\ tomu slouží funkce `str_dup(string, times)`, který vezme vektor\ `string`, zopakuje jej `times`-krát a výsledek spojí. To se hodí např.\ při načítání mnoha textových sloupců pomocí balíku **readr**. Při tom je někdy užitečné říct funkci `read_csv()`, že všechny sloupce tabulky mají typ `character`. K\ tomu slouží řetězec mnoha "c": ```r str_dup("c", 28) ``` ``` ## [1] "cccccccccccccccccccccccccccc" ``` Funkce `str_dup()` také recykluje všechny své parametry: ```r str_dup(c("a", "b", "c"), 1:3) ``` ``` ## [1] "a" "bb" "ccc" ``` ### Odstranění okrajových mezer Někdy dostaneme řetězec, který začíná nebo končí "bílými znaky" (mezerami, tabelátory, novými řádky "`\n`" apod.). Tato situace vznikne např.\ tehdy, když řetězec vznikl rozdělením delšího řetězce na části. Tyto bílé znaky je často vhodné odstranit. K\ tomu slouží funkce `str_trim(string, side)`, kde `string`\ je řetězec a `side` označuje stranu, ze které se mají bílé znaky odstranit: ```r s1 <- c("Ahoj,", " lidi, ", "jak ", "se", " máte?") str_trim(s1, "left") # odstraní mezery zleva ``` ``` ## [1] "Ahoj," "lidi, " "jak " "se" "máte?" ``` ```r str_trim(s1, "right") # odstraní mezery zprava ``` ``` ## [1] "Ahoj," " lidi," "jak" "se" " máte?" ``` ```r str_trim(s1, "both") # odstraní mezery z obou stran ``` ``` ## [1] "Ahoj," "lidi," "jak" "se" "máte?" ``` ```r str_trim(s1) # totéž -- "both" je implicitní hodnota side ``` ``` ## [1] "Ahoj," "lidi," "jak" "se" "máte?" ``` Funkce `str_squish(string)` funguje podobně, ale odstraní navíc i\ všechny přebytečné prázdné znaky\ -- na začátku řetězce `string`, na jeho konci i\ opakované znaky uvnitř: ```r str_squish(" Toto je koktavý text. ") ``` ``` ## [1] "Toto je koktavý text." ``` ### Zarovnání a ořež řetězců na stejnou délku a do odstavce Někdy je užitečné zarovnat řetězce na stejnou délku přidáním bílých (nebo jiných) znaků. K\ tomu slouží funkce `str_pad(string, width, side, pad = " ")`, kde `string` je vektor zarovnávaných řetězců, `width` je minimální délka výsledného řetězce, `side` je strana, na kterou se mají výplňové znaky přidat (implicitně je to `left`) a `pad` je výplňový řetězec (implicitně mezera). ```r str_pad(c("Ahoj", "lidi"), 7) ``` ``` ## [1] " Ahoj" " lidi" ``` ```r str_pad(c("Ahoj", "lidi"), 9, side = "both", pad = "-") ``` ``` ## [1] "--Ahoj---" "--lidi---" ``` Delší řetězce funkce nemění: ```r str_pad(c("Ahoj", "malé zelené bytosti z Viltvodlu VI"), width = 7) ``` ``` ## [1] " Ahoj" "malé zelené bytosti z Viltvodlu VI" ``` V\ některých případech potřebujeme delší řetězec zkrátit na určitou délku tak, že se jeho část vynechá. K\ tomu slouží funkce `str_trunc(string, width, side = c("right", "left", "center"), ellipsis = "...")`, která zkrátí řetězec `string` na délku `width` znaků. Přitom přebytečnou část textu na straně `side` vynechá a nahradí znaky `ellipsis`: ```r s <- "Toto je přehnaně dlouhý text, který toho mnoho neříká." str_trunc(s, 20, "right") ``` ``` ## [1] "Toto je přehnaně ..." ``` ```r str_trunc(s, 20, "left") ``` ``` ## [1] "...oho mnoho neříká." ``` ```r str_trunc(s, 20, "center") ``` ``` ## [1] "Toto je p... neříká." ``` Zarovnat řetězec do odstavce umožňuje funkce `str_wrap(string, width = 80, indent = 0, exdent = 0)`, kde `strings` je zarovnávaný řetězec, `width` je cílová šířka sloupce (implicitně\ 80 znaků), `indent` je odsazení prvního řádku a `exdent` je odsazení následujících řádků (oboje implicitně 0\ znaků): ```r s1 <- "Na počátku bylo Slovo, to Slovo bylo u Boha, to Slovo byl Bůh. To bylo na počátku u Boha. Všechno povstalo skrze ně a bez něho nepovstalo nic, co jest. V něm byl život a život byl světlo lidí. To světlo ve tmě svítí a tma je nepohltila." cat(str_wrap(s1, 60)) ``` ``` ## Na počátku bylo Slovo, to Slovo bylo u Boha, to Slovo byl ## Bůh. To bylo na počátku u Boha. Všechno povstalo skrze ně ## a bez něho nepovstalo nic, co jest. V něm byl život a život ## byl světlo lidí. To světlo ve tmě svítí a tma je nepohltila. ``` ### Konverze malých a velkých písmen Ke konverzi malých písmen na velká slouží funkce `str_to_upper(string, locale = "en")`, ke konverzi na malá písmena funkce `str_to_lower(string, locale = "en")`, kde `string` je převáděný řetězec. Parametr `locale` umožňuje zadat popis jazykových pravidel pro převod znaků. Parametr je nepovinný a pokud není zadán, použije se aktuální *locale* počítače. ```r s <- "Nějaký malý textík..." str_to_lower(s, locale = "cz") ``` ``` ## [1] "nějaký malý textík..." ``` ```r str_to_upper(s, locale = "cz") ``` ``` ## [1] "NĚJAKÝ MALÝ TEXTÍK..." ``` ### Nahrazení chybějících hodnot řetězcem Někdy máme vektor řetězců, ve kterém některé hodnoty chybí, tj.\ mají hodnotu\ `NA`, a\ my potřebujeme tyto hodnoty nahradit nějakým textem. K\ tomu slouží funkce `str_replace_na(string, replacement = "NA")`, kde `string` je vektor řetězců a `replacement` je řetězec, který má v\ řetězci `string` nahradit chybějící hodnoty. Pokud chceme např.\ nahradit chybějící hodnoty slovem "neznámé", můžeme to udělat takto: ```r poloha <- c("nahoře", "dole", NA, "vlevo") str_replace_na(poloha, replacement = "neznámá") ``` ``` ## [1] "nahoře" "dole" "neznámá" "vlevo" ``` ### Formátování čísel Někdy potřebujeme vytisknout čísla v\ "pěkném formátu". To znamená, že musíme převést dané číslo na zformátovaný řetězec, který dané číslo prezentuje v\ požadovaném tvaru. K\ tomu slouží funkce `format()` ze základního balíku **base**. Funkce `format()` je *generická* a umí formátovat mnoho různých objektů. Pro čísla má následující parametry: ```r format(x, trim = FALSE, digits = NULL, nsmall = 0L, justify = c("left", "right", "centre", "none"), width = NULL, na.encode = TRUE, scientific = NA, big.mark = "", big.interval = 3L, small.mark = "", small.interval = 5L, decimal.mark = getOption("OutDec"), zero.print = NULL, drop0trailing = FALSE, ...) ``` kde `x` je numerický vektor. Další užitečné parametry jsou zejména `nsmall` (minimální počet desetinných míst), `big.mark` a `decimal.mark` (znak oddělující tisíce a desetinná místa), `scientific` (pokud je `TRUE`, pak vypisuje čísla ve vědecké notaci), `width` (minimimální počet znaků) a `justify` (zarovnání). Další parametry jsou popsané v\ dokumentaci. ```r x <- 123456.3 print(x) ``` ``` ## [1] 123456.3 ``` ```r print(format(x, big.mark = ".", decimal.mark = ",", nsmall = 2)) ``` ``` ## [1] "123.456,30" ``` ```r print(format(x, scientific = TRUE)) ``` ``` ## [1] "1.234563e+05" ``` ```r print(format(x, width = 10, justify = "left")) ``` ``` ## [1] " 123456.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` a `+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. 2006` i\ `1. č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*) toho, jak vypadá hledaný kus textu. Je to tedy jakási abstraktní maska, která ukazuje, jaké vlastnosti musí kus textu splňovat, aby byl vybrán. Zkoumaný ř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.) Jak už bylo řečeno, 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 sebou se spojí do jednoho výrazu; např.\ `abcd` je spojení čtyř znaků, které fungují jako celek, tj.\ 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", protože spojování má vyšší prioritu než "nebo" - 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 dále; např.\ `abc+` znamená řetězce `abc`, `abcc`, `abccc` atd.; opakuje se jen poslední znak, protože operace spojování i\ "nebo" mají nižší prioritu než kvantifikátory - 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., protože seskupení má nejvyšší prioritu ze všech operací; skupiny mají i\ speciální význam při vybírání částí regulárních výrazů, viz dále 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!"). Konkrétně tečka `.` označuje libovolný znak včetně mezer, tabelátorů apod. Výraz `ma.ka` najde slova jako "matka", "maska" nebo "marka", ale ne "maka". 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 `\.` je tak třeba zadat `"\\."` apod. Nepříjemné je to v\ případě, kdy potřebujete hledat doslovně zpětné lomítko. V\ regulárním výrazu je musíte zabezpečit jiným zpětným lomítkem, takže dostanete `\\`. V\ R však dále musíte každé zpětné lomítko zabezpečit dalším zpětným lomítkem, takže regulární výraz, který hledá zpětné lomítko musí být v\ R zadaný jako čtyři zpětná lomítka: `\\\\`. ### Rozsahy Někdy chceme najít řetězec, který obsahuje jakýkoli znak z\ určité množiny. K\ tomu slouží rozsahy (*character classes*). Nejjednodušší způsob, jak v\ regulárním výrazu zadat rozsah, je použít operátor hranatá závorka. Hranaté závorky určují výčet platných znaků. 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 tedy 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. Tabulka\ \@ref(tab:strings-old-char-classes) uvádí nejdůležitější z\ nich. Kromě těchto rozsahů mohou regulární výrazy v\ R 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. Jejich seznam uvádí tabulka\ \@ref(tab:strings-posix-char-classes). POSIXové rozsahy se od základních rozsahů liší v\ tom, že respektují *locale* počítače, takže mezi písmena počítají i\ znaky národní abecedy. Rozdíl mezi `\w` a `[:alnum:]` tedy 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 anglické abecedy (zahrnutá v\ ASCII). Pokud tedy uvažujete písmena, doporučuji používat vždy POSIXové rozsahy. Table: (\#tab:strings-old-char-classes) Přehled základních rozsahů používaných v\ regulárních výrazech. 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 | Table: (\#tab:strings-posix-char-classes) Přehled POSIXových rozsahů používaných v\ regulárních výrazech. rozsah | význam ------------ | ------------------------------------------------------------------------- `[:lower:]` | malá písmena v\ *locale* počítače `[:upper:]` | velká písmena v\ *locale* počítače `[:alpha:]` | malá i\ velká písmena v\ *locale* počítače `[:digit:]` | číslice, tj.\ 0, 1, ..., 9 `[:alnum:]` | alfanumerické znaky, tj.\ `[:alpha:]` a `[:digit:]` `[:blank:]` | mezera a tabelátor `[:cntrl:]` | řídící znaky `[:punct:]` | `!`, `"`, `#`, `%`, `&`, `'`, `(`, `)`, `*`, `+`, `,`, `-`, `.`, `/`, `:`, `;`, `<`, `>`, `?`, `@`, `[`, `\`, `]`, `^`, `_`, ``, `{`, `|`, `}`, `~`, `'`, `.` `[:space:]` | mezera, tabelátor, vertikální tabelátor, nový řádek (a CR a LF) `[:xdigit:]` | hexadecimální číslice, tj.\ 0, 1, ..., 9, A, B, ..., F, a, b, ..., f `[:print:]` | tisknutelné znaky, tj.\ `[:alpha:]` a `[:punct:]` `[:graph:]` | grafické znaky, tj.\ `[:alpha:]` a `[:punct:]` ### Kvantifikátory Znaky `*+?{` jsou kvantifikátory, které řídí počet opakování předchozího znaku, rozsahu nebo skupiny. Jejich význam shrnuje tabulka\ \@ref(tab:strings-quantif). Table: (\#tab:strings-quantif) Přehled POSIXových rozsahů používaných v\ regulárních výrazech. znak | význam ------- | --------------------------------------------------- `*` | libovolný počet opakování (tj.\ vůbec, jednou 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 Podívejme se na příklady: - `.*` odpovídá jakémukoli znaku, který je opakovaný libovolně krát, tj.\ jakýkoli řetězec včetně `""` - `\w+` odpovídá aspoň jednomu písmenu anglické abecedy - `[+-]?\d+` odpovídá celému číslu (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: ```r 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 `*?`: ```r 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.\"" ``` ### Začátek a konec řetězce Normálně regulární výrazy najdou jakoukoli část řetězce. Někdy je však užitečné hledat jen za začátku nebo na konci řetězce. K\ tomu slouží "kotvy" (*anchors*). 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). Pokud tedy chceme najít jen řádky, které začínají písmenem "A", můžeme použít regulární výraz `^A`. Pokud chceme najít řádky, které končí tečkou, můžeme použít regulární výraz `\.$` (v\ R bude potřeba zpětné lomítko zdvojit a zadat `"\\.$"`). Výraz `^-+$` splní pouze řádky, které obsahují pouze pomlčky. ### Skupiny Někdy je potřeba některé znaky seskupit. K\ tomu slouží kulaté závorky. Skupiny ovlivňují priority vyhodnocování regulárního výrazu. To je užitečné např.\ 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í.) Skupiny ovlivňují priority i\ 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) můžeme použít regulární výraz `(.)(.).\2\1`. V\ něm musí být znak `\1` stejný jako první znak palindromu (odpovídající první skupině s\ tečkou) a `\2` musí být znak stejný jako druhý znak palinromu (odpovídající druhé skupině s\ tečkou). (Náš regulární výraz najde ovšem i\ palindromy složené z\ čísel apod. Pro hledání skutečných palindromů by bylo bezpečnější nahradit v\ regulárním výrazu tečky např.\ rozsahem `[[:alpha:]]`.) ### Příklady Podívejme se nyní na regulární výrazy pro dva případy zmíněné výše: pro nalezení telefonního čísla a datumu. 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 musejí být všechna zpětná lomítka zdvojena, takže regulární výraz musí být zapsán takto: ```r 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) ``` Regulární výraz, který pozná všechny výše uvedené formáty data: ```r r <- str_c("\\d{1,2}\\.\\s*(\\d{1,2}\\.|leden|únor|březen|duben|červen|červenec|srpen|", "září|ří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 ``` ### Praktická práce s\ regulárními výrazy Regulární výrazy jsou velmi mocné, ale poněkud nepřehledné. Vždy byste si proto měli vyzkoušet, zda váš regulární výraz 1)\ nachází to, co chcete, a\ 2)\ nenachází to, co nechcete. Je proto velmi užitečné, abyste si připravili několik příkladů řetězců, které by výraz měl a které by neměl najít. K\ otestování regulárního výrazu pak můžete použít buď funkce popsané v\ dalším oddílu, nebo uživatelsky přívětivé funkce `str_view(string, pattern, match = NA)` a `str_view_all(string, pattern, match = NA)`, kde `string` je prohledávaný vektor řetězců, `pattern` je zkoušený regulární výraz a `match` je logická hodnota. Pokud je `TRUE`, zobrazí jen řetězce, které splňují daný regulární výraz, pokud `FALSE`, pak zobrazí jen řetězce, které daný regulární výraz nesplňují. Pokud necháte implicitiní hodnotu\ `NA`, pak zobrazí všechny zadané řetězce. Funkce zobrazí v\ RStudiu v\ záložce `Viewer` zadané řetězce a barevně vyznačí části, které odpovídají zadanému regulárnímu výrazu. První funkce zobrazí pouze první výskyt, zatímco druhá funkce všechny výskyty. Pohodlné testování regulárních výrazů umožňuje i\ doplněk RStudia RegExplain. Protože není zatím součástí CRANu, nainstalujete jej takto: ```r devtools::install_github("gadenbuie/regexplain") ``` I\ když váš regulární výraz funguje, měli byste být opatrní a pamatovat na to, že funguje jen za předem určených předpokladů: náš výraz pro datum např.\ najde jen datum zadané tak, jak je zvykem v\ České republice, a\ to ještě jen v\ případě, že je zadán i\ rok. Pokud byste chtěli obecnější řešení, začal by váš regulární výraz být složitější a složitější. Pak se často vyplatí výraz zjednodušit a hledaný řetězec hledat na dvakrát nebo na třikrát a nebo využít i\ jiné nástroje než jen regulární výrazy. Některé texty byste neměli pomocí regulárních výrazů procházet téměř nikdy. Sem patří např.\ webové stránky, které je možné procházet mnohem elegantněji s\ použitím syntaxe *XPath* nebo *CSS*. Pamatujte také, že regulární výrazy obsahují mnohem více než to, co jste viděli v\ tomto oddílu. 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. Ve většině případů vám však bude obsah tohoto oddílu bohatě stačit. ## Funkce pro práci s\ regulárními výrazy Nyní se podíváme na to, jak využít regulární výrazy pro práci s\ řetězci v\ R. Většina funkcí pro práci s\ regulárními výrazy definovaná v\ balíku **stringr** má podobnou syntaxi: `str_XXX(string, pattern, ...)`, kde `XXX` je část jména funkce, `string` je vektor zpracovávaných řetězců, `pattern` je použitý regulární výraz a `...` případné další parametry. Pokud některá funkce vrací jen první výskyt hledaného vzoru v\ řetězci, pak většinou existuje i\ podobná funkce s\ koncovkou `_all`, která vrací všechny výskytu zadaného vzoru. ### Detekce vzoru Nejjednodušším případem použití regulárních výrazů je nalezení řetězců, které odpovídají danému regulárnímu výrazu. K\ detekci, zda řetězec odpovídá zvolenému regulárnímu výrazu, slouží funkce `str_detect(string, pattern, negate = FALSE)`, kde `string` je prohledávaný vektor řetězců a `pattern` je hledaný vzor. Funkce vrací logický vektor stejné délky jako `string` s\ hodnotou `TRUE`, pokud byl vzor nalezen, a\ hodnotou `FALSE` v\ opačném případě. Nastavení parametru `negate` na `TRUE` výsledek obrací: nyní funkce vrací `TRUE`, pokud řetězec daný vzor neobsahuje. Řekněme, že chceme zjistit, které řetězce ve vektoru `s1` obsahují slova o\ pěti písmenech, kde první dvě jsou "ma" a poslední dvě "ka" (další příklady najdete výše): ```r s1 <- c("matka", "mačka", "mačká", "maska", "Matka", "marka!", "ma ka") str_detect(s1, "ma.ka") # TRUE, pokud obsahuje vzor ``` ``` ## [1] TRUE TRUE FALSE TRUE FALSE TRUE TRUE ``` ```r str_detect(s1, "ma.ka", negate = TRUE) # TRUE, pokud neobsahuje vzor ``` ``` ## [1] FALSE FALSE TRUE FALSE TRUE FALSE FALSE ``` Funkce `str_which(string, pattern, negate = FALSE)` je podobná a má i stejné parametry. Místo logických hodnot však vrací celočíselný vektor pozic prvků vektoru `string`, které splňují zadaný regulární výraz: ```r str_which(s1, "ma.ka") ``` ``` ## [1] 1 2 4 6 7 ``` Často potřebujeme řetězce, které odpovídají regulárnímu výrazu vybrat. To by bylo možné provést pomocí subsetování: ```r s1[str_detect(s1, "ma.ka")] ``` ``` ## [1] "matka" "mačka" "maska" "marka!" "ma ka" ``` Balík **stringr** však k\ tomuto účelu nabízí komfortnější funkci `str_subset(string, pattern, negate = FALSE)`, která vrací ty prvky vektoru\ `s`, které obsahují vzor\ `p` (parametry funkce mají stejný význam jako u `str_detect()`): ```r str_subset(s1, "ma.ka") ``` ``` ## [1] "matka" "mačka" "maska" "marka!" "ma ka" ``` Podívejme se na praktičtější příklad. Řekněme, že chceme vybrat ty řetězce z\ vektoru `cisla`, které obsahují telefonní čísla: ```r 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_subset(cisla, r) ``` ``` ## [1] "Leoš: 777 666 555 444" ## [2] "Lída: domů +420 734 123 456, práce 777-666-555" ``` Mohl by nás zajímat i\ počet výskytů regulárního výrazu v\ řetězci. K\ tomu slouží funkce `str_count(string, pattern)`, která spočítá počet výskytů regulárního výrazu\ `pattern` v\ řetězci\ `string`. Pracuje vektorově přes zadaný vektor řetězců\ `string` i\ přes regulární výraz\ `pattern`. Implicitní hodnota\ `pattern` je prázdný řetězec `""`. V\ tomto případě vrací funkce `str_count()` počet znaků v\ řetězci (počítaných podle aktuálního *locale*). Podívejme se na několik příkladů: ```r ovoce <- c("jablko", "ananas", "hruška", "rybíz") str_count(ovoce, "a") # počet výskytů "a" v každém slově ``` ``` ## [1] 1 3 1 0 ``` ```r # počet "a" v jablku, "a" v ananasu, "b" v hrušce a "r" v rybízu str_count(ovoce, c("a", "a", "b", "r")) ``` ``` ## [1] 1 3 0 1 ``` ```r str_count(c("Ahoj", "lidičky!")) # počet znaků v každém řetězci ``` ``` ## [1] 4 8 ``` ### Získání částí řetězců, které splnují vzor Většinou nám nestačí najít výskyt řetězce, který odpovídá regulárnímu výrazu, ale chceme tento řetězec získat. K\ tomu slouží funkce `str_extract(string, pattern)`, která získá z\ každého řetězce ve vektoru\ `string` tu jeho část, která odpovídá prvnímu výskytu vzoru\ `pattern`. Funkce vrací vektor stejné délky jako\ `string`; pokud není vzor nalezen, vrací\ `NA`. Řekněme, že chceme např.\ vybrat z\ tweetů jednotlivé hashtagy (zjednodušíme si život a budeme předpokládat, že hashtag začíná křížkem a tvoří ho písmena a čísla a končí s\ prvním výskytem ne-alfanumerického znaku jako je mezera, tečka, čárka apod.): ```r r <- "#[[:alpha:]]+" # hashtag následovaný jedním nebo více písmeny tweet <- c("#Brno je prostě nejlepší a #MU je nejlepší v Brně.", "Někdy je možné najít zajímavé podcasty na #LSE.", "Stupnování 'divnosti': divný, divnější, #ParisHilton.", "Docela prázdný tweet.") str_extract(tweet, r) ``` ``` ## [1] "#Brno" "#LSE" "#ParisHilton" NA ``` Funkce `str_extract()` vrací pouze první výskyt výrazu v\ každém řetězci. Pokud chceme získat všechny výskyty, musíme použít funkci `str_extract_all(string, pattern, simplify = FALSE)`. Implicitně funkce vrací seznam, jehož prvky odpovídají prvkům vektoru\ `string`; nenalezené výskyty pak indikuje prázdný vektor řetězců (`character(0)`). Funkce však umí zjednodušit výsledek na matici. K\ tomu slouží parametr `simplify` nastavený na hodnotu `TRUE`. V\ tomto případě odpovídají řádky výsledné matice prvkům vektoru\ `string`; nenalezené výskyty jsou pak označené jako prázdné řetězce `""`. ```r str_extract_all(tweet, r) ``` ``` ## [[1]] ## [1] "#Brno" "#MU" ## ## [[2]] ## [1] "#LSE" ## ## [[3]] ## [1] "#ParisHilton" ## ## [[4]] ## character(0) ``` ```r str_extract_all(tweet, r, simplify = TRUE) ``` ``` ## [,1] [,2] ## [1,] "#Brno" "#MU" ## [2,] "#LSE" "" ## [3,] "#ParisHilton" "" ## [4,] "" "" ``` Někdy nechceme získat celý vzor, nýbrž pouze jeho části. K\ tomu slouží funkce `str_match(string, pattern)` a `str_match_all(string, pattern)`, kde `string` je vektor prohledávaných řetězců a `pattern` regulární výraz. K\ rozdělení regulárního výrazu do částí se používají skupiny. Funkce `str_match()` hledá první výskyt regulárního výrazu\ `pattern` v\ řetězci a vrací matici, jejíž řádky odpovídají prvkům vektoru\ `string`. První sloupec je celý regulární výraz, druhý sloupec první skupina v\ regulárním výrazu, třetí sloupec druhá skupina atd. (Pokud jsou skupiny zanořené jedna do druhé, pak se jejich pořadí počítá podle pořadí levé závorky.) Nenalezené prvky mají hodnotu\ `NA`. Pokud např.\ chceme získat jméno hashtagu bez křížku, můžeme vzít druhý sloupec matice, kterou v našem případě vrátí funkce `str_match()`: ```r r <- "#([[:alpha:]]+)" # totéž, co výše, ale všechna písmena tvoří skupinu str_match(tweet, r) ``` ``` ## [,1] [,2] ## [1,] "#Brno" "Brno" ## [2,] "#LSE" "LSE" ## [3,] "#ParisHilton" "ParisHilton" ## [4,] NA NA ``` Poznámka: Vždy stojí za zvážení, zda provést nějakou operaci naráz pomocí složitého regulárního výrazu, nebo ji rozdělit do několika kroků. Extrakci hashtagů z\ minulého příkladu můžeme snadno provést ve dvou jednodušších krocích: nejdříve extrahovat celý hashtag, a\ pak z\ něj odstranit křížek: ```r str_extract(tweet, r) %>% str_remove("#") ``` ``` ## [1] "Brno" "LSE" "ParisHilton" NA ``` Funkce `str_match_all()` vrací všechny výskyty regulárního výrazu. Jejím výsledkem je seznam matic. Řádky těchto matic odpovídají jednotlivým nálezům. Sloupce mají význam jako výše. Pokud není regulární výraz v\ daném řádku nalezen, je výsledkem prázdná matice. ```r str_match_all(tweet, r) ``` ``` ## [[1]] ## [,1] [,2] ## [1,] "#Brno" "Brno" ## [2,] "#MU" "MU" ## ## [[2]] ## [,1] [,2] ## [1,] "#LSE" "LSE" ## ## [[3]] ## [,1] [,2] ## [1,] "#ParisHilton" "ParisHilton" ## ## [[4]] ## [,1] [,2] ``` Podívejme se opět na poněkud komplexnější příklad. Vektor `cisla` obsahuje řetězce, které obsahují telefonní čísla. Pro každého člověka chceme získat jeho první telefonní číslo a převést je do standardního tvaru (řekněme, že standardní tvar vynechává předčíslí země a trojice čísel odděluje pomlčkou). Postup může být následující: nejdříve získáme jednotlivé výskyty regulárního výrazu pomocí funkce `str_match()`. Z\ výsledku si vezmeme pouze skupiny, které odpovídají trojicím čísel (v\ našem případě třetí, čtvrtý a pátý sloupec výsledku). Nakonec spojíme jednotlivá trojčíslí do jednoho řetězce pomocí funkce `str_c()`; abychom jí zabránili spojit všechna trojčíslí pro všechny lidi dohromady, aplikujeme funkci `str_c()` na jednotlivé řádky matice pomocí funkce `map_chr()` z\ balíku **purrr**: ```r 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") cisla2 <- str_match(cisla, r)[, 3:5] purrr::map_chr(1:nrow(cisla2), ~ str_c(cisla2[., ], collapse = "-")) ``` ``` ## [1] "777-666-555" "734-123-456" NA ``` ### Indexy řetězců splnujících vzor Někdy se hodí najít indexy, kde začíná a končí vzor v\ daném řetězci. K\ tomu slouží funkce `str_locate()` a `str_locate_all`. Funkce `str_locate(string, pattern)` najde indexy prvního výskytu regulárního výrazu `pattern` v\ řetězci\ `string`. Výsledkem je matice, jejíž řádky odpovídají prvkům vektoru\ `string`. První sloupec je index začátku prvního výskytu vzoru, druhý sloupec je index konce prvního výskytu vzoru. Pokud není vzor v\ řetězci nalezen, vrátí `NA`. Nalezené indexy výskytu řetězce je možné následně použít k\ extrakci daného řetězce pomocí funkce `str_sub()`. To je však ekvivalentní k\ použití funkce `str_extract()`. ```r r <- "#[[:alpha:]]+" # hashtag následovaný jedním nebo více písmeny tweet <- c("#Brno je prostě nejlepší a #MU je nejlepší v Brně.", "Někdy je možné najít zajímavé podcasty na #LSE.", "Stupnování 'divnosti': divný, divnější, #ParisHilton.", "Docela prázdný tweet.") str_locate(tweet, r) # vrací matici indexů prvních výskytů klíčových slov v tweetu ``` ``` ## start end ## [1,] 1 5 ## [2,] 43 46 ## [3,] 41 52 ## [4,] NA NA ``` ```r str_sub(tweet, str_locate(tweet, r)) # vyřízne tato klíčová slova ``` ``` ## [1] "#Brno" "#LSE" "#ParisHilton" NA ``` ```r str_extract(tweet, r) # totéž ``` ``` ## [1] "#Brno" "#LSE" "#ParisHilton" NA ``` K\ nalezení všech výskytů vzoru ve vektoru řetězců\ `string` slouží funkce `str_locate_all(string, pattern)`, která vrací seznam matic indexů. Prvky seznamu odpovídají prvkům vektoru\ `string`. Řádky každé matice odpovídají jednotlivým výskytům vzoru v\ jednom prvku vektoru\ `string`. První sloupec matice je index začátku výskytu, druhý sloupec je index konce výskytu. Pokud není vzor nalezen, vrací prázdnou matici: ```r str_locate_all(tweet, r) ``` ``` ## [[1]] ## start end ## [1,] 1 5 ## [2,] 28 30 ## ## [[2]] ## start end ## [1,] 43 46 ## ## [[3]] ## start end ## [1,] 41 52 ## ## [[4]] ## start end ``` Funkce `invert_match(loc)` invertuje matici indexů vrácenou funkcí `str_locate_all()`, takže obsahuje indexy částí řetězce, které *neodpovídají* vzoru. Detaily najdete v\ dokumentaci. ### Nahrazení vzoru Regulární výrazy umožňují i\ nahrazení částí řetězců, které odpovídají danému regulárnímu výrazu. K\ tomu slouží funkce `str_replace()` a `str_replace_all()`. Funkce `str_replace(string, pattern, replacement)` nahradí ve vektoru řetězců\ `string` první výskyt vzoru\ `pattern` řetězcem\ `replacement`. Funkce `str_replace_all()` má stejnou syntaxi, ale nahradí všechny výskyty vzoru ve vektoru\ `string`. Začněme nejjednodušším případem. Řekněme, že chceme v\ každém tweetu skrýt hashtagy: nahradit je řetězcem `"XXX" nebo je úplně vymazat. To můžeme udělat např.\ takto: ```r r <- "#[[:alpha:]]+" str_replace_all(tweet, r, "#XXX") # náhrada hashtagu pomocí #XXX ``` ``` ## [1] "#XXX je prostě nejlepší a #XXX je nejlepší v Brně." ## [2] "Někdy je možné najít zajímavé podcasty na #XXX." ## [3] "Stupnování 'divnosti': divný, divnější, #XXX." ## [4] "Docela prázdný tweet." ``` ```r str_replace_all(tweet, r, "") # vymazání hashtagu ``` ``` ## [1] " je prostě nejlepší a je nejlepší v Brně." ## [2] "Někdy je možné najít zajímavé podcasty na ." ## [3] "Stupnování 'divnosti': divný, divnější, ." ## [4] "Docela prázdný tweet." ``` Pokud chceme nějaký text odstranit, můžeme jej nahradit prázdným řetězcem (jak ukazuje příklad výše), nebo použít užitečnou zkratku, kterou nabízí funkce `str_remove(string, pattern)` a `str_remove_all(string, pattern)`. Ta první odstraní první výskyt regulárního výrazu `pattern` z\ řetězce `string`, druhá všechny výskyty. Vymazat hashtag můžeme tedy i\ takto: ```r str_remove_all(tweet, r) ``` ``` ## [1] " je prostě nejlepší a je nejlepší v Brně." ## [2] "Někdy je možné najít zajímavé podcasty na ." ## [3] "Stupnování 'divnosti': divný, divnější, ." ## [4] "Docela prázdný tweet." ``` Funkce `str_replace_all()` dokáže nahradit více vzorů naráz. K\ tomu stačí nahradit vzor\ `pattern` a nahrazující řetězec\ `replacement` pojmenovaným vektorem řetězců: jména prvků vektorů určují, co se nahrazuje, hodnoty určují, čím se nahrazuje. Řekněme, že chceme nahradit slova "jeden", "dva" a "tři" odpovídajícími číslicemi: ```r ovoce <- c("jeden banán", "dva pomeranče", "tři mandarinky") str_replace_all(ovoce, c("jeden" = "1", "dva" = "2", "tři" = "3")) ``` ``` ## [1] "1 banán" "2 pomeranče" "3 mandarinky" ``` Regulární výrazy však dokáží víc než jen nahradit kus řetězce jiným fixním řetězcem (např.\ "XXX"): dokáží náhradu zkonstruovat přímo z\ nahrazovaného textu. K\ tomu opět slouží skupiny. Funkce `str_replace()` i `str_replace_all()` si zapamatují obsah skupin obsažených v\ regulárním výrazu a mohou jej použít v\ nahrazujícím řetězci\ `r`. Obsah první skupiny je `\1`, druhé skupiny `\2` atd. (v\ R je ovšem třeba zpětné lomítko zdvojit). Řekněme, že chceme hashtagy v\ tweetech upravit tak, že hashtag bude nejdříve uveden bez křížku, a\ pak v\ závorce s\ křížkem: ```r r <- "#([[:alpha:]]+)" str_replace_all(tweet, r, "\\1 (#\\1)") ``` ``` ## [1] "Brno (#Brno) je prostě nejlepší a MU (#MU) je nejlepší v Brně." ## [2] "Někdy je možné najít zajímavé podcasty na LSE (#LSE)." ## [3] "Stupnování 'divnosti': divný, divnější, ParisHilton (#ParisHilton)." ## [4] "Docela prázdný tweet." ``` Paměť v\ regulárních výrazech se dá použít na celou řadu praktických úprav řetězců. Řekněme například, že máme vektor datumů v\ anglosaském formátu "MM-DD-YYYY", a\ chceme je převést do českého formátu "DD. MM. YYYY". Bez regulárních výrazů to může být pracná záležitost. S\ použitím regulárních výrazů je to hračka: ```r datumy <- c("born: 06-01-1921", "died: 02-01-2017", "no information at all") str_replace_all(datumy, "(\\d{2})-(\\d{2})-(\\d{4})", "\\2. \\1. \\3") ``` ``` ## [1] "born: 01. 06. 1921" "died: 01. 02. 2017" "no information at all" ``` ### Rozdělení řetězců podle vzoru Často je potřeba rozdělit řetězec do několika částí oddělených nějakým vzorem. K\ tomu slouží funkce `str_split(string, pattern, n = Inf, simplify = FALSE)` a `str_split_fixed(string, pattern, n)`, které rozdělí řetězec\ `string` v\ bodech, kde je nalezen vzor\ `pattern`. Celé číslo\ `n` určuje, na kolik částí je řetězec rozdělen. Funkce `str_split()` nepotřebuje mít `n` zadané (implicitně rozdělí řetězec do tolika částí, do kolika je potřeba). Funkce vrací seznam, jehož každý prvek odpovídá jednomu prvku vektoru\ `string`. Funkce `str_split_fixed()` musí mít zadaný počet\ `n` a vrací matici, jejíž řádky odpovídají prvkům vektoru\ `string` a sloupce jednotlivým nálezům (přebytečné sloupce jsou naplněné prázdným řetězcem\ `""`). Pokud je počet\ `n` nedostatečný, je nerozdělený zbytek řetězce vložen do posledního zadaného sloupce. Pokud však funkci `str_split()` nastavíme parameter `simplify` na `TRUE`, pak také zjednoduší svůj výsledek do matice. ```r ovoce <- c("jablka a hrušky a hrozny", "pomeranče a fíky a datle a pomela") str_split(ovoce, " a ") ``` ``` ## [[1]] ## [1] "jablka" "hrušky" "hrozny" ## ## [[2]] ## [1] "pomeranče" "fíky" "datle" "pomela" ``` ```r str_split(ovoce, " a ", 3) ``` ``` ## [[1]] ## [1] "jablka" "hrušky" "hrozny" ## ## [[2]] ## [1] "pomeranče" "fíky" "datle a pomela" ``` ```r str_split_fixed(ovoce, " a ", 4) ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] "jablka" "hrušky" "hrozny" "" ## [2,] "pomeranče" "fíky" "datle" "pomela" ``` ```r str_split_fixed(ovoce, " a ", 3) ``` ``` ## [,1] [,2] [,3] ## [1,] "jablka" "hrušky" "hrozny" ## [2,] "pomeranče" "fíky" "datle a pomela" ``` ```r str_split(ovoce, " a ", simplify = TRUE) ``` ``` ## [,1] [,2] [,3] [,4] ## [1,] "jablka" "hrušky" "hrozny" "" ## [2,] "pomeranče" "fíky" "datle" "pomela" ``` Řekněme, že potřebujeme rozdělit řetězce, které obsahují pouze dobře formátované datum v\ české konvenci na den, měsíc a rok. To můžeme udělat např.\ takto: ```r datum <- c("1.6.2001", "1. 2. 2003", "blábol") str_split_fixed(datum, "\\.\\s*", 3) ``` ``` ## [,1] [,2] [,3] ## [1,] "1" "6" "2001" ## [2,] "1" "2" "2003" ## [3,] "blábol" "" "" ``` Pokud řetězce s\ daty nejsou formátované dobře, ale mohou být v\ jednom z\ formátů "DD.MM.YYYY", "DD. MM. YYYY" nebo "DD-MM-YYYY", pak stačí jen zobecnit regulární výraz, který jednotlivé složky data odděluje: ```r datum <- c("10-5-1999", "1.6.2001", "1. 12. 2003", "blábol") str_split_fixed(datum, "(\\.\\s*|-)", 3) ``` ``` ## [,1] [,2] [,3] ## [1,] "10" "5" "1999" ## [2,] "1" "6" "2001" ## [3,] "1" "12" "2003" ## [4,] "blábol" "" "" ``` (Pro práci s\ volně formátovanými daty má mnoho užitečných funkcí balík **lubridate**, viz\ oddíl\ \@ref(sec-datetime).) Někdy chceme s\ řetězci pracovat po slovech. K\ tomu slouží funkce `word()`, která rozdělí řetězec na slova a vrátí slova se zadanými indexy. Použití je ```r word(string, start = 1L, end = start, sep = fixed(" ")) ``` kde `string` je vektor rozdělovaných řetězců, `start` je index prvního vraceného slova, `end` je index posledního vraceného slova a `sep` je regulární výraz, který odděluje slova (implicitně je to jedna mezera). Indexy mohou být i\ záporné\ -- pak se počítají odzadu, tj.\ `-1` je poslední slovo. Všechny parametry se recyklují. Na příklady se podívejte do dokumentace. ## Modifikace chování regulárních výrazů Chování regulárních výrazů je možné ve funkcích z\ balíku **stringr** modifikovat pomocí čtyř funkcí: `regex()`, `fixed()`, `coll()` a `boundary()`. Nejdůležitější z\ nich je funkce `regex()`. Pokaždé, když zadáte regulární výraz, funkce z\ balíku **stringr** na něj tiše uplatní funkci `regex()`, takže dva následující výrazy jsou zcela ekvivalentní: ```r s1 <- c("máma", "Máma", "MÁMA") str_detect(s1, "(.)á\\1a") ``` ``` ## [1] TRUE FALSE FALSE ``` ```r str_detect(s1, regex("(.)á\\1a")) ``` ``` ## [1] TRUE FALSE FALSE ``` Funkce `regex()` umožňuje poněkud modifikovat chování regulárního výrazu. K\ tomu jí slouží čtyři parametry. Parametr `ignore_case` umožňuje vypnout rozdíl mezi malými a velkými písmeny (*case sensitivity*): ```r str_detect(s1, regex("(.)á\\1a", ignore_case = TRUE)) ``` ``` ## [1] TRUE TRUE TRUE ``` Normálně v\ regulárních výrazech v\ balíku **stringr** znamenají znaky `^` a `$` začátek a konec řetězce. Pokud mají znamenat začátek a konec řádku (což je obvyklejší), je třeba použít parametr `multiline`: ```r s2 <- "1. nakoupíš 7 vajec.\n2. umyješ podlahu\n3. nakrmíš králík" cat(s2) ``` ``` ## 1. nakoupíš 7 vajec. ## 2. umyješ podlahu ## 3. nakrmíš králík ``` ```r str_extract_all(s2, "^\\d+\\.") ``` ``` ## [[1]] ## [1] "1." ``` ```r str_extract_all(s2, regex("^\\d+\\.", multiline = TRUE)) ``` ``` ## [[1]] ## [1] "1." "2." "3." ``` Zbývající dva parametry jsou méně důležité: parametr `dotall` způsobí, že `.` zahrne všechny znaky, včetně konce řádku\ `\n`. Parametr `comments` umožňuje přidávat do regulárního výrazu komentáře. Alternativou funkce `regex()` je funkce `fixed()`. Ta zajistí, že se výraz bere doslovně. Řekněme, že chceme spočítat počet souvětí v\ řetězci a že počet souvětí aproximujeme počtem teček (naše souvětí vždy končí tečkou, ne vykřičníkem nebo otazníkem, a\ text neobsahuje žádné zkratky, pořadová čísla ani tečky nevyužívá nijak jinak než na konci vět). Abychom našli všechny výskyty tečky v\ řetězci, můžeme ji buď zbavit speciálního významu pomocí zpětného lomítka, nebo použít funkci `fixed()`: ```r s3 <- "Máma má máso. Ema má mísu. Ó my se máme." str_count(s3, "\\.") ``` ``` ## [1] 3 ``` ```r str_count(s3, fixed(".")) ``` ``` ## [1] 3 ``` Podobně jako `regex()` je i\ ve funkci `fixed()` možné vypnout rozdíl mezi malými a velkými písmeny pomocí parametru `ignore_case`. Funkce `fixed()` porovnává řetězec doslovně, takže znaky bere doslovně jako byty, což nemusí vždy správně fungovat pro znaky, které nejsou součástí kódu ASCII (zhruba pro znaky, které nejsou součástí anglické abecedy). Pokud chceme takové znaky brát doslovně, je potřeba použít funkci `cols()`, které je navíc možné nastavit *locale* jazyka (implicitní jazyk je angličtina). Poslední funkce pro modifikaci chování regulárních výrazů je funkce `boundary()`, která umožňuje nastavit okraje regulárního výrazu. Platné volby jsou "character", "line_break", "sentence" a "word"). Nejužitečnější je \ `boundary("word")`, které hledá hranice mezi slovy: ```r s4 <- "Svítilo zářilo zlaté slunce na pobřeží na lagunce." str_split(s4, boundary("word")) ``` ``` ## [[1]] ## [1] "Svítilo" "zářilo" "zlaté" "slunce" "na" "pobřeží" "na" ## [8] "lagunce" ``` ```r str_extract_all(s4, boundary("word")) ``` ``` ## [[1]] ## [1] "Svítilo" "zářilo" "zlaté" "slunce" "na" "pobřeží" "na" ## [8] "lagunce" ```