Ciele
- Zopakovať si tvorbu regulárnych výrazov na základe slovnej špecifikácie.
- Definovať deterministický konečnostavovový automat (DKA) ako ekvivalentný spôsob určenia regulárneho jazyka.
- Konštruovať DKA na základe zadaného regulárneho výrazu.
- Implementovať DKA pre daný regulárny výraz vo vyššom programovacom jazyku (tvorba primitívneho jazykového procesora).
Úvod
Cieľom tohto cvičenia je opakovanie a prehĺbenie teórie regulárnych výrazov a pochopenie priameho odvodenia deterministického konečnostavového automatu z regulárneho výrazu (pomocou metódy tokenov). Na záver pristúpime k prezentácii dvoch základných spôsobov praktickej programovej implementácie deterministických konečnostavových akceptorov.
Postup
Krok 1
Regulárne výrazy predstavujú prvú z pokročilých metód špecifikácie jazyka (konkrétne tzv. regulárneho jazyka), ktorú sme predstavili v predchádzajúcom cvičení. Taktiež vieme, že regulárne výrazy je možné zobraziť aj graficky pomocou prechodových diagramov. Princíp zostrojenia takýchto diagramov bol vysvetlený v predchádzajúcom cvičení.
Prostrednícvom nasledujúceho príkladu zopakujeme tvorbu regulárneho výrazu na základe slovnej špecifikácie ním identifikovaného jazyka a následne pristúpime ku konštrukcii jeho prechodového diagramu.
Úloha 1.1
Skonštruujte regulárny výraz a jemu zodpovedajúci prechodový diagram pre reťazce nad abecedou $A=\{x, y, z\}$, v ktorých bezprostredne po každom výskyte prvku $y$ nasleduje počet znakov $x$ rovný kladnému násobku čísla 2.
Krok 2
Jedným z pokročilých spôsobov špecifikácie jazyka je aj jeho akceptácia prostredníctvom automatu. Automat predstavuje v teoretickej informatike bežne využívaný matematický model, ktorý na vstupe prijíma reťazec symbolov. Tento reťazec spracuje a po konečnom počte ktorov rozhodne, či patrí do takto špecifikovaného jazyka alebo nie (hovoríme, že automat reťazec akceptuje resp. neakceptuje).
Existuje viacero druhov automatov, ktoré sa líšia svojou zložitosťou a typom jazykov, ktoré dokážu spracovať. Na tomto a nasledujúcom cvičení sa zameriame na najjednoduchší z nich, tzv. konečnostavové automaty, ktoré dokážu rozpoznať práve triedu regulárných jazykov.
Schému konečnostavového automatu môžeme vidieť na Obr. 1. Tento model sa skladá zo vstupnej pásky a konečnostavovej riadiacej jednotky. Na vstupnej páske je zapísaný reťazec, o ktorom má automat rozhodnúť, či patrí do daného jazyka. Riadiaca jednotka sa vždy nachádza v určitom stave a vidí jeden aktuálny symbol zo vstupnej pásky. Deterministický konečnostavový automat pracuje diskrétne po jednotlivých krokoch. V každom kroku prečíta aktuálny symbol zo vstupnej pásky, presunie čítaciu hlavu na nasledujúci symbol a prejde do nového stavu. Nový stav sa určí na základe pôvodného stavu a prečítaného vstupného symbolu. Na začiatku výpočtu je čítacia hlava nastavená na prvý symbol reťazca a konečnostavová riadiaca jednotka sa nachádza v tzv. počiatočnom stave. Výpočet sa skončí, keď sa spracuje celý vstupný reťazec, alebo keď sa automat zasekne. Reťazec je následne akceptovaný, ak automat po spracovaní celého vstupu skončí v akceptačnom stave.
Uvedené skutočnosti teraz formalizujeme následovne. Deterministický konečnostavový automat (DKA) je usporiadaná pätica $$ M = (Q, T, \delta, q_0, F), $$ kde:
- $ Q $ je konečná množina stavov automatu,
- $ T $ je konečná množina prípustných vstupných symbolov (alebo vstupná abeceda) automatu,
- $ \delta $ je prechodová funkcia automatu, $ \delta: Q \times T \rightharpoonup Q $,
- $ q_0 $ je počiatočný alebo iniciálny stav automatu, $ q_0 \in Q $,
- $ F $ je množina akceptačných stavov automatu, $F \subseteq Q $.
Upozornenie
Prechodová funkcia $\delta$ je parciálna (čiastočne definovaná) funkcia, čo je v rámci jej špecifikácie vyjadrené využitím symbolu "$\rightharpoonup$" namiesto symbolu "$\to$" vyhradeného pre úplne definované funkcie. Znamená to, že funkcia $\delta$ nemusí byť definovaná pre všetky prvky z jej domény $Q\times T$.
Konfigurácia DKA je usporiadaná dvojica $(q, w) \in Q \times T^* $, kde $q$ je aktuálny stav automatu a $w$ je ešte nespracovaná časť vstupu. Vo výpočte automatu sa vyskytujú taktiež osobitné konfigurácie, ktoré zohrávajú špecifickú úlohu pri akceptácii vstupu:
- Začiatočná konfigurácia automatu – je konfigurácia $ (q_0, w) $, kde $w$ je celý vstupný reťazec.
- Koncová konfigurácia automatu – je konfigurácia tvaru $(q, \varepsilon)$, teda taká, pri ktorej bol celý vstup spracovaný.
- Akceptujúca konfigurácia automatu – je koncová konfigurácia, v ktorej $q \in F$.
Poznámka
Všimnite si rozdiel medzi konfiguráciou, ktorá je prvkom množiny $Q\times T^*$ a argumentom prechodovej funkcie $\delta$, ktorý spadá do množiny $Q\times T$.
Na množine konfigurácií $Q \times T^* $ definujeme binárnu reláciu prechodu (alebo krok výpočtu) DKA, označovanú symbolom $\vdash$ nasledovne: Nech $q, p \in Q, w \in T^*, x \in T$. Potom $$ (q, xw) \vdash (p, w) \quad \textrm{vtedy a len vtedy, ak} \quad \delta(q, x) = p. $$
Výpočtom automatu $M$ nazývame konečnú lineárnu postupnosť konfigurácií $c_0, c_1, \dots c_k$, kde $k \in \mathbb{N}_0$ a pre každé $i \in \left\{0, 1, \ldots, k-1 \right\}$ platí $c_{i} \vdash c_{i+1}$, kde $c_{0}$ je začiatočná konfigurácia a $c_{k}$ je koncová konfigurácia. Každá konfigurácia má tvar $$c_i = (q_i, w_i),$$ pričom $q_i \in Q$ a $w_i \in T^*$. Takýto výpočet budeme označovať symbolicky $c_0 \vdash^k c_k$, kde $k$ predstavuje počet krokov výpočtu.
Na vyjadrenie výpočtového správania automatu v ľubovoľnom počte krokov využijeme uzávery relácie prechodu $\vdash$. Nech $M = (Q, T, \delta, q_0, F)$ je DKA a $\vdash$ je jeho relácia kroku výpočtu. Potom definujeme:
- Tranzitívny uzáver relácie ($\vdash$ alebo $\vdash^{+}$) – je množina všetkých dvojíc $(c, c') \in (Q \times T^{*})^2$, pre ktoré existuje $k \in \mathbb{N}$ a konfigurácie $c_0, \ldots, c_k$ také, že $$c = c_0 \vdash c_1 \vdash \cdots \vdash c_k = c', \qquad k \ge 1.$$
- Reflexívny a tranzitívny uzáver relácie ($\vdash$ alebo $\vdash^{*}$) – je množina všetkých dvojíc $(c, c') \in (Q \times T^{*})^2,$ pre ktoré existuje $k \in \mathbb{N}_0$ a konfigurácie $c_0, \ldots, c_k$ také, že $$c = c_0 \vdash c_1 \vdash \cdots \vdash c_k = c', \qquad k \ge 0.$$
Reťazec $w$ je akceptovaný DKA $M$, ak existuje výpočet, ktorého prvá konfigurácia je začiatočná konfigurácia so vstupným reťazcom $w$ a posledná konfigurácia je koncová akceptujúca konfigurácia, teda $$(q_0,w) \vdash^{*} (q,\varepsilon)\text{, kde }q\in F.$$
Jazyk $L(M)$ rozpoznávaný alebo taktiež akceptovaný konečným automatom $M$ je množina: $$ L(M) = \{ w \mid (q_0, w) \vdash^* (q, \varepsilon) \land w \in T^* \land q \in F \} $$
Krok 3
Konečnostavové automaty sa v teoerickej informatike bežne vytvárajú prostredníctvom zostavenia prechodových tabuliek (ako je tomu v zbierke v kapitole 3.2). Takto vytvorené automaty sú nedeterministické, čo znamená, že pri niektorom vstupe $x$ je prechodová funkcia definovaná nie do jediného možného stavu $q$, ale do množiny možných stavov $Q'\subseteq Q$.
Na transformáciu NKA na DKA sa využíva metóda nazývaná determinizácia. My sa však teraz oboznámime s metódou priamej konštrukcie DKA za základe daného regulárneho výrazu tzv. metódou tokenou. Tá je uvedená v animovanej prezentácii a taktiež detailne popísaná na nasledujúcich príkladoch.
Príklad
Nájdite DKA, ktorý reprezentuje regulárny výraz pre jazyk binárnych numerálov $0|1\{0|1\}$.
Riešenie
Postupovať budeme nasledujúcim spôsobom.
-
Pre daný regulárny výraz nakreslíme prechodový diagram:
Obr. 3: Prechodový diagram pre regulárny výraz $0|1\{0|1\}$ -
Určíme počiatočný stav $q_0$ umiestnením tokenu: Stav $q_0 = \textcolor{red}{\bullet}0|\textcolor{red}{\bullet}1\{0|1\}$
Obr. 4: Počiatočný stav -
Vygenerujeme jednotlivé stavy pomocou prechodovej funkcie $\delta$:
-
$\delta(q_0, 0) = q_1$, dostávame stav $q_1 = 0|1\{0|1\}\textcolor{red}{\bullet}$ (koncový stav)
Obr. 5: Zmena stavu na $q_1$ -
$\delta(q_0, 1) = q_2$, dostávame stav $q_2 = 0|1\{\textcolor{red}{\bullet}0|\textcolor{red}{\bullet}1\}\textcolor{red}{\bullet}$ (koncový stav)
Obr. 6: Zmena stavu na $q_2$ -
$\delta(q_1, 0) = \bot$, prechod nie je definovaný
-
$\delta(q_1, 1) = \bot$, podobne ako v predošlom prípade
-
$\delta(q_2, 0) = q_2$,
-
$\delta(q_2, 1) = q_2$
- Zostavíme tabuľku pre prechodovú funkciu:
| stav | 0 | 1 |
|---|---|---|
| $q_0$ | $q_1$ | $q_2$ |
| $q_1$ | $\bot$ | $\bot$ |
| $q_2$ | $q_2$ | $q_2$ |
- Z prechodovej tabuľky nakreslíme diagram DKA:
Obr. 7: DKA pre regulárny výraz $0|1\{0|1\}$
Poznámka
Symbolom $\bot$ označujeme nedefinovanú hodnotu.
Príklad
Vykonajte výpočty na základe vyššie uvedeného DKA nad reťazcami $\varepsilon$, $01$, $101$.
Riešenie
a) $(q_0,\varepsilon)\nvdash$
Ide o špeciálny prípad výpočtu kedy, začiatočná konfigurácia je zároveň poslednou konfiguráciou výpočtu. Tá však nie je akceptujúca, keďže stav $q_0$ nie je koncový. Reťazec $\varepsilon$ preto nie je akceptovaný.
b) $(q_0,01)\vdash(q_1,1)\nvdash$
Reťazec $01$ nie je akceptovaný, hovoríme že automat sa zasekol v konfigurácii $(q_1,1)$.
c) $(q_0,101)\vdash(q_2,01)\vdash(q_2,1)\vdash(q_2,\varepsilon)\nvdash$
Reťazec $101$ je akceptovaný.
Príklad
Nájdite DKA, ktorý reprezentuje nasledujúci regulárny výraz $\{xy|z[x]\}y$.
Riešenie
Postupovať budeme nasledujúcim spôsobom.
-
Pre daný regulárny výraz nakreslíme prechodový diagram:
Obr. 8: Prechodový diagram pre regulárny výraz $\{xy|z[x]\}y$ -
Určíme počiatočný stav $q_0$ umiestnením tokenu: Stav $q_0 =\{\textcolor{red}{\bullet}xy|\textcolor{red}{\bullet}z[x]\}\textcolor{red}{\bullet}y$
Obr. 9: Počiatočný stav -
Vygenerujeme jednotlivé stavy pomocou prechodovej funkcie $\delta$:
-
$\delta(q_0, x) = q_1$, dostávame stav $q_1 =\{x\textcolor{red}{\bullet}y|z[x]\}y$
Obr. 10: Zmena stavu na $q_1$ -
$\delta(q_0, y) = q_2$, dostávame stav $q_2 =\{xy|z[x]\}y\textcolor{red}{\bullet}$ (koncový stav)
Obr. 11: Zmena stavu na $q_2$ -
$\delta(q_0, z) = q_3$, dostávame stav $q_3 =\{\textcolor{red}{\bullet}xy|\textcolor{red}{\bullet}z[\textcolor{red}{\bullet}x]\}\textcolor{red}{\bullet}y$
Obr. 12: Zmena stavu na $q_3$ -
$\delta(q_1, x) = \bot$,
-
$\delta(q_1, y) = q_0$,
-
$\delta(q_1, z) = \bot$,
-
$\delta(q_2, x) = \bot$,
-
$\delta(q_2, y) = \bot$,
-
$\delta(q_2, z) = \bot$,
-
$\delta(q_3, x) = q_4$, dostávame stav $q_4 =\{\textcolor{red}{\bullet}x\textcolor{red}{\bullet}y|\textcolor{red}{\bullet}z[x]\}\textcolor{red}{\bullet}y$
Obr. 13: Zmena stavu na $q_4$ -
$\delta(q_3, y) = q_2$,
-
$\delta(q_3, z) = q_3$,
-
$\delta(q_4, x) = q_1$,
-
$\delta(q_4, y) = q_5$, dostávame stav $q_5 =\{\textcolor{red}{\bullet}xy|\textcolor{red}{\bullet}z[x]\}\textcolor{red}{\bullet}y\textcolor{red}{\bullet}$ (koncový stav)
Obr. 14: Zmena stavu na $q_5$ -
$\delta(q_4, z) = q_3$,
-
$\delta(q_5, x) = q_1$,
-
$\delta(q_5, y) = q_2$,
-
$\delta(q_5, z) = q_3$,
- Zostavíme tabuľku pre prechodovú funkciu:
| stav | x | y | z |
|---|---|---|---|
| $q_0$ | $q_1$ | $q_2$ | $q_3$ |
| $q_1$ | $\bot$ | $q_0$ | $\bot$ |
| $q_2$ | $\bot$ | $\bot$ | $\bot$ |
| $q_3$ | $q_4$ | $q_2$ | $q_3$ |
| $q_4$ | $q_1$ | $q_5$ | $q_3$ |
| $q_5$ | $q_1$ | $q_2$ | $q_3$ |
- Z prechodovej tabuľky nakreslíme diagram DKA:
Obr. 15: DKA pre regulárny výraz $\{xy|z[x]\}y$
Okrem prezentovanej metódy tokenov existujú aj ďalšie spôsoby priamej konštrukcie DKA zo zadaného regulárneho výrazu, ako napríklad Brzozowského derivácia, s ktorou ste sa oboznamili na prednáškach.
Precvičte si vytváranie deterministického konečnostavového akceptora na základe zadaného regulárneho výrazu s využitím metódy tokenov na nasledujúcich príkladoch.
Úloha 3.1
Vytvorte prechodový diagram regulárneho výrazu $a[a]\{b\}b$ a zostavte jemu zodpovedajúci DKA. Na základe zostaveného DKA zistite, či reťazce $abbb$, $aaa$ patria do takto špecifikovaného jazyka.
Úloha 3.2
Vytvorte prechodový diagram regulárneho výrazu $[c]\{ab\}c$ a zostavte jemu zodpovedajúci DKA. Na základe zostaveného DKA zistite, či reťazce $ababc$, $caba$ patria do takto špecifikovaného jazyka.
Úloha 3.3
Vytvorte prechodový diagram regulárneho výrazu $\{b\}[c]ba$ a zostavte jemu zodpovedajúci DKA. Na základe zostaveného DKA zistite, či reťazce $bbca$, $cba$ patria do takto špecifikovaného jazyka.
Úloha 3.4
Vytvorte prechodový diagram regulárneho výrazu $a\{ab\}a[ab]b$ a zostavte jemu zodpovedajúci DKA. Na základe zostaveného DKA zistite, či reťazce $aababb$, $aaa$ patria do takto špecifikovaného jazyka.
Krok 4
V predchádzajúcom kroku tohto cvičenia sme sa naučili, ako z regulárneho výrazu priamo vytvoriť DKA. V tomto kroku sa oboznámime s dvoma základnými prístupmi k jeho praktickej programovej implementácii, a to:
- rekurzívnym resp. neiteratívnym a
- iteratívnym prístupom.
Najprv sa teda oboznámime so všeobecným princípom rekurzívnej implementácie DKA.
Vstupom tejto implementácie je reťazec a výstupom je informácia, či reťazec patrí alebo nepatrí do jazyka, teda pravdivostná hodnota. Každý stav $q_i \in Q$ bude v rámci tejto implementácie zodpovedať funkcii $$\texttt{qi:String -> Bool},$$ ktorá na vstupe prijíma reťazec a na výstupe poskytne pravdivostnú hodnotu. Princíp fungovania každej takejto funkcie je nasledovný:
- Ak ide o akceptačný stav ($q_i \in F$) a vstupný reťazec je prázdny ($\varepsilon$), funkcia vráti hodnotu $\texttt{True}$.
- Ak reťazec začína symbolom $x$ a prechodová funkcia určuje $\delta(q_i, x) = q_j$, funkcia rekurzívne zavolá $\texttt{qj}$ so zvyšným reťazcom (bez prvého symbolu) a vráti jej výsledok.
- Vo všetkých ostatných prípadoch funkcia vráti $\texttt{False}$.
$$\texttt{qi(}w\texttt{)} = \left\{\begin{array}{ll} \texttt{True}, & \text{ak } w=\varepsilon\;\land\;q_i\in F,\\ \texttt{qj(}w_r\texttt{)}, & \text{ak } w=xw_r\;\land\;\delta(q_i,x)=q_j,\\ \texttt{False}, & \text{inak. } \end{array}\color{transparent}\right\}$$
Nasledujúce fragmenty kódu predstavujú princíp implementácie koncových a nekoncových stavov DKA v jazyku python.
#implementácia funkcie zodpovedajúcej koncovému
#stavu qi
def qi(string: str) -> bool:
if len(string) == 0:
return True
else:
if string[0] == 'j':
return qj(string[1:])
#...
elif string[0] == 'k':
return qk(string[1:])
else:
return False
#implementácia funkcie zodpovedajúcej stavu qi
def qi(string: str) -> bool:
if len(string) > 0:
if string[0] == 'j':
return qj(string[1:])
#...
elif string[0] == 'k':
return qk(string[1:])
else:
return False
return False
Volaním funkcie $\texttt{q0}$ zodpovedajúcej počiatočnému stavu implementovaného DKA s argumentom predstavujúcim vstupný reťazec spustíme samotný výpočet DKA. Keďže tento prístup využíva pre spracovanie symbolov vstupného reťazca postupné volanie funkcií zodpovedajúcich jednotlivým stavom implementovaného DKA, hovoríme o rekurzívnom presnejšie povedané neiteratívnom prístupe k jeho implementácii.
Príklad
Implementuje rekurzívny variant DKA pre regulárny výraz binárnych numerálov. Zohľadnite skutočnosť, že na vstupe môže byť ľubovoľný reťazec. Váš program by mal vedieť rozoznať jazyk binárnych numerálov v zadanom reťazci.
Riešenie
def q0(string: str) -> bool:
if len(string) > 0:
if string[0] == '0':
return q1(string[1:])
elif string[0] == '1':
return q2(string[1:])
else:
return False
return False
def q1(string: str) -> bool:
if len(string) == 0:
return True
else:
return False
def q2(string: str) -> bool:
if len(string) == 0:
return True
else:
if string[0] in ('0', '1'):
return q2(string[1:])
else:
return False
if __name__ == "__main__":
input_string = input("Zadaj vstupný reťazec> ")
while input_string != "quit":
print(
f"Reťazec '{input_string}' "
f"{'JE' if q0(input_string) else 'NIE JE'} akceptovaný!"
)
input_string = input("Zadaj vstupný reťazec> ")
Úloha 4.1
Akú základnú programovaciu techniku reprezentuje slučka (prechod resp. výpočet tvaru $(q,xw)\vdash^{1}(q,w)$) v diagrame DKA v prípade jeho neiteratívnej implementácie? Možno túto techniku zachytiť aj iným typom výpočtu v DKA?
Úloha 4.2
Modifikujte vyššie uvedenú implementáciu tak, aby poskytovala informáciu o tom, v ktorom stave bol vstupný reťazec akceptovaný.
Úloha 4.3
Implementujte rekurzívny variant DKA pre regulárny výraz z druhého príkladu predchádzajúceho kroku.
Okrem vyššie uvedeného prístupu môžno zvoliť aj štandardnejší iteratívny prístup k implementácii DKA, kedy aktuálny stav automatu predstavuje internu premennú, ktorá sa pri postupnom čítaní jednotlivých symbolov vstupného reťazca mení na základe prechodovej funkcie $\delta$. Ak sa po prečítaní celého reťazca v tejto internej premennej nachádza koncový stav, reťazec je akceptovaný, v opačnom prípade reťazec nebude akceptovaný. Reťazec nebude akceptovaný taktiež v prípade, ak funkcia $\delta$ neposkytne žiadny nový stav po prečítaní niektorého zo symbolov vstupného reťazca.
Úloha 4.4
Podľa pokynov vyučujúceho samostatne implementujte iteratívne varianty DKA pre regulárne výrazy z prvého a druhého príkladu predchádzajúceho kroku. Prediskutujte realizované riešenia.
Doplňujúce úlohy
Úloha A.1
Preštudujte príklady na vytváranie regulárnych výrazov v zbierke od č. 1.13
Úloha A.2
Samostatne vyriešte príklady na vytváranie DKA v zbierke od č. 3.1 - precvičte si ich pomocou metódy vytvárania prechodových diagramov.
Úloha A.3
Precvičiť znalosti regulárnych výrazov (v ich bežnejšom zápise) môžete aj pomocou krížoviek. Skúste si niektoré regulárne výrazy z krížoviek prepísať do zápisu používaného na tomto cvičení.
Dotazník na ohodnotenie učiva
Vážení študenti,
týmto by sme Vás chceli požiadať o poskytnutie spätnej väzby k formátu a obsahu materiálov na cvičenia vo forme vyplnenie nasledujúceho dotazníka .
Tento dotazník slúži na ohodnotenie vašich skúseností s pripravenými materiálmi z druhého cvičenia. Na vylepšovaní materiálov na základe vašej spätnej väzby aktívne pracujeme, a to s cieľom zvýšiť kvalitu, zrozumiteľnosť a prínosnosť výučby.