Hoe eindige automaten naar reguliere expressies te converteren?
Geplaatst op februari 16, 2021 door adminHet omzetten van reguliere expressies naar (minimale) NFA die dezelfde taal accepteren is eenvoudig met standaard algoritmen, bijv. Thompsons algoritme . De andere richting lijkt echter vervelender en soms zijn de resulterende uitdrukkingen rommelig.
Welke algoritmen zijn zijn er voor het omzetten van NFA in equivalente reguliere expressies? Zijn er voordelen met betrekking tot tijdcomplexiteit of resultaatgrootte?
Dit wordt verondersteld een referentievraag te zijn. Voeg een algemene beschrijving van uw methode toe, evenals een niet-triviaal voorbeeld.
Reacties
- Let op een soortgelijke vraag op cstheory.SE die waarschijnlijk niet geschikt is voor ons publiek.
- alle antwoorden gebruiken een formele techniek om RE vanuit DFA te schrijven. Ik geloof dat mijn analysetechniek relatief eenvoudig en objectief is, laat ik in mijn antwoord zien : Wat is de taal van deze deterministische eindige automaten? Ik denk dat het soms nuttig zou zijn. Ja, natuurlijk gebruik ik zelf soms een formele methode (stelling van Arden) RE schrijven is vraag is complex zoals in dit voorbeeld wordt gegeven: Hoe een reguliere expressie voor een DFA te schrijven
Antwoord
Er zijn verschillende methoden om de conversie uit te voeren van eindige automaten naar reguliere expressies. Hier zal ik degene beschrijven die gewoonlijk op school wordt onderwezen en die erg visueel is. Ik geloof dat het in de praktijk het meest wordt gebruikt. Het schrijven van het algoritme is echter niet zon goed idee.
Statusverwijderingsmethode
Dit algoritme gaat over het omgaan met de grafiek van de automaat en is dus niet erg geschikt voor algoritmen omdat het graf primitieven zoals … staat verwijderen. Ik zal het beschrijven met primitieven van een hoger niveau.
Het belangrijkste idee
Het idee is om reguliere expressies op randen te overwegen en vervolgens tussenliggende toestanden te verwijderen terwijl de labels aan de randen consistent blijven.
Het belangrijkste patroon is te zien in de volgende figuren. De eerste heeft labels tussen $ p, q, r $ die reguliere expressies $ e, f, g, h, i $ zijn en we willen $ q $ verwijderen.
Eenmaal verwijderd, stellen we $ e, f, g, h, i $ samen op (met behoud van de andere randen tussen $ p $ en $ r $ maar dit wordt niet weergegeven op dit):
Voorbeeld
Met hetzelfde voorbeeld als in Raphaels antwoord :
we verwijderen achtereenvolgens $ q_2 $:
en vervolgens $ q_3 $:
dan moeten we nog steeds een ster op de uitdrukking toepassen van $ q_1 $ tot $ q_1 $. In dit geval is de eindtoestand ook initiaal, dus we hoeven alleen maar een ster toe te voegen:
$$ (ab + (b + aa) (ba) ^ * (a + bb)) ^ * $$
Algoritme
L[i,j]
is de regexp van de taal van $ q_i $ tot $ q_j $. Eerst verwijderen we alle mul ti-edge:
for i = 1 to n: for j = 1 to n: if i == j then: L[i,j] := ε else: L[i,j] := ∅ for a in Σ: if trans(i, a, j): L[i,j] := L[i,j] + a
Nu, de statusverwijdering. Stel dat we de status $ q_k $:
remove(k): for i = 1 to n: for j = 1 to n: L[i,i] += L[i,k] . star(L[k,k]) . L[k,i] L[j,j] += L[j,k] . star(L[k,k]) . L[k,j] L[i,j] += L[i,k] . star(L[k,k]) . L[k,j] L[j,i] += L[j,k] . star(L[k,k]) . L[k,i]
willen verwijderen: zowel met een potlood papier als met een algoritme moet je uitdrukkingen zoals star(ε)=ε
, e.ε=e
, ∅+e=e
, ∅.e=∅
(Door hand je schrijft gewoon de rand niet als het niet $ ∅ $ is, of zelfs $ ε $ voor een zelflus en je negeert wanneer er geen overgang is tussen $ q_i $ en $ q_k $ of $ q_j $ en $ q_k $)
Hoe gebruik je nu remove(k)
? Je moet de laatste of begintoestanden niet lichtjes verwijderen, anders mis je delen van de taal.
for i = 1 to n: if not(final(i)) and not(initial(i)): remove(i)
Als je maar één eindtoestand $ q_f $ en één hebt begintoestand $ q_s $ dan is de laatste uitdrukking:
e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])
Als je meerdere eindtoestanden (of zelfs begintoestanden) hebt, is er geen eenvoudige manier om samen te voegen deze, behalve het toepassen van de transitieve sluitingsmethode. Meestal is dit geen probleem met de hand, maar dit is lastig bij het schrijven van het algoritme. Een veel eenvoudigere oplossing is om alle paren $ (s, f) $ op te sommen en het algoritme op de (reeds verwijderde) grafiek uit te voeren om alle uitdrukkingen $ e_ {s, f} $ te krijgen, ervan uitgaande dat $ s $ de enige begintoestand is en $ f $ is de enige eindtoestand, waarna alle $ e_ {s, f} $ worden samengevoegd.
Dit, en het feit dat dit talen dynamischer wijzigt dan de eerste methode, maakt het meer foutgevoelig bij het programmeren. Ik raad aan om een andere methode te gebruiken.
Nadelen
Er zijn veel gevallen in dit algoritme, bijvoorbeeld om te kiezen welk knooppunt we moeten verwijderen, het aantal eindtoestanden aan het einde , het feit dat een eindtoestand ook initieel kan zijn, enz.
Merk op dat nu het algoritme is geschreven, dit veel lijkt op de transitieve sluitingsmethode.Alleen de context van het gebruik is anders. Ik raad het implementeren van het algoritme niet aan, maar het is een goed idee om de methode te gebruiken om dat met de hand te doen.
Opmerkingen
- In het voorbeeld 2e afbeelding, na het verwijderen van knooppunt ” 2 “, er ontbreekt een rand – lusrand (ab) in knooppunt A.
- @Kabamaru: opgelost. Maar nu denk ik dat $ \ varepsilon $ in de 3e afbeelding ook
ab
zou moeten zijn, en op dezelfde manier misschien in de laatste reguliere expressie. - Je kunt het algoritme maken werk voor een willekeurig aantal begin- en eindtoestanden door een nieuwe initiaal $ q ^ + $ en een nieuwe eindtoestand $ q ^ – $ toe te voegen, en deze te verbinden met de oorspronkelijke begin- en eindtoestanden door $ \ varepsilon $ -edges. Verwijder nu alle de oorspronkelijke toestanden. De uitdrukking wordt dan gevonden op de enkele resterende rand van $ q ^ + $ tot $ q _- $. De constructie geeft geen loops op $ q ^ + $ of $ q _- $ aangezien deze toestanden geen ingaande resp. uitgaande randen. Of als je streng bent, zullen ze labels hebben die de lege set vertegenwoordigen.
- Er is nog steeds een probleem met het tweede voorbeeld: vóór vereenvoudiging accepteert de automaat ” ba “, (1, 3, 1) maar na vereenvoudiging niet ‘ t.
Antwoord
Methode
De leukste methode die ik heb gezien is er een die de automaat uitdrukt als vergelijkingssysteem van (reguliere) talen die kan opgelost worden. Het is vooral mooi omdat het beknoptere uitdrukkingen lijkt te geven dan andere methoden.
Laat $ A = (Q, \ Sigma, \ delta, q_0, F) $ een NFA zonder $ \ varepsilon $ – overgangen. Maak voor elke staat $ q_i $ de vergelijking
$ \ qquad \ displaystyle Q_i = \ bigcup \ limits_ {q_i \ overset {a} {\ to} q_j} aQ_j \ cup \ begin {cases} \ {\ varepsilon \} &, \ q_i \ in F \\ \ emptyset &, \ text {else} \ end {cases} $
waarbij $ F $ de verzameling laatste toestanden is en $ q_i \ overset {a} {\ to} q_j $ betekent dat er een overgang is van $ q_i $ naar $ q_j $ gelabeld met $ a $ . Als je $ \ cup $ leest als $ + $ of $ \ mid $ (afhankelijk van je reguliere expressiedefinitie), zie je dat dit een vergelijking is van reguliere expressies.
Voor het oplossen van het systeem heb je associativiteit nodig en distributiviteit van $ \ cup $ en $ \ cdot $ (stringconcatenation), commutativiteit van $ \ cup $ en Ardens Lemma ¹:
Laat $ L, U, V \ subseteq \ Sigma ^ * $ gewone talen met $ \ varepsilon \ notin U $. Dan,
$ \ qquad \ displaystyle L = UL \ cup V \ quad \ Longleftrightarrow \ quad L = U ^ * V $
De oplossing is een reeks reguliere expressies $ Q_i $, één voor elke staat $ q_i $. $ Q_i $ beschrijft precies die woorden die kunnen worden geaccepteerd door $ A $ wanneer ze worden gestart in $ q_i $; daarom is $ Q_0 $ (als $ q_0 $ de begintoestand is) gewenste expressie.
Voorbeeld
Voor de duidelijkheid duiden we singleton-sets aan met hun element, dwz $ a = \ {a \} $. voorbeeld is te danken aan Georg Zetzsche.
Beschouw t zijn NFA:
[ bron ]
Het overeenkomstige vergelijkingssysteem is:
$ \ qquad \ begin {align} Q_0 & = aQ_1 \ cup bQ_2 \ cup \ varepsilon \\ Q_1 & = bQ_0 \ cup aQ_2 \\ Q_2 & = aQ_0 \ cup bQ_1 \ end {align} $
Steek nu de derde vergelijking in de tweede:
$ \ qquad \ begin {align} Q_1 & = bQ_0 \ cup a (aQ_0 \ cup bQ_1) \\ & = abQ_1 \ cup (b \ cup aa) Q_0 \\ & = (ab) ^ * ( b \ cup aa) Q_0 \ end {align} $
Voor de laatste stap passen we Ardens Lemma toe met $ L = Q_1 $, $ U = ab $ en $ V = (b \ cup aa) \ cdot Q_0 $. Merk op dat alle drie de talen normaal zijn en $ \ varepsilon \ notin U = \ {ab \} $, waardoor we het lemma kunnen toepassen. Nu voegen we dit resultaat toe aan de eerste vergelijking:
$ \ qquad \ begin {align} Q_0 & = a (ab) ^ * (b \ cup aa ) Q_0 \ cup baQ_0 \ cup bb (ab) ^ * (b \ cup aa) Q_0 \ cup \ varepsilon \\ & = ((a \ cup bb) (ab) ^ * (b \ cup aa) \ cup ba) Q_0 \ cup \ varepsilon \\ & = ((a \ cup bb) (ab) ^ * (b \ cup aa) \ cup ba) ^ * \ qquad \ text {(door Ardens Lemma)} \ end {align} $
We hebben dus een reguliere expressie gevonden voor de taal die door bovenstaande automaat wordt geaccepteerd, namelijk
$ \ qquad \ displaystyle ((a + bb) (ab) ^ * (b + aa) + ba) ^ *. $
Merk op dat het vrij beknopt is (vergelijk met de resultaat van andere methoden) maar niet uniek bepaald; het oplossen van het vergelijkingssysteem met een andere reeks manipulaties leidt tot andere – equivalente! – uitdrukkingen.
- Voor een bewijs van Arden ” s Lemma, zie hier .
Reacties
- Wat is de tijdcomplexiteit van dit algoritme? Is er een grens aan de grootte van de geproduceerde uitdrukking?
- @jmite: ik heb geen idee. Ik ‘ denk niet dat ik ‘ zou proberen dit te implementeren (andere methoden lijken in dit opzicht beter haalbaar) maar gebruik het als een pen-en-papier-methode.
- Hier ‘ is een Prolog-implementatie van dit algoritme: github.com / wvxvw / intro-to-automata-theory / blob / master / automata / … maar het
maybe_union/2
predikaat zou kunnen gebruiken meer werk (vooral wat betreft het elimineren van gemeenschappelijke prefix) om nettere reguliere expressies te maken. Een andere manier om deze methode te zien, is door het te begrijpen als vertaling van regex naar rechtslineaire grammatica, waar talen met Prolog-achtige unificatie of ML-achtige patroonafstemming zeer goede transducers opleveren, dus ‘ is niet alleen een algoritme voor pen en papier 🙂 - Slechts één vraag. De ε in de eerste vergelijking is omdat Qo een starttoestand is of omdat het ‘ een eindtoestand is? Hetzelfde geldt als ik twee eindtoestanden heb?
- @PAOK Controleer de definitie van $ Q_i $ hierboven (de regel); het ‘ s omdat $ q_0 $ een eindtoestand is.
Antwoord
Brzozowski algebraïsche methode
Dit is dezelfde methode als beschreven in Raphaels antwoord , maar vanuit een punt van weergave van een systematisch algoritme, en dan, inderdaad, het algoritme. Het blijkt gemakkelijk en natuurlijk te implementeren als je eenmaal weet waar je moet beginnen. Het kan ook gemakkelijker zijn met de hand als het tekenen van alle automaten om de een of andere reden onpraktisch is.
Bij het schrijven van een algoritme moet je onthouden dat de vergelijkingen altijd lineair moeten zijn, zodat je een goede abstracte weergave van de vergelijkingen hebt, iets dat je kunt vergeten als je met de hand oplost.
Het idee van het algoritme
Ik zal “niet beschrijven hoe het werkt, aangezien het goed is gedaan in Raphaels antwoord dat ik stel voor om eerder te lezen.In plaats daarvan concentreer ik me op in welke volgorde je de vergelijkingen moet oplossen zonder al te veel extra co te doen mputaties of extra gevallen.
Beginnend met Ardens rule “s ingenieuze oplossing $ X = A ^ * B $ voor de taalvergelijking $ X = AX∪B $ we kunnen de automaat beschouwen als een set vergelijkingen van de vorm:
$$ X_i = B_i + A_ {i, 1} X_1 +… + A_ {i, n} X_n $$
we kunnen dit oplossen door inductie op $ n $ door de arrays $ A_ {i, j} $ en $ B_ {i, j} $ dienovereenkomstig bij te werken. Bij stap $ n $ hebben we:
$$ X_n = B_n + A_ {n, 1} X_1 +… + A_ {n, n} X_n $$
en De regel van Arden geeft ons:
$$ X_n = A_ {n, n} ^ * (B_n + A_ {n, 1} X_1 +… + A_ {n, n-1} X_ {n -1}) $$
en door $ B “_n = A_ {n, n} ^ * B_n $ en $ A” _ {n, i} = A_ {n, n} ^ * in te stellen A_ {n, i} $ krijgen we:
$$ X_n = B “_n + A” _ {n, 1} X_1 +… + A “_ {n, n-1} X_ {n -1} $$
en we kunnen dan alle behoeften van $ X_n $ in het systeem verwijderen door voor $ i j < n $:
$$ B “_i = B_i + A_ {i, n} B” _n $$ $$ A “_ {i, j} = A_ {i, j} + A_ {i, n} A “_ {n, j} $$
Wanneer we $ X_n $ when $ n = 1 $ hebben opgelost, krijgen we een vergelijking als deze:
$$ X_1 = B” _1 $$
zonder $ A “_ {1, i} $. Zo kregen we onze reguliere expressie.
Het algoritme
Dankzij dit kunnen we het algoritme bouwen. Om dezelfde conventie te hebben als in de inductie hierboven, zullen we zeggen dat de begintoestand $ q_1 $ is en dat het aantal toestanden $ m $ is. Ten eerste de initialisatie om $ B $:
for i = 1 to m: if final(i): B[i] := ε else: B[i] := ∅
en $ A $:
for i = 1 to m: for j = 1 to m: for a in Σ: if trans(i, a, j): A[i,j] := a else: A[i,j] := ∅
te vullen
en dan het oplossen:
for n = m decreasing to 1: B[n] := star(A[n,n]) . B[n] for j = 1 to n: A[n,j] := star(A[n,n]) . A[n,j]; for i = 1 to n: B[i] += A[i,n] . B[n] for j = 1 to n: A[i,j] += A[i,n] . A[n,j]
de laatste uitdrukking is dan:
e := B[1]
Implementatie
Zelfs als het een systeem van vergelijkingen lijkt dat te symbolisch lijkt voor een algoritme, is dit zeer geschikt voor een implementatie. Hier is een implementatie van dit algoritme in Ocaml (verbroken link) . Merk op dat afgezien van de functie brzozowski
, alles bedoeld is om af te drukken of te gebruiken voor het voorbeeld van Raphael. Merk op dat er een verrassend efficiënte functie is voor het vereenvoudigen van reguliere expressies simple_re
.
Reacties
- Link is dood …
- Implementatie in Javascript: github.com/devongovett/regexgen/blob/master/src/regex.js
- Bedankt voor deze geweldige uitleg. Als ik het goed begrijp, uw initialisatie-pseudo-code gaat ervan uit dat er voor gegeven i en j er maximaal één is zodat (i, a, j) een overgang is. Dit is correct als we ermee instemmen deze overgang te labelen met de regexp die overeenkomt met alle letters in Σ dat label overgangen van i naar j in de letterautomaat, maar dan is je notatie a in Σ een beetje vreemd omdat het niet echt een letter is.Als we letter voor letter gaan, kunnen we verschillende overgangen hebben van i naar j en moeten doe de vereniging van hun labels in het luslichaam.
Antwoord
Transitieve sluitingsmethode
Deze methode is gemakkelijk in een formulier te schrijven van een algoritme, maar genereert absurd grote reguliere expressies en is onpraktisch als je het met de hand doet, vooral omdat dit te systematisch is. Het is echter een goede en eenvoudige oplossing voor een algoritme.
Het sleutelidee
Laat $ R ^ k_ {i, j} $ de reguliere expressie vertegenwoordigen voor de strings vanaf $ q_i $ tot $ q_j $ met de staten $ \ {q_1,…, q_k \} $. Laat $ n $ het aantal toestanden van de automaat zijn.
Stel dat u de reguliere expressie $ R_ {i, j} $ al kent van $ q_i $ tot $ q_j $ zonder de tussenliggende toestand $ q_k $ (behalve voor extremiteiten), voor alle $ i, j $. Dan kun je raden hoe het toevoegen van een andere staat de nieuwe reguliere expressie $ R “_ {i, j} $ zal beïnvloeden: het verandert alleen als je directe overgangen hebt naar $ q_k $, en het kan zo worden uitgedrukt:
$$ R “_ {i, j} = R_ {i, j} + R_ {i, k}. R_ {k, k} ^ *. R_ {k, j} $$
($ R $ is $ R ^ {k-1} $ en $ R “$ is $ R ^ k $.)
Voorbeeld
We zullen hetzelfde voorbeeld gebruiken als in Raphaels antwoord . In eerste instantie kunt u alleen de directe overgangen gebruiken.
Hier is de eerste stap (merk op dat een zelflus met een label $ a $ de eerste $ ε $ zou hebben omgezet in $ (ε + a) $.
$$ R ^ 0 = \ begin {bmatrix} ε & a & b \\ b & ε & a \\ a & b & ε \ end {bmatrix} $$
Bij de tweede stap kunnen we $ q_0 $ gebruiken (die voor ons is hernoemd naar $ q_1 $, omdat $ R ^ 0 $ al wordt gebruikt voor het doel hierboven). We zullen zien hoe $ R ^ 1 $ werkt.
Van $ q_2 $ tot $ q_2 $: $ R ^ 1_ {2,2} = R ^ 0_ {2,2} + R ^ 0_ {2,1} {R ^ 0_ {1,1}} ^ * R ^ 0_ {1,2} = ε + b ε ^ * a = ε + ba $.
Hoe komt dat? Het is omdat van $ q_2 $ naar $ q_2 $ gaan met slechts $ q_1 $ als tussenstatus kan worden gedaan door hier te blijven ($ ε $) of door naar $ q_1 $ ($ a $) te gaan en daar te herhalen ($ ε ^ * $) en terugkomend ($ b $).
$$ R ^ 1 = \ begin {bmatrix} ε & a & b \\ b & ε + ba & a + bb \\ a & b + aa & ε + ab \ end {bmatrix} $$
Zo kun je ook $ R ^ 2 $ en $ R ^ 3 $ en $ R ^ 3_ {1,1 } $ geeft je de laatste uitdrukking omdat $ 1 $ zowel begin als eind is. Merk op dat hier veel vereenvoudiging van uitdrukkingen is gedaan. Anders zou de eerste $ a $ van $ R ^ 0 $ $ (∅ + a) $ zijn en zou de eerste $ a $ van $ R ^ 1 $ $ (((∅ + a) + ε (ε) ^ * a ) $.
Algoritme
Initialisatie:
for i = 1 to n: for j = 1 to n: if i == j: R[i,j,0] := ε else: R[i,j,0] := ∅ for a in Σ: if trans(i, a, j): R[i,j,0] := R[i,j,0] + a
Transitieve sluiting:
for k = 1 to n: for i = 1 to n: for j = 1 to n: R[i,j,k] := R[i,j,k-1] + R[i,k,k-1] . star(R[k,k,k-1]) . R(k,j,k-1)
Dan is de laatste uitdrukking (veronderstel dat $ q_s $ de begintoestand is):
e := ∅ for i = 1 to n: if final(i): e := e + R[s,i,n]
Maar je kunt je voorstellen het genereert lelijke reguliere expressies. Je kunt inderdaad dingen verwachten als $ (∅) ^ * + (a + (∅) ^ *) (ε) ^ * (a + ∅) $ die dezelfde taal vertegenwoordigt als $ aa $. Merk op dat het vereenvoudigen van een reguliere expressie in de praktijk handig is.
Geef een reactie