Deterministické konečnostavové automaty.

Ciele

  1. Zopakovať si tvorbu regulárnych výrazov na základe slovnej špecifikácie.
  2. Definovať deterministický konečnostavovový automat (DKA) ako ekvivalentný spôsob určenia regulárneho jazyka.
  3. Konštruovať DKA na základe zadaného regulárneho výrazu.
  4. 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éma konečnostavového automatu
Obr. 2: Schéma konečnostavového automatu

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.

  1. Pre daný regulárny výraz nakreslíme prechodový diagram:

    Prechodový diagram pre regulárny výraz $0|1\{0|1\}$
    Obr. 3: Prechodový diagram pre regulárny výraz $0|1\{0|1\}$

  2. Určíme počiatočný stav $q_0$ umiestnením tokenu: Stav $q_0 = \textcolor{red}{\bullet}0|\textcolor{red}{\bullet}1\{0|1\}$

    Počiatočný stav
    Obr. 4: Počiatočný stav

  3. 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)

    Zmena stavu na $q_1$
    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)

    Zmena stavu na $q_2$
    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$

  1. 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$

  1. Z prechodovej tabuľky nakreslíme diagram DKA:
    DKA pre regulárny výraz $0|1\{0|1\}$
    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.

  1. Pre daný regulárny výraz nakreslíme prechodový diagram:

    Prechodový diagram pre regulárny výraz $\{xy|z[x]\}y$
    Obr. 8: Prechodový diagram pre regulárny výraz $\{xy|z[x]\}y$

  2. 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$

    Počiatočný stav
    Obr. 9: Počiatočný stav

  3. 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$

    Zmena stavu na $q_1$
    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)

    Zmena stavu na $q_2$
    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$

    Zmena stavu na $q_3$
    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$

    Zmena stavu na $q_4$
    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)

    Zmena stavu na $q_5$
    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$,

  1. 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$

  1. Z prechodovej tabuľky nakreslíme diagram DKA:
    DKA pre regulárny výraz $\{xy|z[x]\}y$
    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.

Fragment DKA
Obr. 16: Fragment DKA

#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

Fragment DKA
Obr. 17: Fragment DKA

#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.