TP 2 : Automates
Ce TP est à effectuer en OCaml, sous Visual Code. Vous pouvez utiliser le Codespace GitHub ou votre ordinateur personnel.
Si vous avez une boucle infinie (le terminal qui ne répond pas) : Ctrl + C
On pourra créer un fichier tp2.ml. Pour exécuter : sélectionner les lignes OCaml et appuyer sur Shift + Entrée. Ceci envoie le code sélectionné dans le terminal (utop). Vous pouvez aussi utiliser utop en mode interactif.
On utilisera ;; à la fin de chaque fonction pour envoyer correctement le code sur utop.
Solution
(* type automate = {
initiaux : int list;
finaux : int list;
transitions : (int * char * int) list
}
(* 2 *)
let a1 = {
initiaux = [0];
finaux = [1];
transitions = [(0, 'b', 0); (0, 'a', 1); (1, 'a', 0); (1, 'b', 1)]
}
let a2 = {
initiaux = [0];
finaux = [2];
transitions = [(0, 'a', 0); (0, 'b', 1); (1, 'a', 0); (1, 'a', 2); (2, 'b', 2)]
}
(* 3 *)
let miroir a =
let rec aux = function
| [] -> []
| (e, l, e2)::q -> (e2, l, e)::aux q
in {initiaux = a.finaux; finaux = a.initiaux; transitions = aux a.transitions};;
miroir a1
(* 4 *)
let est_deterministe a =
let rec aux = function
| [] -> true
| (q1, a, q2)::t ->
let rec aux2 = function
| [] -> aux q
| (q1', a', q2')::t' -> if q1 = q1' && a = a' then false else aux2 t'
in aux2 q
in List.length a.initiaux = 1 && aux a.transitions;;
est_deterministe a1;;
est_deterministe a2;; *)
(* 1 *)
type afdc = {
initial : int;
finaux : int list;
delta : int array array
}
let a1 = {
initial = 0;
finaux = [1];
delta = [|[|1; 0|]; [|0; 1|]|]
}
let a2 = {
initial = 0;
finaux = [2];
delta = [|[|0; 1|]; [|1; 2|]; [|2; 0|]|]
}
(* 2 *)
let rec delta_etoile a q u = match u with
| [] -> q
| t::q2 -> delta_etoile a (a.delta.(q).(t)) q2;;
delta_etoile a1 a1.initial [0; 1];;
(* 3 *)
let accepte a u =
List.mem (delta_etoile a a.initial u) a.finaux;;
accepte a1 [0; 1];;
accepte a1 [1; 0; 0];;
(* 4 *)
let complementaire a =
let rec aux n =
if n = -1 then []
else if List.mem n a.finaux then aux (n - 1)
else n::aux (n - 1) in
{initial = a.initial; finaux = aux (Array.length a.delta - 1); delta = a.delta};;
complementaire a1;;
(* 5 *)
let accessibles a =
let vus = Array.make (Array.length a.delta) false in
let rec aux q =
vus.(q) <- true;
for i = 0 to Array.length a.delta.(q) - 1 do
if not vus.(a.delta.(q).(i)) then aux a.delta.(q).(i)
done in
aux a.initial;
let rec aux2 n =
if n = -1 then []
else if vus.(n) then n::aux2 (n - 1)
else aux2 (n - 1) in
aux2 (Array.length a.delta - 1);;
accessibles a1;;
let a3 = {
initial = 0;
finaux = [2];
delta = [|[|1; 0; 0|]; [|0; 1; 0|]; [|1; 0; 2|]|]
};;
accessibles a3;; (* 3 n'est pas accessible *)
(* 6 *)
let vide a =
List.exists (fun q -> List.mem q a.finaux) (accessibles a);;
(* 7 *)
let inter a b =
let n = Array.length a.delta in
let p = Array.length b.delta in
let s = Array.length a.delta.(0) in
let d = Array.make_matrix (n*p) s (-1) in
for i = 0 to n - 1 do
for j = 0 to p - 1 do
for k = 0 to s - 1 do
d.(i*p + j).(k) <- a.delta.(i).(k)*p + b.delta.(j).(k)
done
done
done;
let rec finaux l1 l2 = match l1, l2 with
| [], _ -> []
| _, [] -> []
| i::q, j::q' -> (i*p + j)::(finaux q l2)@(finaux [i] q') in
{initial = a.initial*p + b.initial; finaux = finaux a.finaux b.finaux; delta = d};;
let a3 = inter a1 a2;;
accepte a3 [0; 1; 0; 0; 1];;
accepte a3 [0; 1; 1; 0; 0; 1];;
accepte a3 [1; 0; 0; 1];;
(* 8 *)
(* A est inclus dans B ssi A inter (complémentaire de B) est vide *)
let inclus a b =
vide (inter a (complementaire b));;
(* 9 *)
let equivalent a b =
inclus a b && inclus b a;;
Automate déterministe
On utilisera le type suivant d'automate déterministe complet (AFDC), où les états sont entre et et les lettres sont des entiers ( est représenté par , par ...) :
type afdc = {
initial : int;
finaux : int list;
delta : int array array
}
Si a est de type afdc, a.delta.(i).(j) est l'état atteint en lisant la lettre à partir de l'état (c'est-à-dire ). Le nombre d'états de a est donc Array.length a.delta.
- Définir en OCaml les automates
a1eta2suivants :

-
Définir une fonction
delta_etoile : afdc -> int -> int list -> inttelle quedelta_etoile a q urenvoie l'état atteint en lisant le mot à partir de l'état (c'est-à-dire ). Vérifier aveca1. -
Définir une fonction
accepte : afdc -> int list -> booltelle queaccepte a udétermine si est reconnu para. Vérifier aveca1. Quelle est la complexité deaccepte? -
Définir une fonction
complementaire : afdc -> afdctelle quecomplementaire arenvoie un automate reconnaissant le complémentaire du langage reconnu para. Vérifier aveca1.
Rappel : on a besoin que l'automate soit déterministe complet, ce qui est supposé dans ce TP. -
Définir une fonction
accessibles : afdc -> int listtelle queaccessibles arenvoie la liste des états accessibles depuis l'état initial dea. Pour cela, on pourra utiliser un parcours en profondeur. Vérifier sur des exemples.
Indice
let accessibles a =
let vus = ... in (* vus.(i) = true si l'état i a été visité *)
let rec aux q = (* parcours en profondeur depuis l'état q *)
...
aux a.initial; (* on commence le parcours en profondeur depuis l'état initial *)
...
- En déduire une fonction
vide : afdc -> booldéterminant si un automate reconnaît le langage vide.
Automate produit
-
Écrire une fonction
inter : afdc -> afdc -> afdctelle queinter a brenvoie un automate reconnaissant l'intersection des langages reconnus paraetb. On suppose queaetbont le même alphabet.
Pour cela, on construira l'automate produit deaetb, qui possède états (où est le nombre d'états deaet le nombre d'états deb), et dont l'état sera numéroté .
Vérifier aveca1eta2. -
En déduire une fonction
inclus a bdéterminant si le langage reconnu par l'automateaest inclus dans celui reconnu parb. On pourra utiliser les fonctions précédentes. -
En déduire une fonction
equivalent a bdéterminant si les langages reconnus par les automatesaetbsont égaux.