Scriem întâi o funcție care colectează literalele pure dintr-o singură clauză (listă de literale). O variantă de abordare ar fi colectarea literalelor pozitive și negative în două mulțimi, și apoi eliminarea elementelor comune. Alegem o altă soluție: acumulăm mulțimea de propoziții care au apărut cu ambele polarități, și deasemenea mulțimea literalelor pure (care au apărut până în prezent doar cu o polaritate).
Avem nevoie de un tip literal:
type lit = P of string N of stringDeasemenea, pentru a crea mulțimi de literale scriem un modul care conține tipul literal și funcția standard de comparare:
module Lit = struct type t = lit let compare = compare endPentru literalele care apar cu ambele polarități e suficient să ținem minte mulțimea numelor lor. Instanțiem deci module pentru mulțimi de nume (șiruri) și mulțimi de literale:
module SS = Set.Make(String) module LS = Set.Make(Lit)Avem nevoie deasemenea de o funcție pentru negația unui literal:
let neg = function P s -> N s N s -> P sPutem acum defini funcția care prelucrează o clauză. Ea va acumula o pereche de mulțimi: cea a numelor de propoziții care au fost văzute cu ambele polarități, și cea a literalelor care au fosît văzute doar cu o polaritate. Parcurgem lista de literale (clauza) cu List.fold_left. Funcția de prelucrare va avea ca prim parametru (și rezultat) o pereche de mulțimi (bad, good) descrisă ca mai sus, iar ca al doilea parametru, un literal (elementul curent al listei). Distingem trei cazuri:
let purelit1 = List.fold_left (fun (bad, pure) -> function P s N s as lit -> if SS.mem s bad then (bad, pure) else let nl = neg lit in if LS.mem nl pure then (SS.add s bad, LS.remove nl pure) else (bad, LS.add lit pure) )Interpretorul inferează pentru funcția purelit1 următorul tip:
val purelit1 : SS.t * LS.t -> lit list -> SS.t * LS.t = <fun>Din cei trei parametri pentru List.fold_left, am specificat doar funcția de prelucrare. Deci, funcția purelit1 va lua ceilalți doi parametri rămași (o valoare inițială pentru perechea de mulțimi, și lista de literale), și va returna o nouă valoare pentru perechea de mulțimi. Astfel, purelit1 poate fi aplicată repetat pentru o listă de clauze, actualizând perechea de mulțimi după prelucrarea fiecărei liste. Pentru a obține funcția finală dorită, iterăm funcția purelit1 cu List.fold_left pe lista de clauze, pornind de la perechea de mulțimi vide. Din rezultat ne interesează doar mulțimea literalelor pure, adică al doilea element al perechii, care se obține cu funcția standard snd (second):
let purelit clauses = snd (List.fold_left purelit1 (SS.empty, LS.empty) clauses)Am avut de prelucrat o listă de liste, pentru care sunt necesare două iterări (cicluri) încuibate: o parcurgere a listei de liste, și în interior câte o parcurgere pentru fiecare listă-element. Ambele parcurgeri se fac cu List.fold_left, iar valoarea acumulată după parcurgerea unei liste-element (aici, o clauză) e punctul de plecare pentru parcurgerea următoarei liste-element. E util într-un astfel de caz să definim o funcție pentru parcurgerea unei singure liste (cu o valoare inițială dată deasemenea ca parametru), și aceasta poate fi folosită direct ca funcție pentru ciclul exterior (parcurgerea listei de liste).
Exercițiul 2. Considerăm formule propoziționale reprezentate prin tipul
type bexp = V of string Neg of bexp And of bexp * bexp Or of bexp * bexpcare sunt în formulă normală conjunctivă, adică nu există subexpresii And într-o expresie Or, și negația se aplică doar literalelor.
Definim întâi tipurile pentru formula booleană, și pentru literale. O variabilă propozițională V "a" va fi convertită în literalul pozitiv P "a", iar negația ei Neg (V "a") în literalul negativ N "a".
type bexp = V of string Neg of bexp And of bexp * bexp Or of bexp * bexp type lit = P of string N of stringMai departe, scriem o funcție care transformă în clauză (listă de literale) o formulă care conține disjuncții. Fiind deja în formă normală conjunctivă, înseamnă că sub o disjuncție nu putem avea conjuncții, iar negația se aplică doar direct propozițiilor.
let rec oneclause lst = function V s -> P s :: lst Neg (V s) -> N s :: lst Or (e1, e2) -> oneclause (oneclause lst e1) e2 _ -> failwith "formula not in CNF"Pentru propoziții pozitive sau negate, literaul e adăugat în capul listei. Pentru disjuncție, sunt adăugate întâi literalele din primul operand, apoi din al doilea. Pentru orice alt caz (conjuncție, sau negație aplicată unei formule mai complexe) generăm o excepție -- aceste cazuri nu ar trebui să apară dacă formula e deja în CNF.
Mai departe, construim pe aceeași structură o funcție care ia o listă de clauze și adaugă clauzele pentru o formulă. Pentru o conjuncție, fiecare din operanzi va genera o clauză (sau mai multe, dacă conține la râdul lui conjuncții). Pentru orice altă formulă, folosim funcția anterioară, pornind de la lista vidă de literale:
let mkclauses = let rec mkcls2 clist = function And (e1, e2) -> mkcls2 (mkcls2 clist e1) e2 f -> oneclause [] f :: clist in mkcls2 []
Marius Minea Last modified: Sun Nov 10 22:00:00 EET 2013