25 - Prolog - změna DB/programu za běhu (demonstrace na prohledávání stavového prostoru, práce se seznamy).
pondělí 21. května 2018
17:15
(demonstrace na prohledávání stavového prostoru, práce se seznamy)
Dynamická změna programu za běhu
Kromě faktů, které je definovány před spuštěním programu, je možné v Prologu používat i dynamické klauzule
Ty jsou definovány v průběhu běhu programu pomocí příslušných příkazů
Nejdříve je potřeba dát Prologu najevo, jaké dynamické klauzule plánujeme použít. To se dělá pomocí predikátu dynamic
Následují kód dává prologu najevo, že v programu budou dynamické klauzule pom a price s aritou 1
:- dynamic
pom/1,
price/1,
Přidání klauzule do databáze pomocí assert
assert(parent(jana, john)) - přidá fakt parent(jana, john) do DB
Ještě existují varianty asserta a assertz, které přidávají na začátek respektive konec databáze
Je samozřejmě možné přidávat nejen fakta, ale i pravidla: 
assert((naive(X) :- happy(X))). --- přidá pravidlo naive(X):-  happy(X))
Odstranění klauzulí se provádí pomocí retract
retract(parent(jana, john)) - odstraní se první klauzule, která odpovídá danému vzoru
Ještě existuje retractall(X), které smaže všechny dynamické klauzule odpovídající X
Příklad na dynamické prohledávání stavového prostoru
Zadání: Napsat predikát getPath, který má jako první parametr startovní bod a jako druhý cílový bod a do třetího unifikuje nejkratší (s nejmenší cenou) cestu mezi těmito body. Předpokládejte, že stavový prostor je konečný. Máte k dispozici predikát nextStep(X, XX, P), který jako první parametr dostane nějaký stav stavového prostoru a v druhém vrátí další stav a ve třetím bude výsledná cena tohoto kroku.
Řešení:
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
:- dynamic
pom/1,
price/1,
ppos/1.
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
getPath(S,E,Path) :-
retractall(pom(_)),
retractall(price(_)),
retractall(ppos(_)),
assert(price(none)),
mks(S,E,Path).
mks(S,E,_) :-
search(S,E,0,[]).
mks(S,_,[S|Path]) :-
ppos(Path).
checkP(NP,Path) :-
price(none),
retract(price(none)),
assert(price(NP)),
assert(ppos(Path)),
!,fail.
checkP(NP,Path) :-
price(P),
NP<P,
retract(price(P)),
retract(ppos(_)),
assert(price(NP)),
assert(ppos(Path)),
!,fail.
search(E,E,Price,Path) :- !, checkP(Price,Path).
search(S,E,P,TP) :-
assertz(pom(S)),
nextStep(S,Nxt,SP),
not(pom(Nxt)),
NP is P+SP,
append(TP,[Nxt],NTP),
search(Nxt,E,NP,NTP).
search(S,_,_,_) :-
pom(S),
retract(pom(S)),
!, fail.
nextStep(pos(X,Y),pos(XX,Y),P) :-
XX is X + 1, check(XX,Y), P is (XX-3)*(XX-3)+(Y-3)*(Y-3).
nextStep(pos(X,Y),pos(X,YY),P) :-
YY is Y + 1, check(X,YY), P is (X-3)*(X-3)+(YY-3)*(YY-3).
nextStep(pos(X,Y),pos(XX,Y),P) :-
XX is X - 1, check(XX,Y), P is (XX-3)*(XX-3)+(Y-3)*(Y-3).
nextStep(pos(X,Y),pos(X,YY),P) :-
YY is Y - 1, check(X,YY), P is (X-3)*(X-3)+(YY-3)*(YY-3).
check(X,Y) :-
X > 0, X =< 5, Y > 0, Y =< 5.
Popis: Nejdříve se deklarují potřebé dynamické klauzule (pom, price, ppos). V predikátu getPath nejdříve vyčistíme DB od případných klauzulí z minula. Potom se pustí predikát Search, který má za úkol vyzkoušet nějakou cestu k cíli. Aktuální cena cesty je 0. 
Použije se druhý predikát Search (první se použije jen když se dojde do cíle), kde se nejdříve přidá aktuální pozice so databáze ( assertz(pom(S)) ) - pom jsou navštívené pozice. Následně pomocí nextStep vygeneruje následující pozici, zkontroluje, zda tato pozice není v pom a pokud ne, tak vypočítá novou cenu cesty. Následně volá rekurzivně predikát search, nyní ale s novou aktuální pozicí, stejným cílovým bodem (ten se nemění), novou vypočítanou aktuální cenou cesty a aktualizovanou aktuální cestou (kde byla přidána pozice navštívená v minulém kroku). 
Pokud by program došel do cíle E, tak se místo druhého predikátu search použije ten první - search(E,E,Price,Path) :- !, checkP(Price,Path), který má za úkol zjistit, jestli aktuální cesta nepřekročila hodnotu price(X), kde je uložena délka nejkratší dosud nalezené cesty pomocí predikátu checkP. Pokud je aktuální cena nižší, tak se price přepíše novou hodnotu, jinak tam zůstane ta stará. Do ppos se dá aktuální nejkratší nalezená cesta jako seznam pozic. 
Práce se seznamy
Seznamy jsou definovány induktivně: Prázdný seznam je označen atomem [ ] , k reprezentaci neprázdného seznamu slouží binární funktor tečka '.' . Neprázdný seznam je tedy tzv. tečkový pár (terminologie pochází z jazyka LISP) .(Hlava,Tělo), kde Hlava je první prvek seznamu a Tělo je seznam tvořený zbývajícími prvky seznamu. Pro zjednodušení zápisu lze použít výčet prvků v hranatých závorkách (oba zápisy jsou ekvivalentní).
Příklad:
.(a,.(b,.(c,[ ]))) .
[a,b,c] .
Pro práci se seznamy se často využívá operátor '|', který umožňuje přístup k jednotlivým částem seznamu. Seznam lze pak zapsat jako [Začátek|Tělo], kde Začátek je výčet (nikoliv seznam) prvků tvořící začátek definovaného seznamu a Tělo je seznam (nikoliv výčet) tvořící zbytek definovaného seznamu (je-li prázdný, nemusí se uvádět).
Příklady:
[H|T] = [a, b, c, d], kde H = a,  T = [b, c, d]
[A|B|C|L] = [a, b, c], kde A = a, B = b, C = c a L = []
[H|T] = [[a,b,c],a,b,[a,b]], kde H = [a,b,c], T = [a,b,[a,b]]