====== 4. lekce: Regulární výrazy ======
V předchozích lekcích jsme se naučili [[kurz:prvni_dotaz|pokládat dotaz]], [[kurz:zobrazeni_dotazu|zobrazovat a ukládat]] výsledky a [[kurz:vyhodnoceni_dotazu|vyhodnocovat výsledky]] pomocí frekvenční distribuce. V této lekci se podíváme na pokročilejší způsoby dotazování.
Tzv. [[pojmy:regularni_vyrazy|regulárními výrazy]] rozumíme sekvence znaků, pomocí kterých můžeme vyhledat množinu slov. Regulární výrazy využívají jednak znaky se speciálním významem (například znak ''*'' se používá jako zástupný symbol pro libovolný počet opakování, znak ''|'' je symbolem pro výběr z alternativ, podrobněji viz níže), jednak běžné znaky, tj. znaky abecedy, číslice apod., které mají doslovný význam. Všechny speciální znaky si postupně představíme a zároveň si vyzkoušíme, jak fungují v [[kontext|rozhraní KonText]].
Pro hledání v rámci **jednoduchého** dotazu s využitím **regulárních výrazů** je třeba povolit příslušnou volbu pod dotazovacím řádkem. Následující výklad v této lekci předpokládá, že tato volba je zapnuta.
===== Tečka: libovolný znak =====
Začněme tím nejuniverzálnějším znakem, tedy tečkou (''.''), která zastupuje právě jeden libovolný znak. Jakékoliv třípísmenné slovo tak lze v KonTextu najít (mimo jiné) pomocí sekvence tří teček za sebou (''...''). Zadáme-li takový dotaz s výchozím atributem **word** (slovní tvar), zobrazí se ve výsledku slova jako: //ale//, //pro//, //tak//, //jak// apod.
Hledáme-li tečku jakožto interpunkční znaménko, využijeme zpětné lomítko (více [[regularni_vyrazy#dalsi_specialni_symboly|viz níže]]). Dotaz pak bude vypadat takto: ''\.''
===== Kvantifikátory =====
Hojně využívané jsou tzv. kvantifikátory, které určují, kolikrát se smí bezprostředně předcházející znak (nebo speciální symbol) v daném řetězci opakovat.
^ regulární výraz ^ znak ^ co zastupuje ^ další informace ^
| **hvězdička** | ''*'' | libovolný počet (0 a více) opakování předchozího znaku nebo celku | ekvivalentní s ''{0,}'' |
| **plus** | ''+'' | 1 nebo více opakování předchozího znaku nebo celku | ekvivalentní s ''{1,}'' |
| **otazník** | ''?'' | žádný nebo jeden výskyt předchozího znaku nebo celku | ekvivalentní s ''{0,1}'' |
| **interval ve složených závorkách** | ''{n, k}'' | //n// až //k// opakování předchozího znaku nebo celku | je-li //k// vynecháno, tedy zůstane-li ''{n,}'', odpovídá intervalu nejméně //n// opakování;\\ pokud má výraz tvar ''{n}'', odpovídá mu přesně //n// opakování |
| **kulaté závorky** | ''()'' | vytvářejí celek, na který lze aplikovat další operace, např. opakování | celkem mohou být konkrétní znaky i vyšší jednotky |
Regulární výrazy mohou být složeny pouze ze speciálních znaků, z kombinací znaků speciálních a alfanumerických, nebo dokonce jen ze znaků alfanumerických. Z následující tabulky si můžete udělat představu, jakou množinu slov lze vyhledat prostřednictvím různých regulárních výrazů:
^ Dotaz ^ Výsledek ^
|''%%...%%''|slova o třech písmenech|
|''a..''|slova o třech písmenech začínající na písmeno //a//|
|''a.*''|slova o libovolné délce začínající na písmeno //a//, včetně samotného písmene //a// (např. //a//, //až//, //ale//, //ahoj//...)|
|''k.*o''|slova o libovolné délce začínající na písmeno //k// a končící písmenem //o// (//kdo//, //kolo//, //kafíčko//)|
|''256''|číslo //256//|
|''19..''| čtymístná čísla začínající číslicemi //1// a //9//, např. letopočty (případně též kombinace čísla //19// s různými znaky)|
|''kočka''|slovo //kočka//|
|''kočk.*''|všechny tvary lemmatu //kočka// s výjimkou Lsg. (//kočce//) a Gpl. (//koček//), ale i další odvozená slova: //kočkodan//, //kočkovitý//, //kočkovat// aj.|
Regulární výrazy budeme nejčastěji zadávat v rámci CQL dotazu, tj. po přepnutí přepínače **Pokročilý dotaz**, o čemž bude řeč v [[kurz:pokrocile_dotazy|příští lekci]]. Lze je ale používat i v dotazu jednoduchém (i ten je totiž následně automaticky přeložen do dotazovacího jazyka CQL), musí však být zapnuta volba **Povolit regulární výrazy** pod dotazovacím okénkem. V takovém případě je rovněž možné upřesnit výchozí [[pojmy:atributy_pozicni|poziční atribut]], podle nějž se má vyhledávat (např. zadáme-li regulární výraz ''.*číst'', zobrazí se nám při výchozím atributu ''word'' pouze infinitivní tvary prefigovaných odvozenin od slovesa //číst//, kdežto při atributu ''lemma'' se zobrazí všechny slovesné formy těchto sloves).
Podívejme se nyní na konkrétní příklady regulárních výrazů zadaných do korpusu [[cnk:syn2020|SYN2020]] prostřednictvím výchozího atributu ''word'':
Zadejte následující dotazy:
* ''ps?t''
* ''ps*t''
* ''ps+t''
Následně aplikujte kvantifikátory na větší celek než pouhý jeden znak:
* ''cha(cha)?''
* ''cha(cha)*''
* ''cha(cha)+''
Zobrazte si u jednotlivých výsledků frekvenční distribuci (viz [[vyhodnoceni_dotazu#frekvencni_distribuce|předchozí lekce]]) slovních tvarů (word) a lemmat. Nerozlišujte velikost písmen a vytvořte si přehled toho, co dané dotazy našly a co nenašly.
Ověřme si nyní společně výsledky.
^ dotaz ^ počet výskytů ^ odpovídající lemmata bez rozlišení velikosti (v závorce absolutní četnost) ^
| ''ps?t'' | 45 | //pt// (26), //pst// (19) |
| ''ps*t'' | 81 | //pt// (26), //pssst// (24) //pst// (19), //psssst// (7), //psst// (5) |
| ''ps+t'' | 55 | //pssst// (24) //pst// (19), //psssst// (7), //psst// (5) |
| ''cha(cha)?'' | 150 | //cha// (108), //chacha// (42) |
| ''cha(cha)*'' | 164 | //cha// (108), //chacha// (42), //chachacha// (10), //chachachacha// (2), //chachachachacha// (1), //chachachachachacha// (1) |
| ''cha(cha)+'' | 56 | //chacha// (42), //chachacha// (10), //chachachacha// (2), //chachachachacha// (1), //chachachachachacha// (1) |
Možná si kladete otázku, jestli se regulární výrazy hodí i pro výzkum zásadnějších jevů než citoslovcí. Je dobré zdůraznit, že se bez nich neobejdeme např. při prohledávání [[pojmy:mluveny|mluvených korpusů]], které ne vždy bývají [[pojmy:lemma|lemmatizované]], obrovskou pomocí jsou ale i při vyhledávání v korpusech lemmatizovaných.
Zkusme si v jednom takovém nelematizovaném korpusu -- [[cnk:oral2013|ORAL2013]] -- najít co nejvíc různých podob výrazu //jestli//.
- Nejprve se zamyslete nad tím, která písmena se při hovoru vynechávají, a lexém přesto zůstává srozumitelný.
- Zformulujte dotaz (drobná nápověda: vystačíte si pouze s písmeny a otazníky).
- Zobrazte si frekvenční distribuci.
Stejně jako u většiny úloh s regulárními výrazy i v tomto případě existuje více postupů, jak se dobrat ke stejnému výsledku. My jsme si vybrali ten využívající symbol ''?'', který specifikuje, které segmenty se ve slově **nemusejí** vždy vyskytovat. Zápis je takovýto: ''j?est?l?i'' (tzn. předpokládáme, že zredukovat lze hlásky //j//, //t// a //l//) a výsledek tento:
[{{:kurz:jestli-frekvence.png?direct&300|Frekvenční distribuce podob //jestli// v korpusu ORAL2013}}]
Ve frekvenčním seznamu se ocitly dva překvapivé tvary: //jesti// a //esti//. Pomocí modře označeného **p** ([[manualy:kontext:frekvencni_distribuce#frekvencni_seznam_vypis|pozitivní filtr]]) ve frekvenčním seznamu můžeme hned zkontrolovat, zda jde o námi hledaný význam (v obou případech tomu tak je) a nakolik je použití tohoto konkrétního tvaru regionálně či jinak podmíněné (zdá se to být východomoravské specifikum -- 6 případů ze 7, respektive 8 z 9 spadá do této nářeční oblasti).
Více o specifikách hledání v mluvených korpusech najdete v [[kurz:hledani_v_mluvenych_korpusech|bonusové lekci]].
==== Sekvence libovolných znaků ====
[{{ frekvdistr_vedom.png?nolink&direct&250|Frekvenční distribuce deseti nejčastějších lemmat získaných díky tečce a hvězdičce}}]
Asi nejpoužívanější kombinací regulárních výrazů je vyhledání libovolného počtu opakování libovolných znaků, tj. ''.*'' (tečka a hvězdička). Ta může reprezentovat celé slovo nebo jeho libovolnou část. Proto není vhodné zadávat samotný dotaz ''.*'', není-li to nutné, protože výsledkem zdlouhavého a výpočetně náročného hledání budou všechna slova v daném korpusu.
* Vyhledejte v korpusu [[cnk:syn2020|SYN2020]] v jednoduchém typu dotazu řetězec znaků ''.*vědom.*'' (je zapotřebí zapnout možnost **Povolit regulární výrazy** a pro srovnatelnost výsledků zvolit jako výchozí atribut **word**).
* Zobrazte si frekvenční distribuci takto identifikovaných lemmat a výsledný frekvenční seznam si uložte.
Jak kýžená frekvenční distribuce vypadá? Najde veškerá lemmata obsahující daný řetězec znaků (v tomto případě slovní základ //vědom//) a umožní zkoumat nejrůznější typy odvozování.
===== Další speciální symboly =====
Vedle kvantifikátorů a tečky lze v rámci regulárních výrazů použít i další symboly se speciálním významem.
^ regulární výraz ^ znaky ^ co zastupuje ^ další informace ^
| **seznam** | ''[ ]'' | alternativa, možnost výběru **jednoho** libovolného znaku ze znaků uvnitř hranatých závorek | v rámci seznamu je možné používat také pomlčku (''-'') jako operátor rozsahu (např. ''[0-9]'') pro číslice |
| **inverzní seznam** | ''[^ ]'' | výběr **jednoho** libovolného znaku **s výjimkou** znaků uvnitř hranatých závorek | pokud je prvním znakem seznamu stříška (''^''), jde o inverzní seznam: tedy jeden libovolný znak kromě těch uvedených uvnitř hranatých závorek |
| **svislá čára** | ''|'' | alternativa, ovšem ne jenom mezi jednotlivými znaky, ale mezi řetězci tvořícími celek | kombinuje se často s kulatými závorkami, které pomáhají určit prioritu vyhodnocení |
| **zpětné lomítko** | ''\'' | pokud předchází speciálnímu znaku, ztrácí daný symbol svůj zvláštní význam | takto lze vyhledávat např. interpunkční znaménka či další speciální znaky v textu |
Význam hranatých závorek je dvojí:
- v rámci regulárních výrazů představují seznam (viz tabulka výše)
- v rámci [[pojmy:cql|CQL]] představují samostatnou pozici, [[pokrocile_dotazy#hlavni_rysy_cql|viz příští lekce]].
A opět pár příkladů s výsledky:
^ Dotaz ^ Výsledek ^
|''b[iy]l.*''|všechna slova začínající na //bil// nebo //byl//|
|''b[^iy]l.*''|všechna slova nezačínající na na //bil// nebo //byl//, teda např. //bílý//, //bolest//, //bát// aj.|
|''za(ps|sp)ati?''|infinitivy //zapsat(i)// a //zaspat(i)//|
|''\.\!?''|sekvence interpunkčních znamének tečka vykřičník otazník|
|''[\.\!?]''|všechny tečky, vykřičníky a otazníky|
==== Kategorie znaků unicodu ====
Jednotlivé znaky v řetězci je také možné zastoupit pomocí definovaných [[https://en.wikipedia.org/wiki/Unicode_character_property#General_Category|kategorií znaků unicodu]]. Na pozici jednoho znaku se ve výrazu uvede šablona ''\p{ }'', v níž se mezi složené závorky uvede požadovaná kategorie. Pro vyhledávání v korpusu jsou patrně nejužitečnější kategorie velkých (''Lu'') a malých (''Ll'') písmen uvnitř nadřazené kategorie písmen (''L''). Např. zadání ''\p{Lu}'' tak vyhledá libovolné velké písmeno. Je ale také možné vyhledávat znaky všech typů závorek, uvozovek, interpunkci aj. Další kategorie jsou uvedeny např. [[https://www.regular-expressions.info/unicode.html|zde pod nadpisem Unicode Categories]].
Upozornění: ''\p{Lu}'' **//není totéž//** co ''[A-Z]''. Zatímco unicodová kategorie pro velká písmena je obecně platná pro všechny jazyky a abecedy (a zahrnuje tedy např. "Á", "Ü" nebo "Ž"), znakové třídě ''[A-Z]'' odpovídá jen 26 velkých písmen anglické abecedy bez diakritických znamének (a tedy např. "Á", "Ü" nebo "Ž" nezahrnuje)! Důrazně proto doporučujeme vždy, když potřebujete pracovat s rozsahem písmen, používat unicodové kategorie.
Kategorie unicodu lze libovolně kombinovat s regulárními výrazy:
^ Dotaz ^ Výsledek ^
| ''\p{Lu}.*'' | jakékoliv tvary obsahující počáteční velké písmeno |
| ''\p{Lu}+'' | tvary složené jen z velkých písmen (např. zkratky) |
| ''\p{Lu}\p{Ll}+'' | tvary s počátečním velkým písmenem a ostatními malými (např. propria nebo slova na začátku vět) |
| ''[^\p{L}]'' | jakýkoliv jeden nealfabetický znak (tj. nikoliv libovolné písmeno) |
| ''\p{L}.*[0-9].*'' | tvary začínající na písmeno a obsahující číslici |
=== Příklad dotazu: Konkurence forem ===
Vyhledejte v korpusu [[cnk:syn2015|SYN2015]] prefigovaná adjektivní lemmata začínající na //vy-// a zakončená na //-lý// nebo //-ný// (např. //vyčpělý// a //vydýchaný//).
Použili jsme dotaz ''vy.+(lý|ný)'' s výchozím atributem ''lemma|word''. Celkem jsme dostali 105 373 případů, mezi nejčastějšími figurují slova //vyspělý// (2220), //vytrvalý// (1127), //vybledlý// (695) z první skupiny, z druhé pak //vybraný// (6390), //vyrovnaný// (3158), //vyrobený// (2760) a další.
===== Vyzkoušejte si na závěr =====
V korpusu [[cnk:syn2015|SYN2015]] najděte:
- pomocí výchozího atributu **lemma** všechna slova, která obsahují sekvenci //kořen//, kterou následuje i předchází alespoň jeden znak (typicky předpona a přípona)
- pomocí atributu **word** všechny prefigované infinitivy odvozené od slovesa //téct/téci//
- všechny výskyty tvarů negativního superlativu, tj. tvary začínající na //nejne-// a končící na //-ší// nebo //-čí// (pro jednoduchost odhlédněme od jiných tvarů, než je nominativ singuláru)
Výsledky najdete jako vždy v [[reseni_ukolu#lekce_4|Řešení úkolů]].
Pokročilejší dotazy nelze vytvářet bez znalosti dotazovacího jazyka CQL, o němž si povíme v [[pokrocile_dotazy|následující lekci]].
----
[[manualy:kontext:index|Manuál rozhraní KonText]] • [[cnk:uvod|Korpusy ČNK]] • [[zaciname|Jak začít pracovat s ČNK]] • [[pojmy:prehled_pojmu|Přehled pojmů korpusové lingvistiky]] • [[uvod#zaklady_prace_s_korpusem_v_7_lekcich|Kurz práce s korpusem v 7 lekcích]] • [[prvni_dotaz|1. lekce]] • [[zobrazeni_dotazu|2. lekce]] • [[vyhodnoceni_dotazu|3. lekce]] • [[pokrocile_dotazy|5. lekce]]