(********************************************) |
(** Execução de autómatos não deterministas com transições codificadas como tabelas de Hash para conjuntos de estados *) |
(********************************************) |
(** @author: Simão Melo de Sousa, RELEASE-DI-UBI-PT, desousa@di.ubi.pt *) |
(** compilação: ocamlopt fsa_set.ml -o ndfa versão >=4.07*) |
(** documentação: ocamldoc -hide-warnings -html -charset utf8 -short-functors -stars -keep-code -colorize-code -impl fsa_set.ml *) |
(** Estruturas de dados*) |
(** simbolo é o tipo para o alfabeto considerado, aumentado do símbolo "epsilon".
O símbolo "epsilon" tem por representação o valor None e
o caracter c tem por representação Some c *) |
type simbolo = char option
let (epsilon:simbolo) = None
(** a fita de entrada é representada por uma lista de símbolos, de tipo fita *) |
type fita = simbolo list
(** Estados : módulo para a representações dos estados de um autómato.
representamos estados como inteiros. estruturamos estes tipo com a função compare habitual *) |
module Estados =
struct
type t = int
let compare = compare
end
(** Sestado : módulo para conjuntos de estados *) |
module Sestado = Set.Make(Estados)
(** tipo dos estados, como abreviatura do tipo Estados.t*) |
type estado = Estados.t
(** tipo dos conjuntos de estados, como abreviatura do tipo Sestado.t*) |
type estados = Sestado.t
(** transicoes : tipo das transições. Codificado como uma hashtable de
(estado*simbolo) para conjuntos de estados (por causa do não-determinismo) *) |
type transicoes = ((estado*simbolo), estados) Hashtbl.t
(** maquina : tipo dos autómatos não deterministas. Um autómato é definido como
o tuplo (transições, estados iniciais, estados finais) *) |
type maquina = (transicoes * estados * estados)
(** configuracao : tipo das configurações. Uma configuração é um par *) |
type configuracao = (estados * fita)
(** Leitura dos dados*) |
(** to_fita é uma função de tradução de char para simbolo .
O caracter '_' é entendido como o epsilon *) |
let to_fita c = if c='_' then None else (Some c)
(** tradução para o formato transição*) |
let triplo a b c = ((a, to_fita b),c)
(** função utilitária simples*) |
let char_of_string s = s.[0]
(** read_to_set lê uma linha de inteiros e devolve na forma de uma lista de inteiros *) |
let read_to_list f = (List.map f (String.split_on_char ' ' (read_line ())))
(** read_to_set lê uma linha de inteiros e devolve na forma de um conjunto de inteiros *) |
let read_to_set f = (*Sestado.of_seq (List.to_seq (List.map f (String.split_on_char ' ' (read_line ()))))*)
(read_line ()) |> (String.split_on_char ' ') |> (List.map f) |> List.to_seq |> Sestado.of_seq
(** read_transition lê transições linha a linha até ao fim de ficheiro.
Formata as transições lidas para o tipo transicoes *) |
let read_transition () =
let (ht:transicoes) = Hashtbl.create 97 in
try
while true do
let (a,b) = (Scanf.sscanf (read_line ()) " %d %c %d" triplo) in
let res = try Hashtbl.find ht a with Not_found -> Sestado.empty
in Hashtbl.replace ht a (Sestado.add b res)
done; ht
with _ -> ht
(**
leitura lê no formato texto o autómato por executar, e a palavra por reconhecer.
A leitura devolve as estruturas correspondentes.
*) |
let leitura () =
let dados = read_to_list (fun x -> x |> char_of_string |> to_fita) in
let init_set = read_to_set int_of_string in
let final_set = read_to_set int_of_string in
let transitions = read_transition () in
(dados,(transitions,init_set,final_set))
(** Execução*) |
(** execução que permite assinalar o fim de uma execução, com o registo da respectiva configuração final *) |
exception FIM of configuracao
(** reach calcula que estados são alvo de transições directas
que partem do estado state com rótulo ch no autómato maquina *) |
let reach (transicoes,_,_:maquina) (state:estado) (ch:simbolo) =
try
Hashtbl.find transicoes (state,ch)
with Not_found -> Sestado.empty
(** reach_states calcula que estados são alvo de transições directas
que partem do conjunto de estados states com rótulo ch no autómato maquina *) |
let reach_states ((transicoes,_,_) as maq : maquina) (states:estados) (ch:simbolo) =
Sestado.fold (fun state acc-> Sestado.union acc (reach maq state ch)) states Sestado.empty
(** reach_star_aux é função (recursiva) auxiliar da função reach_star e
calcula que estados são alvo de transições directas e indirectas
que partem do conjunto de estados todo com rótulo ch no autómato maquina .
Os estados já considerados encontram-se em final , nunca é considerado
estados de final quando são processados os estados de todo -- os estados
de todo são realmente os estados que restam ainda processar. Após estarem
devidamente processados, passam para final
Um dos invariantes mantidos é que os estados de final são todos atingíveis
por ch a partir de um caminho que parte dos estados de todo da chamada inicial.
*) |
let rec reach_star_aux ((transicoes,_,_) as maq:maquina) (final:estados) (todo:estados) (ch:simbolo)=
if Sestado.is_empty todo then final
else
let elt = Sestado.choose todo in
let new_final = Sestado.add elt final in
let new_todo = (Sestado.union (Sestado.remove elt todo) (Sestado.diff (reach maq elt ch) new_final)) in
reach_star_aux maq new_final new_todo ch
(** reach_star calcula que estados são alvo de transições directas e indirectas (i.e. uma ou mais)
que partem do conjunto de estados states com rótulo ch no autómato maquina *) |
let reach_star (maq:maquina) (states:estados) (ch:simbolo) =
reach_star_aux maq Sestado.empty states ch
(** reach_epsilon devolve o conjunto de estados para os quais existe um caminho "epsilon"
que parte dos estados de states -- i.e. os estados atingíveis por epsilon
partindo de states *) |
let reach_epsilon maq states = reach_star maq states epsilon
(**
next calcula os estados atingíveis a partir dos estados em states
com o simbolo simb , combinado com os estados atingíveis por
transições epsilon a partir daí.
se não houver estados atingidos, damos a indicação de que se
terminou a execução
*) |
let next ((transicoes,b,c) as maquina) ((states, restante) as config) simb=
let res = reach_epsilon maquina (reach_states maquina states simb) in
if (Sestado.is_empty res) then raise (FIM config) else res
(**
step realiza um passo de execução do autómato maquina a partir da configuração config .
se não resta elementos na fita de entrada então damos a indicação de que terminou a execução,
senão devolvemos a configuração seguinte (considerando o autómato maquina e a
configuração config=(states, a.w):
(states, a.w) |-' (states', w)
em que states' = (next maquina config)
*) |
let step ((transicoes,b,c) as maquina) ((states, restante) as config)=
match restante with
[] -> raise (FIM config)
| a::w -> (next maquina config a,w)
(** step_star é a função de execução principal. executa repetidamente step a partir de uma dada configuração.
Espera-se que esta seja invocada com a configuração inicial (estendida com todos os estados atingíveis
com transições epsilon dos estados iniciais)
*) |
let rec step_star (maq:maquina) (config:configuracao) =
step_star maq (step maq config)
(** is_accepted é um predicado que detecta se uma configuração
da execução do autómato (ambos passados em parâmetro) prefigura a aceitação.
Ou seja o buffer de entrada encontra-se vazio e há pelo menos um estado final na
configuração
*) |
let is_accepted ((_,_,accept):maquina) (laqui, restante) =
restante=[] && not (Sestado.is_empty (Sestado.inter accept laqui))
(** a função print_output analisa a configuração final e imprime na
saída standard o veredicto. *) |
let print_output (maq:maquina) (config:configuracao)=
(if (is_accepted maq config) then "YES" else "NO") |> print_endline
(** main função principal *) |
let main () =
let dados,((a,b,c) as maquina) = leitura () in
try
step_star maquina ((reach_epsilon maquina b),dados)
with FIM x -> print_output maquina x
(* executa. *)
let () = main ()
(** epílogoExemplo de entrada:a a b a 0 1 2 0 a 0 0 b 1 0 a 3 1 a 2 2 a 3 3 a 1 3 a 2Resposta: YES*) |