Equivalenza tra PDA e Grammatiche Context Free (CFG)

Traduzione non autorizzata delle pagine del capitolo 2.2 di Sipser. In blu, i miei commenti.

In questa sezione mostriamo che le grammatiche context-free e gli automi pushdown sono equivalenti nelle loro potenzialità. Entrambi sono in grado di descrivere la classe dei linguaggi context-free. Mostriamo come convertire ogni grammatica context-free in un automa pushdown che riconosce lo stesso linguaggio e viceversa. Ricordiamo che abbiamo definito un linguaggio context-free come un linguaggio che può essere descritto con una grammatica context-free, il nostro obiettivo nel teorema seguente.

2.20 Teorema

Un linguaggio è context-free se e solo se un automa pushdown lo riconosce.

Come sempre, quando c'è un "se e solo se" in un teoriema, abbiamo due direzioni da provare. In questo teorema entrambe le direzioni sono interessanti (=incasinate). Prima, facciamo la direzione "se", più facile.

2.21 Lemma

Se un linguaggio è context-free, allora un automa pushdown lo riconosce.

Idea

Sia A un CFL. Dalla definizione, sappiamo che A ha una CFG, G, che lo genera. Mostriamo come convertire G in un PDA equivalente, che chiamiamo P.

Il PDA P che ora descriviamo accetta i suoi input w, se G genera questo input, determinando se c'è una derivazione per w. Ricordiamo che una derivazione è semplicemente la sequenza di sostituzioni fatte allo stesso modo in cui una grammatica genera una stringa. Ogni passo della derivazione produce una stringa intermedia di variabili e terminali. Disegnamo P per determinare se qualche serie di sostituzioni, usando le regole di G, possono condurre dalla variabile di partenza a w.

Una delle difficoltà nel testare se c'è una derivazione per w è immaginare quali sostituzioni fare. Il nondeterminismo del PDA permette di indovinare la sequenza di sostituzioni corrette. A ogni passaggio della derivazione, una delle regole per una variabile particolare è selezionata nondeterministicamente e usata per sostituire questa variabile.

Il PDA P inizia scrivendo la variabile di partenza nel suo stack. Passa attraverso una serie di stringhe intermedie, facendo una sostituzione dopo l'altra. Eventualmente può arrivare a una stringa che contiene solo simboli terminali, che significa che ha usato la grammatica per derivare la stringa (bingo!). Allora P accetta se la stringa è identica a quella che ha ricevuto in input.

Implementare questa strategia su un PDA richiede un'idea addizionale. Abbiamo bisogno di vedere come il PDA memorizza la stringa intermedia per andare da una a un'altra. Usare semplicemente lo stack per memorizzare ogni stringa intermedia è una tentazione. Però questo non funzione bene, perché il PDA ha bisogno di trovare la variabile nella stringa intermedia e fare sostituzioni. Il PDA può accedere solo al simbolo in cima allo stack e che può essere un simbolo terminale dentro una variabile. Il modo per aggirare il poblema è tenere solo una parte della stringa intermedia nello stack: i simboli con cui inizia la prima variabile nella stringa intermedia. Ogni simbolo terminale che appare prima della prima variabile è messa in corrispondenza immediatamente con i simboli nella stringa di input. La figura seguente illustra il PDA P.

Figura 2.22: P rappresenta la stringa intermedia 01A1A0

Di seguito, una descrizione informale di P.

  1. Metti il simbolo marcatore $ e la variabile di partenza nello stack
  2. Ripeti i seguenti passi all'infinito
    1. se la cima dello stack è una variabile simbolo A scegliere nondeterministicamente una delle regole per A e sostituire A con la stringa sul lato destro della regola
    2. se la cima dello stack è il simbolo terminale a, leggere il simbolo successivo dall'input e confrontarlo con a. Se è lo stesso, ripetere. Se non è lo stesso, rigettare questo ramo del nondeterminismo
    3. se la cima dello stack è il simbolo terminale $, entrare nello stato accettato. Facendo così, si accetta l'input se è stato letto interamente.

Qui manca un esempio, perché non si capisce molto.

Immaginiamo un PDA fatto così:

S → aAb

A → # | aAa

La stringa aa#ab è riconosciuta? Servono alcuni passaggi: aaAab (A = #), aAb (A = aAa), S (S = aAb), per cui va mantenuta la stringa intermedia.

  1. metto nello stack il simbolo di dollaro $ seguito da aAb, cioè la variabile di partenza, esclusi i terminali a destra. Il mio stack contiene $aA, e siccome è una LIFO, estraggo nell'ordine Aa$
  2. la cima dello stack è una variabile, quindi nondeterministicamente sostituisco A con # e aAa in due diversi branch
    1. # ⇒ nello stack ho #a$, ma quando confronto a#b con la mia stringa, ottengo una differenza, poiché il secondo carattere non è # ma a
    2. aAa => nello stack ho aAaa$, quindi la mia stringa completa è aaAab, ma quando vado a ricontrollarla sostituisco nondeterministicamente A con # e con aAa
      1. # ⇒ ottengo aa#ab, che è la mia stringa
      2. aAa ⇒ scarto questa opzione perché otterrei aaaAaab, ma il terzo carattere è # e non a.

Non ho capito ancora in che modo funziona lo scarico/carico dello stack.

Prova

Ora diamo un dettaglio formale della costruzione dell'automa pushdown P = (Q, Σ, Γ, δ, q1, F) . Per fare una costruzione più chiara, usiamo l'abbreviazione per la funzione di transizione. QUesta notazione fornisce un modo per scrivere una stringa intera nello stack in un passaggio della macchina. Possiamo simulare questa azione introducendo degli stati addizionali per scrivere la stringa un simbolo alla volta, come implementato nella seguente costruzione formale.

Siano q e r stati del PDA e sia a in Σε e s sia in Γε. Vogliamo che il PDA vada da q a r e legge a ed estrae s. Inoltre vogliamo che allo stesso tempo si spinga la stringa intera u = u1...ul nello stack. Possiamo implementare questa azione introducendo dei nuovi stati q1, ..., ql-1 e impostando la funzione di transizione in questo modo:

δ(q, a, s) per contenere (q1, ul),
δ(q1, ε, ε) = {(q2, ul-1)},
δ(q2, ε, ε) = {(q3, ul-2)},
δ(q3, ε, ε) = {(q4, ul-2)},
...
δ(ql-1, ε, ε) = {(r, u1)}.

Usiamo la notazione (r, u) ∈ δ(q, a ,s) per dire che quando q è lo stato dell'automa, a è il prossimo simbolo di input, s è il simbolo in cima allo stack, il PDA può leggere la a e prelevare la s, allora inserisce la stringa u nello stack e va allo stato r. La figura seguente mostra questa implementazione:

Figura 2.23: implementazione dell'abbreviazione (r, xyz) ∈ δ(q, a, s)

Gli stati di P sono Q = {qstart, qloop, qaccept} ∪ E, dove E è l'insieme di stati di cui abbiamo bisogno per implementare la scorciatoria appena descritta. Lo stato di inizio è qstart. L'unico stato accettato è qaccept.

La funzione di transizione è definita di seguito. Cominciamo inizializzando lo stack perché contenga i simboli $ e S, implementando il passo 1 nella descrizione informale: δ(qstart, ε, ε) = {(qloop, S$)}. Poi facciamo le transizioni per il ciclo principale del passo 2.

Prima ci occupiamo del caso (a), se la cima dello stack contiene una variabile. Sia δ(qloop, ε, A) = {(qloop, w) | dove A → w è una regola in R}.

Secondo, gestiamo il caso (b), se la cima dello stack contiene un terminale. Si ha che δ(qloop, a, a) = {(qloop, ε)}.

Infine, gestiamo il caso (c), se il marcatore di stack vuoto $ è in cima allo stack. Si ha che δ(qloop, ε, $) = {(qaccept, ε)}.

Il diagramma di stato è mostrato nella figura 2.24.

Figura 2.24: il diagramma di stato di P

Questo completa la prova del lemma 2.21.

La seconda parte la farò se e quando lo riterrò utile

Suggerimenti per il pumping lemma

Ieri siamo andati a parlare con il prof Calzavara e abbiamo raccolto un'importante indicazione sull'applicazione del pumping lemma.

  1. Non fare l'errore di applicare il PL a un esempio, "dimenticandosi" di includere la lunghezza p nella dimostrazione.

Per esempio, la soluzione di 1.49/b non può essere di prendere una stringa 1111010101... perché presuppone che p≤4, ma non prova niente nel caso in cui p>4.

Un'idea di fondo più "solida" sarebbe dire che abbiamo una stringa 1p01p che sicuramente è più lunga di p, e la cui parte sinistra sia xy, mentre z è la parte a destra dello zero. Facendo il pumping up si arriva ad avere un numero di 1 a sinistra maggiore del numero di 1 a destra, che contraddice la definizione del linguaggio.

  1. Il costrutto contrario di "per tutti esiste uno" è "per qualcuno non esiste nessuno".

Non è sufficiente trovare un caso in cui qualcosa non funziona, ma bisogna trovare un caso in cui niente funziona.

  1. In ogni caso, quando si parla di pumping lemma più che dividere la stringa in tre parti è meglio valutare se dividerla in una "parte sinistra" pompabile e una parte destra.

Più che cercare di definire quale sia la suddivisione in xyz, è meglio definire le caratteristiche che dovrebbe avere questa suddivisione.

  1. Il pumping lemma nei linguaggi context-free è più complicato, perché non c'è più una "parte sinistra" pompabile, ma c'è una suddivisione in cinque della stringa per cui la zona pompabile diventa quella centrale.

exercise 1.29

Use the pumping lemma to show that the following languages are not regular.
a. A1 = {0n1n2n| n ≥ 0}
b. A2 = {www | w ∈ {a, b}}
c. A3 = {a2n| n ≥ 0} (Here, a2n means a string of 2n a’s.)

a. Theorem

A1 = {0n1n2n| n ≥ 0} is not regular.

a. Proof

Applying the pumping lemma to a string that matches with A1 we can have two scenarios:

  1. y is composed by only 0s or 1s or 2s. For example, 012 with y = 1. When we change the number of ys, we obtain a different number of 1s than others: 02, 0112, 01112, ..., and this doesn't match with A1 anymore
  2. y is composed by a pair or more of numbers. For example, 001122, with y = 0011. Changing the number of ys, the order of the numbers are not kept: 22, 0011001122, 00110011001122...

so A1 is not regular.

b. Theorem

A2 = {www | w ∈ {a, b}} is not regular.

b. Proof

We show that choosing a pattern w, composed by as and bs, the language A2, that recognizes the pattern w repeated three times, is not regular.

If the y part of the pumping lemma is composed by a substring of w, repeat the y more times means don't match A2 anymore, because the string can be composed by other than ws. I.E. w = aab, a valid string is aabaabaab, y = the central pair aa: repeating y, we obtain: aabbaab (0 times), aabaabaab (matches), aabaaaabaab (1 time). The first and the third examples are not composed by ws.

If the y part of the pumping lemma is exactly w, repeat the y a number of times different by 1 mod 3 (1, 4, 7, 10...) doesn't match the rule of three times w. I.E. w = aab, y = aab ⇒ aabaab (0 times, doesn't match); aabaabaab (1 time, matches), aabaabaabaab (2 times, doesn't match)...

So, A2 is not regular.

c. Theorem

A3 = {a2n| n ≥ 0} (Here, a2n means a string of 2n a’s.)

c. Proof

A3 recognizes a, aa, aaaa, aaaaaaaa, ...

Choose x = a, y = aa, z = a seems a good idea, because we're sure that every string recognized by A3 is writable with an xyiz, but there are two problems: a single "a" is not writable with this pattern, and there are strings that are writable but don't be recognized by A3, for example, i = 2 ⇒ a aa aa a (the spaces have been added to make the string more readable).

Choosing a simpler definition of x, y, z, as x = ε, y = a, z = ε, we are affected by the same problem.

exercise 1.49

a. Theorem

Let B = {1ky|y∈{0,1}* and y contains at least k 1s, for k ≥ 1}. Show that B is a regular language.

a. Proof

B equals to "Let B is a string that begins with 1 and, after, contains at least another 1".

This DFA doesn't "count" the number of 1s

b. Theorem

Let B = {1ky|y∈{0,1}* and y contains at most k 1s, for k ≥ 1}. Show that B is not a regular language.

b. Proof

As shown in 1.77 Sipser's example, we can apply an absurd proof of the pumping lemma to this language

Let we assume that B is regular. Let we choose one 1 as pumping's y and the second part (i.e. from the first 0 found) as pumping's z, and it seems to work:

1111y0100z

or better

1y11y21y31yn0100z

Pumping lemma doesn't allows a length of 0 for the y part, but allows that y is repeated zero times.

Using as example the previous string, with y repeated zero times, we obtain the string 0100, that doesn't match. Thus we obtain a contradiction.

Pumping lemma

Il pumping lemma è una cavolata, espressa in una maniera complicata perché formale.

Ogni stringa sposta l'automa da uno stato all'altro, ma se ci sono più lettere che stati è ovvio che perché la stringa sia riconosciuta bisogna che ripeta più volte il passaggio attraverso alcuni stati.

Gli angolofoni lo chiamano principio dei buchi della piccionaia, noi principio dei cassetti, ma il senso non cambia.

In pratica quindi:

  1. for each i ≥ 0, xyiz ∈ A significa che A contiene xz, xyz, xyyz, xyyyz, xyyyyz...
  2. |y| > 0 significa, banalmente, che non posso scegliere ε come y, cioè devo avere un y che contiene qualcosa. Al limite, lo ripeto zero volte...
  3. |xy| ≤ p significa che o p si ferma a x oppure è lungo almeno xy

Da questi tre punti si evince anche che:

  1. x e z possono essere ε
  2. se p > 0, allora o x o y devono essere diversi da ε

exercise 1.14

a. Theorem

Show that if M is a DFA that recognizes language B, swapping the accept and nonaccept states in M yields a new DFA recognizing the complement of B. Conclude that the class of regular languages is closed under complement.

a. Proof

This proof is based on induction.

Base case: a DFA with a single state, don't care what is the alphabet. Every letter brings to the only state. Obviously, if the state is an accepted state (F = {q0}), the DFA accepts every string; if the state is a non-accepted state (F = ∅), the DFA doesn't accept strings.

Inductive step: Let N a DFA that respect the theorem. If we add a new state Fnew, we must add an outgoing arrow for each letter and we can add some incoming arrow, moving them from other states.

Now, there are three cases: if the string ends before reaching Fnew, the theorem is proven, because the existence of Fnew isn't relevant. If the string ends at Fnew, the change of the acceptance of Fnew inverts the behavior of the DFA.

If the string ends at the state after Fnew, that we can name Fnew+1, the change of the acceptance of Fnew+1 inverts the behavior of the DFA.

After Fnew+1, we are again inside N, so the theorem is proved.

a. Proof (2)

For each string s, the membership of each state at F don't change the path that s follows. So, if s ends in an accepted state, the string is accepted, and changing this state the string isn't accepted anymore; and vice versa.

a. Conclusion

Let S a class of strings s composed by letters of Σ. Each string of s must belong to a regular language recognized by N or ¬N, since every state is accepted in N or ¬N.

Since "A language is called regular language if some finite automaton recognizes it" (Sipser, definition 1.16), all the languages that recognize every string s must be recognized by N or ¬N.

b. Theorem

Show by giving an example that if M is an NFA that recognizes language C, swapping the accept and nonaccept states in M doesn’t necessarily yield a new NFA that recognizes the complement of C. Is the class of languages recognized by NFAs closed under complement? Explain your answer.

b. Proof

A simple example is:

N = ({q0, q1}, {A, B}, δ, q0, {q0, q1})
δ = (q0, A) = q1

the string "B" is not recognized both by N and ¬N

Since every NFA have an equivalent DFA (Sipser, theorem 1.39), and the NFA is a special class of DFAs, the class of NFA is closed under complement.

Decidibilità

Il capitolo 4 è tutto basato sulla dimostrazione che quanto visto nei capitoli precedenti è decidibile (o no).

ADFA è decidibile: si crea una macchina di Turing che, dati (B, w) con B automa e w stringa, simuli il DFA. Se la simulazione finisce in uno stato accettato, accetta, altrimenti rifiuta.

ANFA è decidibile: si potrebbe procedere come per la DFA e creare una TM che simuli il NFA, ma c'è un'altra strada: convertire il NFA in un DFA, e poi procedere come per il punto precedente.

AREX è decidibile: si converte l'espressione regolare in una NFA e si procede con il punto precedente.

Alan Turing

Alan Turing è stato un underdog, per questo mi piace.

Quando Hilbert propose i suoi famosi problemi, i più grandi matematici del mondo si lanciarono nel tentativo di lasciare i loro nomi nei libri di matematica, ma poi fu uno sconosciuto "meccanico" a immaginare un sistema minimale per risolvere problemi.

Quando arruolarono a Bletchley Park schiere di esperti di crittografia, Turing sembrava un pesce a merenda. Tutti erano esperti di linguaggio, qualcuno di scacchi, qualcuno di cruciverba, nessun altro poteva immaginare di aggredire Enigma con la forza bruta. Il resto è storia nota: se non lo fosse, ci sono tanti libri e un bel riassunto cinematografico.

Purtroppo l'ultima sfida l'ha persa: Alan Turing contro l'ottusità, mista a ingratitudine, dei suoi stessi connazionali. Turing non era attrezzato per questo tipo di problema, non era decidibile nel senso in cui era abituato.

Nemmeno la sua testardaggine - la testardaggine di un maratoneta - gli hanno permesso di raggiungere una vecchiaia serena, in cui fare pacate raccomandazioni alle nuove generazioni. È morto così: costretto a scegliere tra la prigione e una cura di ormoni per un reato che non è una colpa; si è tolto la vita come Biancaneve, lasciando la sua nazione a fare da strega cattiva.

Strampalato, gay, atletico, strampalato, riservato, ingenuo e geniale, strampalato: questo è il padre dell'informatica, che ci tocca studiare a scuola e a cui siamo grati - tristemente - in definitivo ritardo.

https://www.turing.org.uk/turing/pi2/run.jpg