Sisteme Expert

Laboratorul 5


(Deadline: 05.05.2019 23:59:59)

Daca nu sunteti logati exercitiile nu se mai afiseaza.

Predicatul repeat

Predicatul repeat repeta interogarea expresiei (formata din predicate) care urmeaza dupa el. Repetarea se intampla atata timp cat valoarea expresiei de dupa repeat este falsa (rezultatul este no). Acest lucru e datorat faptului ca expresia de dupa repeat care a rezultat in esec trimite backtrackingul inapoi la predicatul anterior, adica repeat, care intotdeauna se termina cu succes, si are aceasta proprietate de a forta interogarea expresiei ce urmeaza dupa el. Daca rezultatul expresiei este yes, repetarea se opreste deoarece nu se mai intoarce din backtracking la repeat care sa forteze reinterogarea. Atentie, reinteorgarea e de fapt aceeasi interogare facuta din nou si nu o reinterogare ceruta de backtracking prin intoarcerea la expresia respectiva (asa cum veti observa in exemplele ce urmeaza).

Sa observam drept exemplu interogarea urmatoare prin care citim numere de la tastatura atata timp cat sunt nenule: | ?- repeat,read(X),X==0,write(X).
|: 7.
|: 8.
|: 4.
|: 0.
0
X = 0 ? yes
| ?-
Dupa ce a fost citit 7, testul X==0 care da no impinge backtrackingul inapoi la read, dar read prin felul in care e definit nu se poate reinteroga la revenirea din backtracking (este evaluat o singura data si revenirea din backtracking la el va rezulta cu no), asa cum se vede in exemplu: | ?- read(X),X==0,write(X).
|: 7.
no
| ?-
Dar, in exemplul pentru repeat, dupa ce si read(X) a esuat cand a revenit backtrackingul la el, se trece cu inca un pas inapoi, deci ajunge la repeat care forteaza o noua interogare a lui read si ceea ce urmeaza dupa el. Procedeul se repeta pentru citirealui 8 si 4. Cand insa citeste 0, trece de conditia X==0, afiseaza numarul si apoi iese in sfarsit din regula.

Cel mai adesea repeat e insotit de un cut (atunci cand nu vrem sa repetam expresia pana la finalul regulii). Expresia de repetat se va gasi intre repeat si cut. De ce functioneaza asta?

p(...):- ...,repeat, expresie_de_repetat(...), !,... .
A nu se intelege din formatul de mai sus ca expresia de repetat e doar un predicat, ea poate reprezenta un set de predicate legate prin operatori logici. Daca expresia de repetat are per total ca rezultat no si deci nici nu mai ajunge la cut, ea se intoarce din backtracking la repeat, care, dupa cum spuneam mai devreme impinge procesul de interogare inapoi catre expresia de dupa el. Daca expresia iar rezulta in esec, iar se intoarce la repeat, si iar se repeta procedeul. Daca la un moment dat expresia ajunge sa se termine cu succes, atunci ajunge si la cut, si trece la ce urmeazadupa el. Daca ce urmeaza dupa cut da fail, cand porneste sa se intoarca backtrackingul catre repeat da mai intai peste cut, care prin definitie opreste backtrackingul deci nu mai permite intoarcerea la expresia dintre repeat si cut si cu atat mai putin la repeat. Daca, insa, ceea ce urmeaza dupa cut rezulta in succes (intoarce yes) atunci ajunge la sfarsitul regulii.

De exemplu, extragem elemente din baza de cunostinte pana inalnim un p(0): | ?- listing(p).
p(1).
p(2).
p(0).
p(3).
p(4).

yes
| ?- repeat,retract(p(X)),write(X),nl,X==0,write(gata).
1
2
0
gata
X = 0 ? ;
3
4
Prolog interruption (h for help)? a
% Execution aborted
| ?-

Pentru prima solutie programul se comporta conform asteptarilor, sterge p(1), apoi repeta procedeul si sterge p(2), pentru 0 face la fel, dar fiind indeplinita si conditia X==0, ajunge la sfarsitul regulii si se termina cu succes. La cererea urmatoarei solutii, insa, (adica gasirea urmatorului 0), a fost nevoie de intreruperea executiei, deoarece, dupa ce a sters din baza de cunostinte si p(3) si p(4), nu a mai avut ce sa stearga, retract-ul dand mereu no si intorcandu-se la repeat care iar a fortat evaluarea lui. Observati ca dupa ce am cerut o noua solutie pentru X si deci i-am cerut sa continue in backtracking interogarea, s-a reintors in bucla creata de repeat. Practic repeatul folosit fara cut extinde bucla pana la sfarsitul regulii si in cazul in care se cer noi solutii, in multe din situatii ultima astfel de reinterogare va rezulta de fapt in bucla infinita.

Cu ajutorul unui cut ne putem delimita bucla sa nu mai fie pana la sfarsitul regulii si sa se efectueze intr-adevar doar pana la realizarea conditiei cerute:


| ?- listing(p).
p(1).
p(2).
p(0).
p(3).
p(4).

yes
| ?- repeat,retract(p(X)),write(X),nl,X==0,!,write(gata).
1
2
0
gata
X = 0 ? ;
no
| ?-

Atentie, deoarece reinterogarea expresiei de dupa repeat e identica fata de cea anterioara (e efectiv aceeasi interogare care se repeta), folosirea lui repeat are sens numai pentru predicate care schimba starea sistemului (care adauga ori sterg clauze din baza de cunostinte, care citesc de la tastura ori din fisier etc.), altfel daca se foloseste pentru predicate fara efecte asupra starii sistemului se va repeta acea interogare la nesfarsit cu rezultat identic.

Deci pana acum am vazut 3 moduri in care se poate realiza o actiune similara in mod repetat:

Totusi desi uneori pot fi folosite pentru a rezolva aceeasi problema, fiecare metoda are tipul sau specific de probleme in care trebuie intrebuintata, si folosirea unei alte metode complica rezolvarea:

Fisiere

Prologul ofera cateva predicate built-in care permit lucrul cu fisiere.

Citirea din fisier cu redirectare

Redirectarea inputului catre un fisier are drept efect citirea din acel fisier atunci cand se apeleaza predicate care in mod obisnuit citesc din consola.

Pasii pentru realizarea citirii cu redirectare sunt urmatorii:

La citirea pe termeni (cu ajutorul predicatului read, si cu elementele de citit scrise cu punct la sfarsit in fisier), cand se ajunge la sfarsitul fisierului read citeste termenul end_of_file. Deci citirea pe termeni se va opri cand termenul citit devine aceasta valoare. Atentie, in acest caz sfarsitul fisierului se poate "falsifica" adaugand in fisierul de intrare un termen de foma end_of_file., deoarece read va pune in parametrul sau aceasta valoare la fel ca si in cazul ajungerii la sfarsit de fisier. Acelasi lucru se intampla si cand cititi fisierul caracter de caracter cu get_char(X); cand ajunge la caracterul de sfarsit de fisier in X va pune end_of_file.

Exemplu de citire pe termeni (cu read): %citeste_fis_r(+Fisin)
citeste_fis_r(Fisin):- seeing(Input_curent),
                       see(Fisin),
                       repeat,
                         read(X),
                           (X==end_of_file
                           ;
                           write(X),nl,fail),
                       !,
                       seen,
                       see(Input_curent).
Avand fisierul de input:

1. abc. miau.
Iportant! Observati ca dupa fiecare element de citit din fisier s-a pus cate un punct. Aceasta e necesar deoarece, dupa cum stiti, read citeste pana la punct (asa cum citea si din consola). Deci daca nu puneti punct el va incerca sa citeasca pana unde gaseste un punct si in final veti primi o eroare. De asemenea daca vreti sa cititi un termen ce contine un spatiu nu-l veti scrie ab cd. deoarece la procesarea termenului va da de acel spatiu care va genera o eroare. Corect termenul trebuie scris intre apostroafe:'ab cd'. | ?- citeste_fis_r('fisier_input.txt').
1
abc
miau
yes
| ?-

Exemplu de citire caracter cu caracter: %citeste_fis_chr_redir(+Fisin)
citeste_fis_chr_redir(Fisin):-seeing(Input_curent),
                       see(Fisin),
                       repeat,
                         get_char(X),
                           (X==end_of_file
                           ;
                           write(X),fail),
                       !,
                       seen,
                       see(Input_curent).
Avand fisierul de input:

1.abc
cip
Practic va citi si afisa continutul fisieului caracter de caracter. Observati ca nu sunt necesare punctele dupa fiecare caracter in parte (cum ar fi fost necesar la read daca am fi vrut sa tratam fiecare caracter drept entitate separata). Punctul din fisier e doar pentru a demonstra faptul ca e tratat ca un caracter obisnuit. | ?- citeste_fis_chr_redir('fisier_input.txt').
1.abc
cip
yes
| ?-

Testarea faptului ca s-a ajuns la sfarsit de fisier se poate face si cu preficatul at_end_of_stream(+Stream), dar acesta nu poate fi folosit in mod direct la citirea cu redirectare, deoarece nu se cunoaste streamul corespunzator fisierului redirectat, insa acesta se poate afla astfel: | ?- see('chestie.txt').
yes
| ?- current_stream(A,B,S).
A = 'e:/schimbate_pe site/inteligenta artificiala 2013/chestie.txt',
B = read,
S = '$stream'(14413488) ? ;
no
| ?-
Predicatul current_stream considera doar calea absoluta in primul paramteru. Deci desi working directory-ul e setat corespunzator: | ?- current_stream('chestie.txt',_,S).
no
Pentru ca nu gaseste un stream deschis cu calea respectiva; trebuia data calea absoluta.

Atentie current_stream, in caz ca sunt mai multe fisiere deschise le va returna pe fiecare pe rand. Aici in exemplu era doar un fisier deschis, deci ca sa va asigurati ca va da streamul fisierului pe care il doriti, ii puneti si calea pe primul parametru. Daca stiti ca predicatul de citire a fisierelor definit in programul vostru va primi mereu la interogare calea relativa, si deci nu aveti acces direct la calea absoluta, va trebui prin program sa va construiti calea absoluta pornind de la cea relativa. Practic trebuie sa concatenati valoarea working_directory-ului cu calea relativa primita. Pentru a afla working directory-ul trebuie incarcata biblioteca file_systems, si folosit predicatul curent_directory (despre biblioteci veti invata in curand in laboratorul 5). | ?- use_module(library(file_systems)).
% module file_systems imported into user
yes
| ?- current_directory(D).
D = 'e:/schimbate_pe site/inteligenta artificiala 2013/' ?
yes
Dupa asta nu mai urmeaza decat sa concatenati valoarea directorului curent cu valoarea fisierului. Indicatie pentru concatenarea a 2 termeni: se transforma in liste de caractere(cu atom_chars) se aplica concatenarea a 2 liste, se transforma lista rezultata in atom (tot cu atom_chars).

Scrierea in fisier cu redirectare

Redirectarea outputului este similara drept concept cu redirectarea inputului. Pur si simplu dupa redirectare orice predicat de afisare in loc sa scrie in consola (in standard output) va scrie in fisier.

Pasii pentru realizarea scrierii cu redirectare sunt la fel ca si in cazul citirii:




Citirea si scrierea in fisiere fara redirectare

Desi folosirea redirectarii uneori ne usureaza munca (de exemplu cand scriem un predicat de afisare in consola pe care astfel il putem folosi si pentru a scrie intr-un fisier) uneori avem nevoie sa deschidem fisierele in modul clasic (exemplu: daca trebuie sa scriem si in consola si intr-un fisier in mod alternat in cadrul unui program).

In acest caz deschiderea fisierelor se face cu acelasi predicat open/3 dar bineinteles cu parametri diferiti. Primul parametru al lui open reprezinta calea catre fisier, al doilea reprezinta modul de deschidere al fisierului(read - citire, write - suprascriere, append - adaugare la sfarsitul fisierului), al treilea e un parametru calculat de catre open si reprezinta stream-ul prin care citim/scriem in fisier. Toate predicatele de citire si scriere in consola prezentate la laborator pana acum au si forma cu un parametru in plus (dat ca prim argument), si anume streamul, in rest ceilalti parametri ramanand la fel (de exemplu pentru read(X) exista si varianta read(Stream,X), pentru write(X) si nl, avem write(Stream,X) si nl(Stream) etc.). Streamurile se inchid cu predicatul close/1 care primeste ca argument chiar stream-ul.

Citirea din fisier

Sa luam drept exemplu codul: %citeste_fis(+Fisin)
citeste_fis(Fisin):- open(Fisin,read,Stream),
                     repeat,
                       read(Stream,X),
                         (X==end_of_file
                         ;
                         write(X),nl,fail),
                       !,
                       close(Stream).
pentru fisierul de intrare:

1. abc. miau.
are rezultatul: | ?- citeste_fis('fisier_input.txt').
1
abc
miau
yes
| ?-

Atentie daca in urma unei interogari, un fisier ramane deschis (fie pentru citire fie pentru scriere), nu-l veti mai putea deschide inca o data si nici nu va va mai lasa sa il modificati in editor si sa il salvati pana nu inchideti fisierul sau chiar consola (care odata cu inchiderea sa elibereaza toate resursele). | ?- open('fisier_output.txt',write,S).
S = '$stream'(14444752) ?
yes
| ?- open('fisier_output.txt',write,S).
! System error
! 'SPIO_E_FILE_ACCESS'
| ?-
Daca stiti valoarea streamului, il puteti inchide usor cu close, daca insa ati pierdut valoarea (de exemplu in urma unui bug in program, prin care ati deschis fisierul dar nu l-ati mai inchis, si nu ati returnat/afisat valoarea streamului) ori inchideti consola, ori definiti si interogati predicatul de mai jos, care inchide toate streamurile deschise(inclusiv cele care au fost deschise cu see si tell, daca nu e vorba de unul din streamurile standard(de input, output si eroare)):

close_all:-current_stream(_,_,S),close(S),fail;true.
Acest predicat face de fapt acelasi lucru ca si predicatul close_all_streams din biblioteca library(file_systems) despre care veti invata in laboratorul 5. | ?- use_module(library(file_systems)).
% module file_systems imported into user
yes
| ?- close_all_streams.
yes
| ?

Sa mai luam acum un exemplu. Vrem sa citim elementele dintr-un fisier, de exemplu:

a.
b.
c.
d.

Si vrem sa punem aceste elemente intr-o lista. Ne vom folosi de un predicat dinamic in care ne vom cosntrui lista:

:-use_module(library(lists)).%incarcarea bibliotecii lists pentru a folosi predicatul reverse

:-dynamic lista/1. %predicat dinamic in care salvez lista de elemente


%citeste_fis_l(+Fisin,-L)
citeste_fis_l(Fisin, L):- retractall(lista(_)),%sterg faptele vechi
                     assert(lista([])),%valoarea initiala a listei
                     open(Fisin,read,Stream),
                     repeat,
                       read(Stream,X),
                         (X==end_of_file
                         ;
                         retract(lista(Lv)),assert(lista([X|Lv])) %actualizez lista din baza de cunostinte, stergand lista veche si punand lista cu X-ul adaugat
                            ,fail),
                       !,
                       close(Stream),
                       retract(lista(Li)),%obtin forma finala a listei
                       reverse(Li,L).%deoarece am adaugat la inceput lista e inversata

Atunci vom avea interogarea:

| ?- citeste_fis_l('fis_in.txt',L).
L = [a,b,c,d] ?
yes
| ?-

Sa mai luam un exemplu, pentru a vedea si cum citim un fisier caracter de caracter. Predicatul urmator citeste continutul unui intreg fisier si il transforma in atom,

:-use_module(library(lists)).%incarcarea bibliotecii lists pentru a folosi predicatul reverse

:-dynamic lista/1. %predicat dinamic in care salvez lista de elemente

%citeste_fis_chr(+Fisin, - Atom)
citeste_fis_chr(Fisin, Atom):- retractall(lista(_)),%sterg faptele vechi
                     assert(lista([])),%valoarea initiala a listei
                     open(Fisin,read,Stream),
                     repeat,
                       get_char(Stream,X),%citesc doar cate un caracter
                         (X==end_of_file
                         ;
                         retract(lista(Lv)),assert(lista([X|Lv])) %actualizez lista din baza de cunostinte, stergand lista veche si punand lista cu X-ul adaugat
                            ,fail),
                       !,
                       close(Stream),
                       retract(lista(Li)),%obtin forma finala a listei
                       reverse(Li,L),%deoarece am adaugat la inceput lista e inversata
                       %daca doream doar lista cu caracterele ma opream la calculul lui L
                       %dar noi vrem atomul cu continutul fisierului
                       atom_chars(Atom, L).%am transformat lista de caractere in Atom
Consideram fisierul de intrare:
Un soarece visa ca e dulau.
Si ca pisicile se tem de el.
| ?- citeste_fis_chr('input_chr.txt',A).
A = 'Un soarece visa ca e dulau.\nSi ca pisicile se tem de el.' ?
yes

Scrierea in fisier

Sa luam ca exemplu:

%scrie_fis(+Fisout)
scrie_fis(Fisout):- open(Fisout,write,Stream),
                    write(Stream,' o chestie'),
                    nl(Stream),
                    close(Stream).
%scrie_fis_ad(+Fisout)
scrie_fis_ad(Fisout):- open(Fisout,append,Stream),
                       write(Stream,' inca o chestie'),
                       close(Stream).
Predicatul scrie_fis foloseste open cu al doilea parametru setat pe write, deci daca fisierul nu exista il va crea si va scrie in el, iar daca acesta exista il va suprascrie. Predicatul scrie_fis_ad foloseste open cu al doilea parametru setat pe append, deci in caz ca nu exista fisierul, il va crea, iar daca exista, va adauga la sfarsitul lui ceea ce are de scris. | ?- scrie_fis('chestie.txt').
yes
| ?- scrie_fis_ad('chestie.txt').
yes
| ?-
si fisierul rezultat: o chestie
inca o chestie




Teme