Iterácia je ľudská,
rekurzia božská.
Kde začína rekurzia,
tam končí zdravý rozum.
O rekurzívnej definícii hovoríme, keď na definovanie nejakého objektu použijeme iný (spravidla jednoduchší) variant toho istého objektu. Reťazené použitie korektnej rekurzívnej definície musí zabezpečiť, že celý postup konverguje k nejakému hraničnému prípadu, ktorý musí byť samostatne definovaný. Zamedzí sa tak vzniku nekonečnej cyklickej definície.
Známym príkladom je rekurzívna definícia faktoriálu:
(n+1)! = (n+1)*n! n=1,2,...(rekurzívna definícia)
1! = 1 (hraničný prípad)
V porovnaní s procedurálnymi jazykmi (z ktorých niektoré rekurzie ani
nepripúšťajú), je v Prolog-u rekurzia používaná oveľa častejšie. Nie
je snáď prehnané tvrdenie, že každý netriviálny prologovský program
používa rekurziu. V podstate možno rozlíšiť dva spôsoby použitia
rekurzie:
- rekurzívna definícia predikátov - rekurzívne pravidlá - rekurzívna definícia údajových štruktúr - zoznamov.
potomok(Dieta,Rodic) :- rodic(Rodic, Dieta).
potomok(Vnuca,Staryrodic) :- rodic(Staryrodic, Rodic),
rodic(Rodic, Vnuca).
potomok(Pravnuca,Prarodic):- rodic(Prarodic, Staryrodic),
rodic(Staryrodic, Rodic),
rodic(Rodic, Pravnuca).
. . . /* ďalšie generácie */
ale oveľa elegantnejšia je rekurzívna definícia:
potomok(Dieta, Rodic) :- rodic(Rodic, Dieta).
potomok(Potomok,Prapredok) :- rodic(Prapredok,Predok),
potomok(Potomok,Predok).
Prvá klauzula ošetruje hraničnú situáciu - medzi prvým a druhým
argumentom je rozdiel jednej generácie. Druhá klauzula predpokladá
väčší generačný rozdiel - jej použitím sa rekurzívne "uberá" vždy
jednu generáciu tak dlho, kým nenastane spomínaná hraničná situácia.
Schematicky je tento postup znázornený na
obrázku
potomok(Potomok,Prapredok) :-
|<---------------------------------------------------------|
| rodic(Prapredok,Predok) , potomok(Potomok,Predok). |
|<-----------------------------|<--------------------------|
| | |
Prapredok ---------> Predok ----> . . . . ----> Potomok
Použitie predikátu je znovu mnohostranné:
?- potomok(zuza, jana). 'Je Zuza Janin potomok ? no ?- potomok(Kto,karol). 'Kto sú potomkovia Karola ?' Kto = jana -> ; Kto = dana -> ; Kto = milan -> ; Kto = peter -> ; Kto = anna -> ; Kto = viera -> ; no ?- potomok(milan,Kto). 'Kto sú predkovia Milana ?' Kto = karol -> ; Kto = zuza -> ; no ?- potomok(Kto,Komu). 'Kto je komu potomkom ?' Kto = jana , Komu = karol -> ; Kto = dana , Komu = karol -> 'stačia dve riešenia' yesPoužitie rekurzívnych pravidiel je v Prolog-u bežné a prirodzené, ale mnohým začiatočníkom spôsobujú rekurzie značné ťažkosti. Najčastejšími chybami sú:
otec(O,S) :- syn(S,O).
syn(S,O) :- otec(O,S).
lubi(peter,mara,pivo). lubi(jana,pivo). lubi(dana,vino). lubi(mara). /* mara nelubi nikoho/nic */nakoľko kvôli rozdielnemu počtu argumentov sa jedná vlastne o tri naprosto rozdielne predikáty. Je ale možný zápis pomocou zoznamov:
ma_rad(peter,[mara,pivo]). ma_rad(jana,[pivo]). ma_rad(mara,[]).pričom posledný fakt reprezentuje výrok Mara nemá rada nič/nikoho. Použili sme úmyselne iné meno predikátu, aby bolo možné zavedením nového pravidla
lubi(Kto,Koho):- ma_rad(Kto, ZoznamLasok),
prvok(Koho,ZoznamLasok).
dosiahnuť podobné správanie systému ako v
príklade na generujúcu schopnosť Prolog-u
(zmení sa poradie odpovedí systému na niektoré otázky). Predikát
prvok odpovedá výroku "prvý argument je prvkom zoznamu
v druhom argumente", a nakoľko pracuje s rekurzívnou údajovou
štruktúrou, jeho definícia je tiež rekurzívna (pozri
príklad na definíciu predikátu prvok).
rodina( zuza, karol, [jana,dana,milan] ). rodina( jana, jano, [peter,anna] ). rodina( anna, jozo, [viera] ). rodina( eva, milan, [] ). rodina( viera, slob, [] ). rodina( slob, peter, [] ). rodina( dana, slob, [] ).Aj pre takto zakódovaný rodinný strom je možné použiť väčšinu pôvodných predikátov z kapitoly Návrh jednoduchej databázy a z príkladu na vyhľadanie potomkov z databázy , keď predefinujeme predikát rodicia zo skupiny faktov na pravidlo
rodicia(Matka,Otec,Dieta) :- rodina(Matka,Otec,ZoznamDeti),
prvok(Dieta,ZoznamDeti).
Niekoľko málo ďalších zmien vyplýva zo skutočnosti, že bezdetné
rodiny sú charakterizované prázdnym zoznamom v treťom argumente (atóm
bezdetni je už zbytočný). Predikát prvok je
automaticky nesplnený pre prázdny zoznam, vďaka čomu možno vypustiť
pôvodné explicitné testy na bezdetnosť:
otec(Otec, Dieta) :- rodicia( _ , Otec, Dieta). matka(Matka, Dieta) :- rodicia(Matka, _ , Dieta).Naviac je vhodné v definícii predikátu muz nahradiť podcieľ rodicia podcieľom rodina. Odstránia sa tým "násobné" odpovede (pozri poznámku na konci kapitoly Návrh jednoduchej databázy ).
Aj v predošlom príklade bol potrebný predikát prvok. Skôr než sa ho pokúsime definovať ( príklad na definíciu predikátu prvok), je potrebné trochu bližšie pojednať o rôznych formách zápisu zoznamov.
Doteraz sme používali tzv. zoznamovú notáciu, v ktorej sa prvky zoznamu, oddelené čiarkami, uvedú v danom poradí medzi hranatými zátvorkami. V skutočnosti predstavuje neprázdny zoznam binárnu štruktúru, ktorej funktorom je bodka (je definovaný ako vpravo asociatívny infixný operátor). Prvým operandom je hlava zoznamu a druhým jeho telo. Hlava je prvý prvok zoznamu a telo je obvykle zoznam (z pôvodného zoznamu vznikne oddelením hlavy). Uvedená bodková notácia je v porovnaní so zoznamovou neprehľadnejšia. Príklady zoznamov (zápisy v jednom riadku sú rovnocenné):
[a] .(a,[]) a.[] a.[] [a,b,c] .(a,.(b,.(c,[]))) a.(b.(c.[])) a.b.c.[] -?- .(a,.(b,.(c,d ))) a.(b.(c.d )) a.b.c.dPosledný zoznam nekončí prázdnym zoznamom a zatiaľ ho nevieme zapísať v zoznamovej notácii. Aj zoznamová notácia však umožňuje oddeliť Hlavu zoznamu od Tela pomocou zápisu [Hlava|Telo]. V LPA Prolog-u je symbol | deklarovaný ako infixný operátor, v Arity Prolog-u ho možno použiť iba v spojení s hranatými zátvorkami.
ZOZNAM HLAVA TELO
[a] a []
[a,b,c,d] a [b,c,d]
[[a,b],c,d] [a,b] [c,d]
[a,[b,c]] a [[b,c]]
[a|b] a b
[a,b,[c|d]] a [b,[c|d]]
[] nedá sa rozložiť (neúspech)
Zoznam v predposlednom riadku odpovedá bodkovej notácii
a.b.c.d. (Posledná bodka nie je súčasť zápisu, označuje
koniec vety). Zložitejšie zoznamy je účelné názorne zobraziť binárnym
stromom.
. . .
/ \ / \ / \
a . / \ a .
/ \ / \ / \
b . . . b .
/ \ / \ / \ / \
c [] a . c . c d
/ \ / \
b [] d []
[a,b,c] [[a,b],c,d] [a,b,[c|d]]
Prvá klauzula reprezentuje jednu hraničnú situáciu. Druhou hraničnou situáciou je prípad, keď zoznam v druhom argumente je prázdny a nedá sa rozložiť na hlavu a na telo (neúspech). Druhá klauzula je vlastné rekurzívne pravidlo, konvergujúce pri postupnom skracovaní testovaného zoznamu k jednej z uvedených hraničných situácií. Pre označenie údajov, na ktorých "nezáleží", boli použité anonymné premenné (podčiarniky). Predikát prvok možno použiť rôznymi spôsobmi:
?- prvok(c,[a,b,c,d]). 'je c prvkom zoznamu [a,b,c,d] ?' yes ?- prvok(q,[a,b,c]). 'je q prvkom zoznamu [a,b,c] ?' no ?- prvok(X,[a,b,c]). 'ktoré sú prvky zoznamu [a,b,c] ?' X = a -> ; X = b -> ; no 'ďalšie prvky neexistujú' ?- prvok(a,X). 'aké zoznamy obsahujú prvok a ?' X = [a|Y] -> ; 'zoznam s a ako prvým prvkom' X = [H,a|Z] -> ; 'zoznam s a ako druhým prvkom' X = [H,J,a|W] -> 'zoznam s a ako tretím prvkom' yesPremenné H, J predstavujú bližšie nedefinované prvky zoznamu a Y, Z a W reprezentujú bližšie neurčené zoznamy. V skutočnom výpise sú tieto premenné v tvare čísiel s podčiarnikom (vnútorné mená premenných). Odpovede na posledné dve otázky ilustrujú generujúcu schopnosť Prolog-u.
prvok_t(Prvok,[Hlava| _ ]) :- Prvok == Hlava. prvok_t(Prvok,[ _ |Telo]) :- prvok_t(Prvok,Telo).Kladnú odpoveď dostaneme iba na otázky typu:
?- prvok_t(d,[a,s,d,e]). ?- prvok_t(X,[a,s,X,e]). ale na otázky typu: ?- prvok_t(X,[a,s,d,e]). ?- prvok_t(p,[a,X,d,e]). ?- prvok_t(X,[a,Y,d,e]).je odpoveď záporná.
Celý proces možno schematicky znázorniť, pozri obrázku
H L1 L2 L1 L2 L2
|-|<-----|<-----| <----|<-----| |<-----|
. . .
|-|<------------| <-----------| |<-----|
H L L L
spoj([H|L1],L2,[H|L]) :- spoj(L1, L2, L).
Ošetrenie hraničnej situácie realizuje ďalšie pravidlo:
spoj([], L2, L ) :- L2 = L.Posledné pravidlo môže byť nahradené faktom, ktorému odpovedá tvrdenie: keď spojíme prázdny zoznam s akýmkoľvek zoznamom L, výsledkom je zoznam L. Klauzula pre hraničnú situáciu musí byť umiestnená pred rekurzívnym pravidlom (v opačnom prípade napríklad otázka spoj(X,[a,s],Y) spôsobí zacyklenie). Výsledné riešenie má tvar:
spoj([], L, L). spoj([H|L1], L2, [H|L]) :- spoj(L1, L2, L).Nie je účelné snažiť sa pochopiť túto definíciu "procedurálne", nakoľko každá z ôsmych možných možností použitia (toľko je kombinácií viazaných/voľných argumentov) má inú procedurálnu interpretáciu. V ďalšom uvedieme niektoré z možných spôsobov použitia predikátu spoj:
?- spoj([a,b,c], [d,e], [f,g]). no ?- spoj([a,b,c],[d,e],X). X = [a,b,c,d,e] -> ; no ?- spoj(X,Y,[a,b,c]). X = [] Y = [a,b,c] -> ; X = [a], Y = [b,c] -> ; X = [a,b] Y = [c] -> ; X = [a,b,c], Y = [] -> ; no
surodenci(ZM,ZS,D) :- rodina(_,_,ZozDeti),
spoj(ZM,[D|ZS],ZozDeti).
najstarsi(NajSt,R) :- (rodina(R,_,ZD) ; rodina(_,R,ZD)),
R \= slob, spoj(_,[NajSt],ZD).
po_sebe(Mladsi,Starsi) :- rodina(_,_,ZozDeti),
spoj(_,[Mladsi,Starsi|_],ZozDeti).
L H T H
<---------| |-|<-------| |-|
|--------- |-||------- . . . |-|
R H X H
|---------- |-
R R
Zoznamy T a X sa postupne skracujú až na
prázdne zoznamy, rekurzia teda smeruje k hraničnému stavu,
reprezentovanému faktom, že obrátením prázdneho zoznamu sa získa
prázdny zoznam. Tento fakt spolu s rekurzívnym pravidlom pre vyššie
uvedený postup dáva riešenie úlohy :
obrat([],[]). obrat([H|T],R) :- obrat(T,X), spoj(X,[H],R).Dialóg so systémom pre rôzne spôsoby použitia navrhnutého predikátu je nasledovný :
?- obrat([a,b,c],[c,b]). no ?- obrat([a,b,c],X). X = [c,b,a] -> ; noJe však možné definovať aj efektívnejší predikát pre obrátenie poradia prvkov v zozname:
obrat(L,R) :- obrat0(L,[],R). obrat0([],J,J). obrat0([H|A],B,R) :- obrat0(A,[H|B],R).Predikát obrat0 v každom rekurzívnom kroku odoberá hlavu zoznamu z prvého argumentu a pripája ho na začiatok budovaného zoznamu v druhom argumente. Proces začína prázdnym zoznamom v druhom argumente a končí, keď sa stáva prázdnym zoznam v prvom argumente. Vtedy sa výsledok prenáša do tretieho argumentu a jeho prostredníctvom aj do nadradeného predikátu obrat:
1.arg 2.arg 3.arg
[a,b,c, ... ,x,y,z] [] R
[b,c, ... ,x,y,z] [a] R
[c, ... ,x,y,z] [b,a] R
.....
[y,z] [x, ... ,c,b,a] R
[z] [y,x, ... ,c,b,a] R
[] [z,y,x, ... ,c,b,a] [z,y,x, ... ,c,b,a]
Často je potrebné zistiť džžku zoznamu (počet prvkov). Štandardný
Prolog ponúka zabudovaný predikát length(Zoznam,Dlzka).
Pri niektorých symbolických manipuláciách je potrebné pracovať s
jednotlivými znakmi, tvoriacimi atóm. To umožňuje zabudovaný predikát
name(Atom,ZozASCII), kde ZozASCII je zoznam
ASCII kódov znakov, ktoré tvoria atóm Atom.
pocet_deti(Rodic,Pocet) :- rodic_deti(Rodic,ZoznamDeti),
length(ZoznamDeti,Pocet).
rodic_deti(Rodic,ZoznamDeti) :-
(rodina(Rodic,Partner,ZoznamDeti);
rodina(Partner,Rodic,ZoznamDeti)),
Partner \= slob, Rodic \= slob.
Pomocou navrhnutého predikátu je možné zistiť, koľko detí má zadaný
rodič. Je ale možné zistiť aj to, ktorý rodič má zadaný počet detí.
Túto druhú možnosť zabezpečí generujúci predikát
rodic_deti. Samotný predikát length nie je
možné volať s voľným prvým argumentom.
dlzka( [], 0). dlzka([H|T],D) :- dlzka(T,D0), D is D0 + 1.
zenske_meno(Meno) :- name(Meno,ASCII), spoj(_,[97],ASCII).Použili sme predikát spoj na otestovanie, či posledný prvok zoznamu ASCII je ASCII kód písmena a, teda 97 (pozri príklad použitia predikátu prvok pre vyhľadávanie). Arity/Prolog umožňuje zadať ASCII kód znaku tak, že tento znak zapíšeme za opačným apostrofom: `a.
generacia(Potomok,Predok, 1 ) :- rodic(Predok,Potomok).
generacia(Potomok,Predok,Gen) :- rodic(Predok, X ),
generacia(Potomok,X,G1), Gen is G1+1.
Dialóg so systémom vyzerá nasledovne:
?- generacia(peter,karol,G). G = 2 -> yes ?- generacia(Potomok,jano,G). Potomok = peter, G = 1 -> ; Potomok = anna, G = 1 -> ; Potomok = viera, G = 2 -> ; no ?- generacia(peter,Predok,G). Predok = jano, G = 1 -> ; Predok = jana, G = 1 -> ; Predok = karol, G = 2 -> ; Predok = zuza, G = 2 -> ; no ?- generacia(Potomok,Predok,3). Potomok = viera, Predok = karol -> ; Potomok = viera, Predok = zuza -> ; noDôležitým príkladom akumulačných cyklov je budovanie zoznamov, alebo iných štruktúr. V zásade možno postupovať dvojakým spôsobom: priamo (pri vnáraní sa do rekurzie) alebo obrátene (pri vynáraní sa z rekurzie). V prvom prípade je potrebné použiť dva argumenty: v jednom sa buduje štruktúra (od nejakej počiatočnej - tú zadáme ako vstup pri volaní predikátu) a do druhého sa prenesie výsledok vo chvíli, keď rekurzia dosiahla hraničný prípad.
Pri obrátenom spôsobe vystačíme s jediným argumentom. Ten získa svoju počiatočnú "hodnotu" keď rekurzia pri vnáraní dosiahne hraničný prípad a štruktúra sa buduje "obrátene" pri vynáraní sa z rekurzie.
p(a). p(b).Úlohou je navrhnúť predikát, ktorý vytvorí zo znam, tvorený argumentami jednotlivých faktov. Predpokladáme, že príslušné fakty môžme v priebehu budovania zoznamu odstrániť z databázy. Obrátené budovanie zoznamu realizuje predikát:
buduj([Hlava|Telo]) :- retract(p(Hlava)), buduj(Telo), !. buduj([]).Priamy spôsob budovania vyžaduje pomocný argument:
buduj(Telo,Vysledok):- retract(p(Hlava)), buduj([Hlava|Telo],Vysledok), !. buduj(Vysledok,Vysledok).Ak by sme neboli v uvedených definíciách použili symboly rezu, prípadné návrat by generovalo ako ďalšie riešenia čiastkové zoznamy. Rozdiel v činnosti oboch predikátov ilustruje dialóg:
?- buduj(Zoznam). Zoznam = [a,b] -> ; no ........'predpokladáme obnovenie databázy'
?- buduj([],Zoznam). Zoznam = [b,a] -> ; noDruhý z uvedených spôsobov je vhodné použiť nielen vtedy, keď sa z nejakého dôvodu požaduje opačné poradie prvkov, ale najmä v prípade, keď je potrebné pripájať prvky pred nejaký iný zoznam:
?- buduj([f,g],Zoznam). Zoznam = [b,a,f,g] -> ; noV blokových modeloch možno veľmi názorne sledovať vplyv symbolu rezu na činnosť systému Prolog. Pri návrate cez blok rezu sa pokračuje z jeho brány Fail priamo na bránu Fail nadradeného cieľa.
#=================================================================#
|(0)buduj(X) #========================================# |
| | (2)buduj(Y) #================# | |
| | | (4)buduj(W) | | |
|#=============# |#=============# | #=============#| | |
||(1) | ||(3) | | |(5) || | |
?-||retract(p(A))|A=a||retract(p(B))|B=b| |retract(p(C))|| | |
>-++----->-------+->-++------>------+->-+>+---->----+ || | |
|| | || | |++----<----+ || | |
|| | || | ||#=============#|W=[]|Y=[b]|X = [a,b]
|| | || | || |#=# | #=# |
|| | || | |+----->---------++-+-+>+-+-+->-+
|| | || | | ||!| | |!| | |
|| | |#=============# #================##=# | | | | |;
|#=============# #========================================# #=# | |
no#=================================================================# |
<-----------------------<---------------------------------------------<-+
obrázok 1
#===================================================================#
| (0)buduj(X) #=========================================# |
| | (2)buduj(Y) #================# | |
| | | (4)buduj(W) | | |
| #=============# | #=============# | #=============#| | |
| |(1) | | |(3) | | |(5) || | |
?- | |retract(p(A))|A=a| |retract(p(B))|B=b| |retract(p(C))|| | |
->-+>+------>------+->-+-+------>------+->-+>+----->----+ || | |
| | | | | | |++-----<----+ || | |
| | | | | | ||#=============#| | |
| | | | | | || |W=[]|Y=[b]| X=[a,b]
| | | | | | |+------>--------+-->-+-->--+->-+
| | | | #=============# #================# | | |;
| | | | +------<-----------------<---------<--+--<--+-<-+
| | | | | |Y=[] | X=[a]
| | | | +------>----------------->------------+-->--+---->-
| #=============# #=========================================# | yes
#===================================================================#
obrázok 2
#================================================================# |(0)buduj([],X) #=========================================# | | | (2)buduj([a],X) #==================# | | | | | (4)buduj([b,a],X)| | | |#=============# |#=============# | #=============# | | | ||(1) | ||(3) | | |(5) | | | | ?-||retract(p(A))|A=a||retract(p(B))|B=b| |retract(p(C))| | | | >-++------>------+->-++------>------+->-+>+---->----+ | | | | || | || | |++----<----+ | | | | || | || | ||#=============# | | | || | || | || |#=#|#=#|X=[b,a] || | || | |+----->-----------++-+++-++->-+ || | || | | ||!|||!|| | || | |#=============# #==================##=#|| || |; |#=============# #=========================================##=#| | no#================================================================# | <-----------------------<--------------------------------------------<-
utvor([Hlava|Telo]) :- retract(p(Hlava)),utvor(Telo), asserta(p(Hlava)),!. utvor([]).Priamy spôsob budovania vyžaduje pomocný argument:
utvor(Telo,Vysledok) :- retract(p(Hlava)),
utvor([Hlava|Telo],Vysledok),asserta(p(Hlava)),!.
utvor(Vysledok,Vysledok).
V úlohách, ktoré používajú databázu Prolog-u na ukladanie
medzivýsledkov, potrebujeme mať k dispozícii predikát na čistenie
predikát. V Arity/Prolog-u aj v LPA Prolog-u je to
abolish.
odstran_vsetko(Meno/Arita) :-functor(Struktura,Meno,Arita), o_vsetko(Struktura). o_vsetko(S) :- retract( S ), fail. % fakty o_vsetko(S) :- retract((S:-_)), fail. % pravidla o_vsetko(_). % splnenie ciela
riesenia(Term,Otazka,Zoznam) :- vsetky(Otazka,Term),
vytvor(Zoznam), !.
Predikát vsetky ukladá do databázy fakty o vyhovujúcich
termoch a predikát vytvor (pozri buduj v príklade na tvorbu zoznamov) z nich na konci vytvorí zoznam:
vsetky(Otazka,Term) :- Otazka, assertz(term(Term)), fail. vsetky( _ , _ ). vytvor([Term|Zoznam]) :- retract(term(Term)), vytvor(Zoznam). vytvor( [] ).Keď sú v databáze napríklad fakty:
pivo(saris,10). pivo(bazant,12). pivo(kozel,12). pivo(staropramen,10). pivo(prazdroj,12).potom dialóg so systémom môže byť nasledovný:
?- riesenia(Znacka,pivo(Znacka,_),L).
Znacka = _25, L = [saris,bazant,kozel,staropramen,
prazdroj]-> ;
no
Arity/Prolog poskytuje niekoľko zabudovaných predikátov na riešenie
podobných úloh. Predikát bagof(T,C,ZR) vyhľadá (v
procese návratu, ktorý sám vyvoláva) všetky termy T, pre
ktoré je zadaný cieľ C splnený a vytvorí z nich
neusporiadaný zoznam ZR v treťom argumente (opakovaný
výskyt prvkov zoznamu je možný). Argument T musí byť
voľnou premennou, alebo takúto premennú musí obsahovať a táto
premenná musí byť obsiahnutá v cieli C. Ostatné premenné
v cieli C môžu byť voľné (potom systém zostavuje pre ich
rôzne viazania rôzne zoznamy ZR v procese návratu,
vyvolaného zvonku - tým sa líši od predikátu riesenia):
?- bagof(Znacka,pivo(Znacka,_),L). Znacka = _25, L = [saris,staropramen] -> ; Znacka = _25, L = [bazant,kozel,prazdroj] -> ; noJe možné tieto premenné existenčne kvantifikovať zápisom P^C (v prípade jednej premennej P ) alebo (P1,P2, ... PN)^C ( N premenných Pi ). Zápis P^C reprezentuje výrok "existuje také viazanie premennej P, pre ktoré je cieľ C splnený".
Predikát setof pracuje podobne ako bagof, len s tým rozdielom, že výsledný zoznam riešení je zotriedený a sú z neho vylúčené opakované výskyty "úspešných" termov. Predikát findall pracuje podobne ako bagof, ale všetky premenné v cieli chápe ako existenčne kvantifikované. Pre ilustráciu poslúži databáza:
lubi(jano,pivo). lubi(jano,rum). lubi(jano,vino). lubi(fero,pivo). lubi(fero,vino). lubi(jozo,pivo).a nasledujúci dialóg:
?- bagof(Kto,lubi(Kto,Co),L). Co = pivo, L = [jano,fero,jozo] -> ; Co = rum, L = [jano] -> ; Co = vino, L = [jano,fero] -> ; no ?- setof(Kto,lubi(Kto,Co),L). Co = pivo, L = [fero,jano,jozo] -> ; Co = rum, L = [jano] -> ; Co = vino, L = [fero,jano] -> ; no ?- findall(Kto,lubi(Kto,Co),L). L = [jano,jano,jano,fero,fero,jozo] -> ; no ?- bagof(Kto,Co^lubi(Kto,Co),L). L = [jano,jano,jano,fero,fero,jozo] -> ; no ?- setof(Kto,Co^lubi(Kto,Co),L). L = [fero,jano,jozo] -> ; no ?- setof(Co-Pijani,setof(Kto,lubi(Kto,Co),Pijani),L). L=[pivo-[fero,jano,jozo],rum-[jano],vino-[fero,jano]] - >; noOkrem vyššie uvedených viazaní premenných sa v skutočnej odpovedi systému objavia ešte priradenia vnútorných mien premenných (podčiarnik a číslo) ostatným premenným otázky (napr. Kto v prvých dvoch, Kto, Co v ďalších troch, ...). Pre pozmenenú databázu dostávame ďalšie typy dialógov:
lubi(jano,pivo,10). lubi(jano,pivo,12). lubi(jano,vino,cervene). lubi(fero,vino,biele). lubi(fero,vino,cervene). lubi(duro,pivo,12). ?- setof(Kto,lubi(Kto,Co,Druh),L). Co = pivo, Druh = 10, L = [jano] -> ; Co = pivo, Druh = 12, L = [duro,jano] -> ; Co = vino, Druh = biele, L = [fero] -> ; Co = vino, Druh = cervene, L = [fero,jano] -> ; no ?- setof(Kto,Druh^lubi(Kto,Co,Druh),L). Co = pivo, L = [duro,jano] -> ; Co = vino, L = [fero,jano] -> ; no ?- bagof(Kto,Druh^lubi(Kto,Co,Druh),L). Co = pivo, L = [jano,jano,duro] -> ; Co = vino, L = [jano,fero,fero] -> ; no ?- setof(Kto,(Co,Druh)^lubi(Kto,Co,Druh),L). L = [duro,fero,jano] -> ; no
rodina( zuza, karol, deti(jana,deti(dana,deti(milan,bezdetni)))). rodina( jana, jano, deti(peter,deti(anna,bezdetni))). rodina( anna, jozo, deti(viera,bezdetni)). rodina( eva, milan, bezdetni ). rodina( viera, slob, bezdetni ). rodina( slob, peter, bezdetni ). rodina( dana, slob, bezdetni ). otec(O,D) :- rodina(_,O,SD), clen(D,SD). clen(X, deti(X,_) ). clen(X, deti(_,S) ) :- clen(X,S).Predikát clen je analógiou predikátu prvok (pozri príklad - definícia predikátu prvok).
V prípade rodiny sme použili reprezentáciu hierarchickej štruktúry pomocou čiastkových faktov. Niekedy je výhodné reprezentovať celú štruktúru kompaktnejšie ako jediný zložený term. Umožní to jednotnú manipuláciu so všetkými prvkami tejto štruktúry.
veltok(labe(vltava)).
veltok(dunaj(vah(turiec,kysuca,bela(rackova,bystra,
brezinka),orava,revuca),ipel,hron(slatina, bystrica))).
rieka(R,M) :- veltok(S), functor(S,M,_),
povodie(R,S).
povodie(M,S) :- S=..[M|_].
povodie(X,S) :- S=.. [_|Pritoky],
vtekaju(X,Pritoky).
vtekaju(X,[H|_]) :- povodie(X,H).
vtekaju(X,[_|T]) :- vtekaju(X,T).
Pomocou zabudovaného operátora =.. získame zo štruktúry
S meno funktora M a zoznam argumentov
Pritoky. Keď potrebujeme iba meno funktora, môžme použiť
efektívnejší zabudovaný predikát functor. Predikát
rieka je možné použiť viacerými spôsobmi:
?- rieka(rackova,dunaj). 'patrí rackova do povodia rieky dunaj ?' ?- rieka(rackova, P ). 'do akého povodia patrí rackova ?' ?- rieka( R, dunaj). 'ktoré rieky patria do povodia dunaja ?' ?- rieka( R, P ). 'ktoré rieky patria do ktorého povodia?'
mapa(R) :- veltok(V), functor(V,R,_), nl, mapa(V ,0), fail. mapa(_). mapa(V,Odsad) :- V=..[X|Pritoky], tab(Odsad), write(X), nl, Odsad1 is Odsad+5, kresli(Pritoky,Odsad1),!. kresli([H|T],O) :- mapa(H,O), kresli(T,O), !. kresli( [], _).Zabudovaný predikát tab(N) vypíše N medzier. Nasledujúci dialóg ilustruje použitie navrhnutého predikátu:
?- mapa(dunaj).
dunaj
vah
turiec
kysuca
bela
rackova
bystra
brezinka
orava
revuca
ipel
hron
slatina
bystrica
yes
pritok(labe, [vltava]). pritok(vah, [turiec,kysuca,bela,orava,revuca]). pritok(dunaj, [vah,ipel,hron]). pritok(bela, [rackova,bystra,brezinka]). pritok(hron, [slatina,bystrica]).Úlohou je vytvoriť z týchto faktov štruktúry, uvedené v príklade - povodia riek :
gen :- abolish(veltok/1), koren(X), pritok(X,Z), gen(Z,Z1), S =.. [X|Z1], assertz(veltok(S)), fail. gen :- listing(veltok). /* výpis vygenerovaných štruktúr */ koren(X) :- pritok(X,_), not(( pritok(_,Z), prvok(X,Z) )). gen([H|T],[H1|T1]) :- pritok(H,P), !, gen(P,P1), gen(T,T1), H1=.. [H|P1]. gen([H|T],[ H|T1]) :- gen(T,T1). gen( [], [] ).Predikát abolish je zabudovaným predikátom Arity/Prolog- u. Analogický predikát je naprogramovaný štandardnými prostriedkami jazyka Prolog v príklade na odstránenie klauzuly z databázy .
nahrad(TermNovyTerm, NovyTerm) :- !. nahrad( _ , Term, _ , Term ) :- atomic(Term), !. nahrad(Term, Term, NovyTerm, NovyTerm) :- !. nahrad( _ , Term, _ , Term ) :- atomic(Term), !. nahrad(ZaCo, Term, Co , NovyTerm) :- Term =.. [F|Argumenty], nahrad_v_argumentoch(ZaCo,Argumenty, Co,NoveArgumenty), NovyTerm=..[F|NoveArgumenty]. nahrad_v_argumentoch( _ , [] , _ , [] ). nahrad_v_argumentoch(ZaCo, [Term|Termy], Co,[NovyTerm|NoveTermy]) :- nahrad(ZaCo, Term , Co, NovyTerm), nahrad_v_argumentoch( ZaCo, Termy, Co,NoveTermy).Použitie navrhnutého predikátu ilustruje dialóg:
?- nahrad(a, 3*a* (a^2-b)+sin(a)*sqrt(log(a/b)),q, N). N = 3 * q * (q ^ 2 - b) + sin(q) * sqrt(log(q / b)) -> yes