From a2c5b54cce94c8d9bb5d6192956d25fcc55bcd48 Mon Sep 17 00:00:00 2001 From: Samuele Musiani Date: Tue, 2 Jul 2024 19:16:17 +0200 Subject: [PATCH] soluzione personale 2024-05-04 --- prove/totale/totale-2024-06-04-soluzione.typ | 234 +++++++++++++++++++ 1 file changed, 234 insertions(+) create mode 100644 prove/totale/totale-2024-06-04-soluzione.typ diff --git a/prove/totale/totale-2024-06-04-soluzione.typ b/prove/totale/totale-2024-06-04-soluzione.typ new file mode 100644 index 0000000..1ae688e --- /dev/null +++ b/prove/totale/totale-2024-06-04-soluzione.typ @@ -0,0 +1,234 @@ +#import "@preview/finite:0.3.0": automaton + +#set align(center) += Soluzione Linguaggi Totale +==== 2024-06-04 + +#set align(left) +#set par( + justify: true +) + +_NOTA: Questa è una soluzione proposta da me, non è detto che sia giusta. Se +trovate errori segnalateli o meglio correggeteli direttamente_ :) + ++ Per dimostrare che le definizioni di grammatiche libere *sono equivalenti*, + osserviamo che: + - Ogni grammatica di tipo (b) è pure una grammatica di tipo (a). + - Per ogni grammatica di tipo (a) posso trovare una equivalente grammatica + di tipo (b). Infatti utilizzando l'algoritmo per la rimozione delle + produzioni $epsilon$, data $G$ di tipo (a), genero una $G'$ tale che + $L(G') = L(G) without {epsilon}$ e le cui produzioni non sono epsilon. + Per ottenere una del tutto equivalente, nel caso in cui $epsilon in L(G)$, + basta modificare $G' = (\NT, T, S, R)$ con l'aggiunta di un nuovo simbolo + $S' -> epsilon | S$ e $G'' = (\NT union {S'}, T, S', R union + {S' -> epsilon, | S})$. Tale che $G''$ è di tipo (b). + ++ Costruisco l'NFA più semplice che riconosce il linguaggio: + #set align(center) + #automaton(( + q0: (q1:"a"), + q1: (q2:"a"), + q2: (q3:"a"), + q3: (q0:"a") + ), + initial: "q0", + final: "q2" + ) + + #set align(left) + Si può direi che è un DFA minimo e genera $L={a^2, a^6, a^10, dots}$ con regex + $"aa"("aaaa")^*$ ma non è richiesto. + ++ Non riescho a fare le $epsilon$ in questo pacchetto, assumete $"e" = epsilon$: + #set align(center) + #automaton(( + q0: (q1:"a, Z/Z"), + q1: (q1:"a, X/aX", q2:"c, X/X"), + q2: (q2:"b, a/e", q3:"e, X/e"), + q3: () + ), + initial: "q0", + final: "q2" + ) + ++ Per il linguaggio $L = {a^n b^n | n >= 1}$ una grammatica possibile è: + + $S -> "ab"$ + + $S ->"aSb"$ + Costruiamo l'automa $\LR(0)$. Non so come fare stati grandi con tutti gli + items in typst quindi scrivo l'automa e poi definisco gli stati a parte: + #set align(center) + #automaton(( + q0: (q1:"S", q2: "a"), + q1: (), + q2: (q2:"a", q3:"b", q4: "S"), + q3: (), + q4: (q5:"b"), + q5: (), + ), + initial: "", + final: "" + ) + #set align(left) + Items: + - q0: \ + $S' -> .S$ \ + $S-> ."ab"$ \ + $S-> ."aSb"$ + - q1: $S' -> S.$ \ + - q2: \ + $S-> "a.b"$ \ + $S-> "a.Sb"$ \ + $S-> ."ab"$ \ + $S-> ."aSb"$ + - q3: $S -> "ab."$ + - q4: $S -> "aS.b"$ + - q5: $S -> "aSb."$ + + Costruiamo la tabella $\LR(0)$: + #table( + columns: (1fr, 1fr, 1fr, 1fr), + align: center, + table.header( + [*a*], [*b*], [*\$*], [*S*] + ), + $0$, [S2], [], [G1], + $1$, [], [Acc], [], + $2$, [S2], [S3], [G4], + $3$, [R1], [R1], [R1], + $4$, [], [S5], [], + $5$, [R2], [R2], [R2], + ) + Non ci sono conflitti, quindi la grammatica è $\LR(0)$. Discutiamo il + comportamento del parser sugli input: \ + $(0, epsilon, "aabb$")$ \ + $(02, "a", "abb$")$ \ + $(022, "aa", "bb$")$ \ + $(0223, "aab", "b$")$ Facciamo la reduce: rimuoviamo "23" e [2, S] -> G4\ + $(024, "aS", "b$")$ \ + $(0245, "aSb", "$")$ \ + $(01, "S", "$")$ Accept! + \\ + $(0, epsilon, "$")$ Errore, cella bianca + ++ Direi che il testo è sbagliato e il passaggio dei paramentri è per nome e per + riferimento. Per nome basta ricopiare il codice al posto della chiamata (con + le opportune accortezze): + ```c + { + int x = 1; + int y = 5; + int z = 10; + void pippo ( name int y , reference int z ){ + z = x + y + z; + } + + { + int x = 20; + int y = 30; + int z = 50; + //pippo (x++ , x); + // x_ref sarebbe riferimento a x che vale 20. x_top è la x sopra la + // funzione pippo per scope statico. + // In C la segunte istruzione è undefined behavior. Assumiamo che si + // valuti strettamente da sinistra a destra. + x_ref = x_top + (x_ref++) + x_ref; // x_ref = 1 + 20 + 21 = 42 + //pippo (x++ , x); + x_ref = x_top + (x_ref++) + x_ref; // x_ref = 1 + 42 + 43 = 86 + write (x); // 86 + } + + write (x); // 1 + } + ``` + In conclusione il programma stampa *86* e *1* + ++ Assumiamo che la variabile letta all'esterno sia `n`: + ```c + int f(int x, function g()) { + if (x == 0) { + g(); + return 0; + } else { + return f(x - 1, g()) + 1; // Evito che f possa essere tail recursive + } + } + { + void h() { + // Codice generico + } + + g(n, h) + } + ``` + L'idea è di usare `f()` per creare `n` record di attivazione e poi far chiamare + `h()` a `f()`. + ++ È da pensare come javascript. Quindi `h({})` ritorna `{c: {e: 4}}`. Con questa + logica `x` risulta essere: `x = {a: 1, b: {c: {e: 4}, d: {a: 5, b: {c: {a: 6 , e: 7}, d: {e: 8}}}} }`. Quindi: + - *I1*: Errata in quanto `x` ha il campo `x.b.c` ma non contiene `b`. + - *I2*: Errata, `x` non ha il capo `c`. + - *I3*: Giusta + - *I4*: Errata + - *I5*: Giusta + ++ Dal seguente programma in C++ possiamo vedere che stampa *2 3 5 7 11*: + ```cpp +#include +#include +#include + +class Z : std::exception {}; + +void d(int i, int n) { if (i % n == 0) throw Z(); } + +void b(std::list &s, int n); + +class X : std::exception {}; +void c(std::list &s, int i, int n) { + try { b(s, n); } catch (X) { } + s.push_front(i); // Come add() del testo +} + +void b(std::list &s, int n) { + if (s.empty()) { throw X(); } + int i = s.front(); s.pop_front(); // Come remove() del testo + try { + d(i, n); + c(s, i, n); + } catch (Z) { + b(s, n); + } +} + +class Y : std::exception {}; +void a(std::list &s) { + if (s.empty()) { + throw Y(); + } + int n = s.front(); s.pop_front(); // Come remove() del testo + + try { b(s, n); } catch (X) { } + try { a(s); } catch (Y) { } + s.push_front(n); // Come add() del testo +} + +int main() { + std::list l = {2, 8, 6, 3, 5, 12, 7, 11, 14, 10}; + a(l); + for (auto &e : l) { + std::cout << e << " "; + } + std::cout << std::endl; +} + ``` + L'idea è la seguente: l'insieme delle classi partendo dal primo elemento della + lista rimuovono tutti i multipli di quell'elemento. Quando non ci sono più + multipli si passa all'elemento sucessivo. Durante il processo tutti gli + elementi vengono tolti dalla lista. Quando finalmente la lista si svuota, + vengono reinseriti tutti gli elementi in testa. + + La funziona `b()` è quella responsabile di rimuovere gli elementi. La funziona + `d()` controlla se `i` è multiplo di `d` e nel caso lancia `Z` che evita che + venga chiamata `c()` che è la funzione responsabile a "salvare" i numeri che + poi verranno reinseriti nella lista.