250 likes | 355 Views
Prolog – seznamy. Jan Hric, 1997 – 2010b KTIML MFF UK URL: http://kti.ms.mff.cuni.cz/~hric/. Tok dat. není rozlišován vstupní vs. výst. argument + vstupní argument - výstupní argument +- arg. obsahuje volné proměnné ? cokoli mod predikátu a volání
E N D
Prolog – seznamy Jan Hric, 1997 – 2010b KTIML MFF UK URL: http://kti.ms.mff.cuni.cz/~hric/
Tok dat • není rozlišován vstupní vs. výst. argument • + vstupní argument • - výstupní argument • +- arg. obsahuje volné proměnné • ? cokoli • mod predikátu a volání • př: faktorial(+,?), rodic(-,-) • - systém to nekontroluje, konvence programátora
Tok dat - příklad • definice rodic/2 • predek(X,X). % 1. varianta • predek(X,Y):- rodic(X,Z), predek(Z,Y). • predek(X,X). % 2. varianta • predek(X,Y):- rodic(Z,Y), predek(X,Z). • volání p(+,+), p(+,-), p(-,+), p(-,-) • stejné 1.lepší 2.lepší • ?- p(adam,X) vs. ?- p(X,cyril) • vhodnost závisí na zamýšleném (příp. obvyklém) použití • jiné varianty (přehození cílů, klauzulí) jsou horší
Operace na seznamech • zjistit, zda X je prvkem seznamu L • % member(?X,+L) • member(X,[X|_]). % X je hlavou • member(X,[_|L]):- member(X,L). • % X je v těle • ?- member(b,[a,b,c]). % m(+,+) • yes • ?- member(X,[a,b]). % m(-,+) • X=a; % generovani • X=b; • no
Zpracování rekurzívních dat. struktur • - rekurzívní d.s. se zpracovávají rek. programy • - koncové případy jsou ošetřeny fakty • anebo nerekurzívní klauzulí • - aspoň jeden fakt anebo nerek. klauzule ! • - každý možný tvar d.s. (konstruktor) má být ošetřen - jinak neúspěch • - např. seznamy: []/0, ./2 • stromy: void/0, t/3 • - u member/2 ošetření [] není nutné (ekv. fail) • - není to obvyklé
Member/2 - pokrač. • - příklad podspecifikovaného cíle • ?- member(a,L). % m(+,-) • L = [a|_1]; • L = [_1,a|_2]; • L = [_1,_2,a|_3]; • ... atd. • - vydává se “nekonečně” mnoho řešení backtrackingem • - _číslo je výpis volné proměnné
member – pro asociativní paměť • Položky seznamu: klíč-hodnota % -(k,h) • ?- Tab= [k1-h1,k2-h2,k2-h2b], % generovat • member(k2-HN,Tab). % ke kN najde HN • HN = h2; • HN = h2b; • No - Zde využíváme backtracking, ale lze vracet i všechny hodnoty najednou, v seznamu % lookup(Klic, Pamet, Hodnota) : interface pro vyhledávání v nějaké datové struktuře, nejen seznamu (BVS) lookup(K, Tab, H):- member(K-H, Tab). DC: lookup2D/3: vyhledávání pro dvousložkový klíč, v rovině
První a poslední prvek seznamu • % first(?X,+L) :- X je první v L • a) first(X,[X|_]). % dobrý styl • b) first(X,L):- L=[X|_]. • % last(?X,+L) :- X je poslední v L • last(X,[X]). • last(X,[_|L]):- last(X,L). • %nesymetrický přístup: O(1) na první prvek vs. O(n) na poslední
Spojení seznamů - append(L1,L2,L3) spojí L1 a L2 do L3 append([],L,L). append([X|L1],L2,[X|L3]):- append(L1,L2,L3). ?- append([a,b],[c,d],[a,b,c,d]). %(+,+,+) yes ?- append([a,b],[c,d],L). % mod (+,+,-) L=[a,b,c,d]; no - programátorské idiomy: strukturální rekurze podle prvního arg., tvorba výsledku - skládáním substitucí • Pozn.: polymorfizmus – analyzuje a využívá se pouze „horní“ struktura
?- X=[a,b], Y=[c,d], append(X,Y,Z). • Z=[a,b,c,d] • tvorba Z - skládáním substitucí • Z = _L3´ • Z = [a|_L3´´] • Z = [a,b|_L3´´´] • Z = [a,b,c,d] X Z Y . . \ . / \ . / \ . . a / \ c / \ [] [] b d
append/3 jako (nederministický) generátor • ?- append(L1,L2,[a,b,c]). % (-,-,+) • L1=[] • L2=[a,b,c]; • L1=[a] • L2=[b,c]; • L1=[a,b] • L2=[c]; • L1=[a,b,c] • L2=[]; • no
concat/2 – Spojení seznamů - převod (řádkové) matice na vektor % concat(+L,-K) – seznam seznamů L spojí do jednoúrovňového seznamu K concat([], []). concat([Xs|L1],L0) :- append(Xs,L2,L0), concat(L1,L2). ?- concat([[1,2],[5],[],[3,4]],Lout). Lout=[1,2,5,3,4] • Počas výpočtu je L0 seznam s volným koncem • L0=[1,2|L2’] ~> [1,2,5|L2’’] ~> [1,2,5,3,4|L2’’’] ~> [1,2,5,3,4] • Nelze použít concat(-,+) pro rozdělení; generuje Xs=[]
Obracení seznamu • % reverse(L,R):- R je L pozpátku • % mod (+,-) • reverse([],[]). • reverse([X|L],R):- reverse(L,R1), • append(R1,[X],R). • - nepříjemnost: složitost O(n^2) • - typická struktura rekurzívních klauzulí • pred :- předvýpočet, % analýza d.s. • pred, % rek. výpočet mezivýsl. • postvýpočet. % použití -”-
Obracení seznamu II - lineárně • reverse(L,R):- rev1(L,[],R). • rev1([],R,R). % mod (+,+,-) • rev1([X|L],A,R):- rev1(L,[X|A],R). • - programátorská technika - akumulátor: “A” • - inicializace - samostatná klauzule/predikát • - odstínění uživatele od technických detailů • - předávání (volné) výstupní prom. v rek.: “R” • - obvykle samostatný argument • - ukončení - předání: “R:=A” %3.arg := 2.arg • x skládání substitucí - přístup na akum. počas výpočtu
Vypouštění prvku % delete(X,I,O):- vypuštěním X ze seznamu I je seznam O; X musí být v I, vypouštíme jeden (lib.) výskyt % mody vypouštění (?,+,-), vkládání (?,-,+); presneji (?,[?],-) • delete(X,[X|I],I). %1 X musí být v arg2 • delete(X,[Y|I],[Y|O]):- %2 • delete(X,I,O). • ?- delete(X,[1,2,3],O). % 3 výsledky • ?- delete(a,I,[b,c]). %vkládání,3x • Varianta: delete1/3: vypustí prvek a vždy uspěje • delete1(X,[],[]). %3+^1+^2: falešné matche! • Nekorektní další výsledky; správnost všech v.! • DC: deleteAll(X,Lin,Lout);
Permutace • % perm(I,O):- O je permutací I, (+,-) • perm([],[]). • perm(I,[X|O]):- delete(X,I,I1), • perm(I1,O). • ?- perm([1,2,3,4],O). • - skládání substitucí • - rekurze podle výstupu • - nederminizmus: řešení se vrací postupně • - jiná (těžší) možnost: vrátit najednou seznam všech permutací • - DC: permutace rekurzí podle vstupu. V jakém pořadí budou vydávány?
Použití append/3 • last(X,L):- append(_,[X],L). • - stejná asymptotická složitost • - horší konkrétní složitost, ošetřujeme v append 1.arg. zbytečně • delete(X,I,O):- append(L1,[X|L2],I), • append(L1, L2, O). • - L1 se prochází 2x • - nejde použít v modu (?,-,+) • - muselo by se napsat jinak: prohodit cíle v těle • prefix/2, suffix/2 - zdarma (bez rekurze) • prefix(P,L):- append(P,_,L). • Pozn. SwIng: budování konkrétních predikátů zvrchu vs. obecné predikáty zespodu (do knihoven) a příp. interface. • Při (častých) změnách méně výkonného kódu ~> méně úprav a chyb
Seznam výsledků % suffix(+L,-S) – S je přípona L, backtrackingem suffix(S,S). % zahrnuje suffix([],[]). suffix([_|L],S):- suffix(L,S). ?- suffix([1,2,3],L). L=[1,2,3] ; L=[2,3] ; L=[3] ; L=[] ; no % suffixy(+L,-Ss) – Ss jsou všechny prípony L v seznamu suffixy([],[[]]). suffixy([X|Xs],[[X|Xs]|Ss]):- suffixy(Xs,Ss). ?- suffixy([1,2,3],Ss). Ss=[[1,2,3],[2,3],[3],[]] % typicky vhodnější
Prefixy % prefixy(+Xs,-Ps)- Ps je seznam všech předpon Xs prefixy([],[[]]). prefixy([X|Xs],[[]|P0]) :- prefixy(Xs,P1), map_pridejH(X,P1,P0). % (1) prefixy v P1 upravíme % přidáva hlavu ke každému seznamu v P1 map_pridejH(_X,[],[]). % jednoúčelové map map_pridejH( X,[P|Ps],[[X|P]|Ps0]):- map_pridejH(X,Ps,Ps0). ?- prefixy([1,2,3],Ps). Ps=[[],[1],[1,2],[1,2,3]] - Průběžně: Ps’=[[],[2],[2,3]], Ps’’=[[],[3]], Ps’’’=[[]] - (1) obecné map: ..,map(pridejH(X),P1,P0),… • pridejH(X,L,[X|L]). % mimo logiku 1. řádu
Seznam prvků ve stromu • tree2list(Strom,Seznam) :- do Seznamu pozbírá prvky Stromu (zleva doprava) • tree2list(void,[]). • tree2list(t(L,X,R),O):- • tree2list(L,OL), %rekurze doleva • tree2list(R,OR), %rekurze doprava • append(OL,[X|OR],O). % spojení inorder - seznam jako výsledek • pre-,post-,inorder (zleva): rozdíl toku programu a “toku” dat • append/3 na stejném místě, ale s jinak předávanými parametry - pro generování stromů (mod (-,+) ) nutno prohodit cíle - pro repr. stromů použít funkční symbol t/3 , a void/0 - nevhodný je (zde trojprvkový) seznam, pokud je počet položek pevný ?- TestData = t( t(void,1,void), 2, t(void,3,void)), tree2list(TestData,V).
Výroba vyváženého stromu - z (uspořádaného) seznamu L chci vyvážený strom T % vyvazBS(+L,-T). vyvazBS([],void). %vyvazBS([X],t(void,X,void)).%konc.podm.není nutná vyvazBS(L,t(TL,X,TR)):- rozdel(L,L1,[X|L2]), % rozdělí na poloviny % pro uspořádaný seznam L: X je medián vyvazBS(L1,TL), vyvazBS(L2,TR). - vyvazBS/2 lze použít v heapsortu pro vybudování správné struktury „haldy“ (bez uspořádání)
Rozdel/3 • rozdel(L,L1,L2) – rozdělí L na první a druhou polovinu, |L1|<=|L2|<=|L1|+1 • Použije v rozdel1/4 druhý arg. jako čítač rozdel(L,L1,L2):- rozdel1(L,L,L1,L2). % interface rozdel1(L2,[],[],L2). % sudá délka rozdel1(L2,[_],[],L2). % lichá délka rozdel1([X|L],[_,_|Acc],[X|L1],L2):- rozdel1(L,Acc,L1,L2).
Množinové výrazy • Výraz obsahuje +/2 pro sjednocení, */2 pro průnik, -/2 pro rozdíl, seznam pro množinu a i(D,H) pro celočíselný interval. - evalS: interpret množinových výrazů, term je program porovnejte s Aho-Corasick: vzorky (1) jsou program (v neplnohodnotném jazyce, doménově specifický jazyk - DSL), který dokonce dokážeme přeložit ad(1): vzorky jsou seznam seznamů (reprezentací) znaků, např. [[h,e],[s,h,e],[h,e,r]] ?- evalS(-(+([3,1,3],i(2,4)),[2,3]), S). S = [1,4] % synt. cukr: zápis s operátory ?-evalS(([3,1,3]+ i(2,4))–[2,3], S). evalS(L,S):- isList(L), list2set(L,S). % převod na (usp.) množinu evalS(i(D,H),S) :- genInterval(D,H,S). evalS(+(E1,E2),S):-evalS(E1,S1), evalS(E2,S2), union(S1,S2,S). evalS(-(E1,E2),S):-evalS(E1,S1), evalS(E2,S2),rozdilS(S1,S2,S). evalS(*(E1,E2),S):-evalS(E1,S1), evalS(E2,S2), prunik(S1,S2,S). • lze použít pro uspořádané i neuspořádané množiny na výst. • Impl. se liší ve výkonných pred. union/3, …, list2set/2, genInterval/2. • DC: interpret multimnožinových výrazů
Shrnutí - tok dat, mody - dvojí použití predikátů: výpočet, test - problémy s podspecifikovanými argumenty v cíli - nedeterminizmus - zpracování rek. struktur - strukturální rekurze - tvorba výsledků - skládáním substitucí - akumulátor • použít interface:pokud rekurzi nejde použít přímo (např. je potřebný „lokální“ argument) - dobrý styl: nezatěžovat uživatele „technickými“ zbytečnostmi - seznam výsledků, místo vracení backtrackingem - o rekurzi víte vše, dále vestavěné predikáty, programátorské techniky a příklady
Autotest • naprogramujte permutace pomocí vkládání prvku a akumulátoru • návod: použijte akumulátor na permutaci dosud zpracovaného prefixu vstupního seznamu • napište rozdělení prvků seznamu na sudé a liché (podle pořadí) • - v jednom průchodu seznamem • - správně ošetřete seznam liché délky • - použití v “rozděl a panuj”: mergesort • ? je append/3 použitelný v modu (+,-,-) • ? jak se bude chovat volání: append([a,b],X,Y) • - pokud má otázka smysl