Come convertire gli automi finiti in espressioni regolari?
Su Febbraio 16, 2021 da adminLa conversione di espressioni regolari in (minimo) NFA che accettano lo stesso linguaggio è facile con algoritmi standard, ad es. Thompson “s algoritmo . Laltra direzione sembra essere più noiosa, tuttavia, e talvolta le espressioni risultanti sono disordinate.
Quali sono gli algoritmi per convertire NFA in espressioni regolari equivalenti? Ci sono vantaggi per quanto riguarda la complessità temporale o la dimensione del risultato?
Questa dovrebbe essere una domanda di riferimento. Includere una descrizione generale del metodo e una esempio non banale.
Commenti
- Nota una domanda simile su cstheory.SE che probabilmente non è adatto al nostro pubblico.
- tutte le risposte utilizzano una tecnica formale per scrivere RE da DFA. Credo che la mia tecnica tramite analisi sia relativamente facile e oggettiva che dimostro nelle mie risposte : Qual è il linguaggio di questi automi finiti deterministici? Penso che a volte sarebbe utile. Sì, naturalmente a volte io stesso uso il metodo formale (teorema di Arden) scrivere RE è la domanda è complessa come quella fornita in questo esempio: Come scrivere unespressione regolare per un DFA
Risposta
Esistono diversi metodi per eseguire la conversione da automi finiti a espressioni regolari. Qui descriverò quello che di solito viene insegnato a scuola che è molto visivo. Credo sia il più utilizzato nella pratica. Tuttavia, scrivere lalgoritmo non è una buona idea.
Metodo di rimozione dello stato
Questo algoritmo riguarda la gestione del grafico dellautoma e quindi non è molto adatto per gli algoritmi poiché necessita primitive di grafi come … rimozione dello stato. Lo descriverò usando primitive di livello superiore.
Lidea chiave
Lidea è di considerare le espressioni regolari sui bordi e quindi rimuovere gli stati intermedi mantenendo le etichette dei bordi coerenti.
Il modello principale può essere visto nelle figure seguenti. Il primo ha etichette comprese tra $ p, q, r $ che sono espressioni regolari $ e, f, g, h, i $ e vogliamo rimuovere $ q $.
Una volta rimosso, componiamo $ e, f, g, h, i $ insieme (preservando gli altri archi tra $ p $ e $ r $ ma questo non viene visualizzato su questo):
Esempio
Utilizzando lo stesso esempio di Raphael “risposta :
successivamente rimuoviamo $ q_2 $:
e poi $ q_3 $:
quindi dobbiamo ancora applicare una stella allespressione da $ q_1 $ a $ q_1 $. In questo caso, lo stato finale è anche iniziale, quindi dobbiamo solo aggiungere una stella:
$$ (ab + (b + aa) (ba) ^ * (a + bb)) ^ * $$
Algoritmo
L[i,j]
è lespressione regolare della lingua da $ q_i $ a $ q_j $. Per prima cosa, rimuoviamo tutti i 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
Ora, la rimozione dello stato. Supponiamo di voler rimuovere lo stato $ 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]
Nota che sia con una matita di carta che con un algoritmo dovresti semplificare espressioni come star(ε)=ε
, e.ε=e
, ∅+e=e
, ∅.e=∅
(di invece non scrivi il bordo quando non è $ ∅ $, o anche $ ε $ per un ciclo automatico e ignori quando non cè transizione tra $ q_i $ e $ q_k $ o $ q_j $ e $ q_k $)
Ora, come utilizzare remove(k)
? Non dovresti rimuovere alla leggera gli stati finali o iniziali, altrimenti perderai parti della lingua.
for i = 1 to n: if not(final(i)) and not(initial(i)): remove(i)
Se hai solo uno stato finale $ q_f $ e uno stato iniziale $ q_s $ allora lespressione finale è:
e := star(L[s,s]) . L[s,f] . star(L[f,s] . star(L[s,s]) . L[s,f] + L[f,f])
Se hai diversi stati finali (o anche stati iniziali) non esiste un modo semplice per unire questi, oltre ad applicare il metodo della chiusura transitiva. Di solito questo non è un problema a mano ma è scomodo quando si scrive lalgoritmo. Una soluzione molto più semplice è enumerare tutte le coppie $ (s, f) $ ed eseguire lalgoritmo sul grafico (già rimosso dallo stato) per ottenere tutte le espressioni $ e_ {s, f} $ supponendo che $ s $ sia lunico stato iniziale e $ f $ è lunico stato finale, quindi lunione di tutti i $ e_ {s, f} $.
Questo, e il fatto che questo modifichi le lingue in modo più dinamico rispetto al primo metodo, lo rendono più soggetto a errori durante la programmazione. Suggerisco di utilizzare qualsiasi altro metodo.
Contro
Ci sono molti casi in questo algoritmo, ad esempio per scegliere quale nodo rimuovere, il numero di stati finali alla fine , il fatto che anche uno stato finale possa essere iniziale ecc.
Nota che ora che lalgoritmo è scritto, questo è molto simile al metodo di chiusura transitiva.Solo il contesto di utilizzo è diverso. Non consiglio di implementare lalgoritmo, ma utilizzare il metodo per farlo manualmente è una buona idea.
Commenti
- Nellesempio, seconda immagine, dopo aver rimosso il nodo ” 2 “, manca un bordo – bordo del ciclo (ab) nel nodo A.
- @Kabamaru: risolto. Ma ora penso che $ \ varepsilon $ nella terza immagine dovrebbe essere anche
ab
, e allo stesso modo forse nellespressione regolare finale. - Puoi creare lalgoritmo lavorare per un numero qualsiasi di stati iniziali e finali aggiungendo un nuovo $ q ^ + $ iniziale e un nuovo stato finale $ q ^ – $ e collegandoli agli stati iniziale e finale originali mediante $ \ varepsilon $ -edges. Ora rimuovi tutti gli stati originali. Lespressione si trova quindi sul singolo bordo rimanente da $ q ^ + $ a $ q _- $. La costruzione non darà loop a $ q ^ + $ o $ q _- $ poiché questi stati non hanno una risposta in entrata. bordi in uscita. Oppure, se sei rigoroso, avranno etichette che rappresentano linsieme vuoto.
- Cè ancora un problema con il secondo esempio: prima della semplificazione gli automi accettano ” ba “, (1, 3, 1) ma dopo la semplificazione non ‘ t.
Risposta
Metodo
Il metodo più carino che ho visto è quello che esprime lautoma come sistema di equazioni di linguaggi (normali) che possono essere risolto. È particolarmente utile in quanto sembra produrre espressioni più concise rispetto ad altri metodi.
Sia $ A = (Q, \ Sigma, \ delta, q_0, F) $ un NFA senza $ \ varepsilon $ – transizioni. Per ogni stato $ q_i $, crea lequazione
$ \ 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} $
dove $ F $ è linsieme degli stati finali e $ q_i \ overset {a} {\ to} q_j $ significa che cè una transizione da $ q_i $ a $ q_j $ etichettata con $ a $ . Se leggi $ \ cup $ come $ + $ o $ \ mid $ (a seconda della definizione dellespressione regolare), vedrai che questa è unequazione delle espressioni regolari.
Per risolvere il sistema hai bisogno dellassociatività e distributività di $ \ cup $ e $ \ cdot $ (concatenazione di stringhe), commutatività di $ \ cup $ e Arden “s Lemma ¹:
Siano $ L, U, V \ subseteq \ Sigma ^ * $ lingue regolari con $ \ varepsilon \ notin U $. Quindi,
$ \ qquad \ displaystyle L = UL \ cup V \ quad \ Longleftrightarrow \ quad L = U ^ * V $
La soluzione è un insieme di espressioni regolari $ Q_i $, uno per ogni stato $ q_i $. $ Q_i $ descrive esattamente quelle parole che possono essere accettate da $ A $ quando iniziate in $ q_i $; quindi $ Q_0 $ (se $ q_0 $ è lo stato iniziale) è espressione desiderata.
Esempio
Per motivi di chiarezza, denotiamo insiemi singleton tramite il loro elemento, ad esempio $ a = \ {a \} $. lesempio è dovuto a Georg Zetzsche.
Considera t il suo NFA:
[ sorgente ]
Il sistema di equazioni corrispondente è:
$ \ 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} $
Ora collega la terza equazione alla seconda:
$ \ 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} $
Per lultimo passaggio, applichiamo il Lemma di Arden con $ L = Q_1 $, $ U = ab $ e $ V = (b \ cup aa) \ cdot Q_0 $. Nota che tutte e tre le lingue sono regolari e $ \ varepsilon \ notin U = \ {ab \} $, permettendoci di applicare il lemma. Ora inseriamo questo risultato nella prima equazione:
$ \ 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 {(dal Lemma di Arden)} \ end {align} $
Quindi, abbiamo trovato unespressione regolare per il linguaggio accettato dallautoma di cui sopra, ovvero
$ \ qquad \ displaystyle ((a + bb) (ab) ^ * (b + aa) + ba) ^ *. $
Nota che è abbastanza succinto (confronta con il risultato di altri metodi) ma non determinato in modo univoco; risolvere il sistema di equazioni con una diversa sequenza di manipolazioni porta ad altre espressioni equivalenti.
- Per una dimostrazione di Arden ” s Lemma, vedere qui .
Commenti
- Cosa è la complessità temporale di questo algoritmo? Cè un limite alla dimensione dellespressione prodotta?
- @jmite: non ne ho idea. Non ‘ penso di ‘ provare a implementarlo (altri metodi sembrano essere più fattibili a questo proposito) ma lo uso come un metodo penna e carta.
- Qui ‘ unimplementazione Prolog di questo algoritmo: github.com / wvxvw / intro-to-automata-theory / blob / master / automata / … ma il suo predicato
maybe_union/2
potrebbe utilizzare più lavoro (specialmente leliminazione del prefisso comune) per rendere più ordinate le espressioni regolari. Un altro modo per vedere questo metodo è interpretarlo come una traduzione da regex a grammatica lineare a destra, dove le lingue con unificazione simile a Prolog o corrispondenza di pattern in stile ML sono ottimi trasduttori, quindi ‘ non è solo un algoritmo di carta e penna 🙂 - Solo una domanda. La ε nella prima equazione è perché Qo è uno stato iniziale o perché ‘ è uno stato finale? Allo stesso modo se si applicano due stati finali?
- @PAOK Controlla la definizione di $ Q_i $ sopra (la riga); è ‘ perché $ q_0 $ è uno stato finale.
Risposta
Metodo algebrico di Brzozowski
Questo è lo stesso metodo descritto nella risposta di Raphael , ma partendo da un punto di vista di un algoritmo sistematico, e quindi, appunto, lalgoritmo. Risulta essere facile e naturale da implementare una volta che sai da dove cominciare. Inoltre potrebbe essere più facile a mano se disegnare tutti gli automi è poco pratico per qualche motivo.
Quando scrivi un algoritmo devi ricordare che le equazioni devono essere sempre lineari in modo da avere una buona rappresentazione astratta delle equazioni, cosa che puoi dimenticare quando stai risolvendo a mano.
Lidea dellalgoritmo
Non descriverò come funziona poiché è ben fatto nella risposta di Raphael che ho suggerisco di leggere prima, invece, mi concentro sullordine in cui dovresti risolvere le equazioni senza fare troppe operazioni extra mputazioni o casi extra.
A partire dalla soluzione ingegnosa della regola di Arden “$ X = A ^ * B $ allequazione del linguaggio $ X = AX∪B $ possiamo considerare lautoma come un insieme di equazioni della forma:
$$ X_i = B_i + A_ {i, 1} X_1 +… + A_ {i, n} X_n $$
possiamo risolvere questo problema per induzione su $ n $ aggiornando di conseguenza gli array $ A_ {i, j} $ e $ B_ {i, j} $. Al passaggio $ n $, abbiamo:
$$ X_n = B_n + A_ {n, 1} X_1 +… + A_ {n, n} X_n $$
e La regola di Arden ci fornisce:
$$ X_n = A_ {n, n} ^ * (B_n + A_ {n, 1} X_1 +… + A_ {n, n-1} X_ {n -1}) $$
e impostando $ B “_n = A_ {n, n} ^ * B_n $ e $ A” _ {n, i} = A_ {n, n} ^ * A_ {n, i} $ otteniamo:
$$ X_n = B “_n + A” _ {n, 1} X_1 +… + A “_ {n, n-1} X_ {n -1} $$
e possiamo quindi rimuovere tutte le esigenze di $ X_n $ nel sistema impostando, per $ i, j < n $:
$$ B “_i = B_i + A_ {i, n} B” _n $$ $$ A “_ {i, j} = A_ {i, j} + A_ {i, n} A “_ {n, j} $$
Quando abbiamo risolto $ X_n $ quando $ n = 1 $, otteniamo unequazione come questa:
$$ X_1 = B” _1 $$
senza $ A “_ {1, i} $. Così abbiamo ottenuto la nostra espressione regolare.
Lalgoritmo
Grazie a questo, possiamo costruire lalgoritmo. Per avere la stessa convenzione che nellinduzione sopra, diremo che lo stato iniziale è $ q_1 $ e che il numero di stato è $ m $. Innanzitutto, linizializzazione per riempire $ B $:
for i = 1 to m: if final(i): B[i] := ε else: B[i] := ∅
e $ 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] := ∅
e quindi la risoluzione:
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]
lespressione finale è quindi:
e := B[1]
Implementazione
Anche se può sembrare un sistema di equazioni che sembra troppo simbolico per un algoritmo, questo è adatto per unimplementazione. Ecco unimplementazione di questo algoritmo in Ocaml (collegamento interrotto) . Nota che a parte la funzione brzozowski
, tutto è da stampare o da usare per lesempio di Raphael. Nota che esiste una funzione sorprendentemente efficiente di semplificazione delle espressioni regolari simple_re
.
Commenti
- Link is dead …
- Implementazione in Javascript: github.com/devongovett/regexgen/blob/master/src/regex.js
- Grazie per questa ottima spiegazione. Se ho capito bene, il tuo pseudo-codice di inizializzazione presuppone che per i e j dati ci sia al massimo uno a tale che (i, a, j) sia una transizione. Questo è corretto se accettiamo di etichettare questa transizione con la regexp che corrisponde a tutte le lettere in Σ quelletichetta passa da i a j nellautoma delle lettere, ma la tua notazione a in Σ è un po strana in quanto non è realmente una lettera. Se andiamo lettera per lettera possiamo avere diverse transizioni da i a j e dobbiamo fare lunione delle loro etichette nel corpo del ciclo.
Risposta
Metodo di chiusura transitiva
Questo metodo è facile da scrivere in una forma di un algoritmo, ma genera espressioni regolari assurdamente grandi ed è poco pratico se lo fai a mano, soprattutto perché questo è troppo sistematico. Tuttavia è una buona e semplice soluzione per un algoritmo.
Lidea chiave
Che $ R ^ k_ {i, j} $ rappresenti lespressione regolare per le stringhe che vanno da $ q_i $ a $ q_j $ utilizzando gli stati $ \ {q_1,…, q_k \} $. Sia $ n $ il numero di stati dellautoma.
Supponi di conoscere già lespressione regolare $ R_ {i, j} $ da $ q_i $ a $ q_j $ senza lo stato intermedio $ q_k $ (eccetto per le estremità), per tutti $ i, j $. Quindi puoi immaginare come laggiunta di un altro stato influenzerà la nuova espressione regolare $ R “_ {i, j} $: cambia solo se hai transizioni dirette a $ q_k $, e può essere espressa così:
$$ R “_ {i, j} = R_ {i, j} + R_ {i, k}. R_ {k, k} ^ *. R_ {k, j} $$
($ R $ è $ R ^ {k-1} $ e $ R “$ è $ R ^ k $.)
Esempio
Useremo lo stesso esempio della risposta di Raphael . Allinizio puoi usare solo le transizioni dirette.
Ecco il primo passaggio (nota che un ciclo automatico con unetichetta $ a $ avrebbe trasformato il primo $ ε $ in $ (ε + a) $.
$$ R ^ 0 = \ begin {bmatrix} ε & a & b \\ b & ε & a \\ a & b & ε \ end {bmatrix} $$
Al secondo passo possiamo usare $ q_0 $ (che viene rinominato in $ q_1 $ per noi, perché $ R ^ 0 $ è già usato per lo scopo sopra). Vedremo come funziona $ R ^ 1 $.
Da $ q_2 $ a $ 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 $.
Perché? È perché passare da $ q_2 $ a $ q_2 $ usando solo $ q_1 $ come stato intermedio può essere fatto rimanendo qui ($ ε $) o andando a $ q_1 $ ($ a $), ripetendo lì ($ ε ^ * $) e tornando ($ b $).
$$ R ^ 1 = \ begin {bmatrix} ε & a & b \\ b & ε + ba & a + bb \\ a & b + aa & ε + ab \ end {bmatrix} $$
Puoi calcolare in questo modo anche $ R ^ 2 $ e $ R ^ 3 $ e $ R ^ 3_ {1,1 } $ ti darà lespressione finale perché $ 1 $ è sia iniziale che finale. Notare che qui è stata fatta molta semplificazione delle espressioni. Altrimenti il primo $ a $ di $ R ^ 0 $ sarebbe $ (∅ + a) $ e il primo $ a $ di $ R ^ 1 $ sarebbe $ ((∅ + a) + ε (ε) ^ * a ) $.
Algoritmo
Inizializzazione:
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
Chiusura transitiva:
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)
Quindi lespressione finale è (supponendo che $ q_s $ sia lo stato iniziale):
e := ∅ for i = 1 to n: if final(i): e := e + R[s,i,n]
Ma puoi immaginare genera brutte espressioni regolari. Puoi davvero aspettarti cose come $ (∅) ^ * + (a + (∅) ^ *) (ε) ^ * (a + ∅) $ che rappresenta la stessa lingua di $ aa $. Nota che semplificare unespressione regolare è utile nella pratica.
Lascia un commento