260 likes | 400 Views
Effektiv prolog. 25/11 2002 Staffan Larsson Logikprogrammering. Effektivitet. Tidsåtgång Minnesåtgång Utvecklingstid Hur viktiga de olika aspekterna är kan variera 0.1-1 sek mindre viktigt än 10-100 minuter. Prolog vs. andra språk.
E N D
Effektiv prolog 25/11 2002 Staffan Larsson Logikprogrammering
Effektivitet • Tidsåtgång • Minnesåtgång • Utvecklingstid • Hur viktiga de olika aspekterna är kan variera • 0.1-1 sek mindre viktigt än 10-100 minuter
Prolog vs. andra språk • Generellt sett ngt mindre effektivt m a p tid än många andra språk • Mer effektivt m a p utvecklingstid • Lättare att skriva, förstå och debugga • Speciellt lämpat för vissa typer av problem • Symbolisk icke-numerisk processning • Strukturerade dataobjekt och deras relationer • T ex: planering, generell problemlösning, databaser, rapid prototyping, simulering, maskininlärning, natural language processing, AI • Mindre lämpat för matematik
Några sätt att öka effektiviteten • Kompilerad kod istället för tolkad • Effektivare algoritmer • Cachning • spara resultat som kan bli användbara längre fram
Effektivare algoritmer • Kräver • att man uppmärksammar de procedurella aspekterna av Prolog • Djupare förståelse av problemet (ev.) • Exempel • Förbättra ordningen på klausuler • Förbättra ordningen på mål i klausulernas ”body” • Användning av cut • Användning av ackumulatorer • Öka sökeffektivitet: undvik onödig backtracking, avsluta processning av onödiga alternativ snabbt • Mer ändamålsenliga datastrukturer, effektivare operationer
Ändra ordning för att spara tid • Exempel: Färgläggning av karta • Varje land på en karta ska få en färg • Inga angränsande länder får ha samma färg • Kartan definieras av ngb/2 • ngb(Land, Grannar) • Där Grannar är alla länder som gränsar till Land
databas ngb(albanien, [grekland, makedonien, jugoslavien]). ngb(andorra, [frankrike, spanien]). … ngb(österrike, [tjeckien, tyskland, italien, lichtenstein, slovakien, slovenien, schweiz]).
Givet: en databas över länder och deras grannar • T ex de europeiska länderna • En lösning har formen av en lista av par Land/Färg • [albanien/C1, andorra/C2, … , österrike/Cn] • Problem: instansiera C1, C2, …, Cn • Vi definierar • färger(Land_färg_lista) • Sant om Land_färg_lista satisfierar begränsningen att inga angränsande länder har samma färg • Färger: gul, blå, röd, grön
färger([]). färger([Land/Färg|Rest]):- färger(Rest), member(Färg, [gul, blå, röd, grön]), \+ ( member(Land1/Färg, Rest), granne(Land, Land1) ). granne(Land, Land1):- ngb(Land, Grannar), member(Land1, Grannar).
Innan vi anropar färger/1 sätter vi ihop listan • [albanien/C1, andorra/C2, … , österrike/Cn] • Ci, 1<=i<=n, är oinstansierade • Vi definierar därför land(C):- ngb(C,_). ?- setof(Lnd/Färg, land(Lnd), LandFärgLista), färger(LandFärgLista).
Denna lösning blir dock mycket ineffektiv; för Europa tar det väldans lång tid • Varför? • Länderna är ordnade alfabetiskt! • De tilldelas färger i alfabetisk ordning, utan hänsyn till geografiska relationer • Algoritmen hoppar ”slumpmässigt” omkring på kartan • Problem om en tidig tilldelning visar sig omöjlig först mot slutet, när dess sista granne ska få en färg
Intuition: en bättre lösning vore… • Att börja från ett land med många grannar (t ex Tyskland) • Fortsätt till dess grannar • Fortsätt till grannarnas grannar, etc. • Detta kan göras manuellt, eller… • Definiera makelist/1 som konstruerar listan av land/färg-par
makelist(List):- collect([tyskland], [], List). collect([], Closed, Closed). collect([X|Open], Closed, List):- member(X,Closed), !, collect(Open, Closed, List). collect([X|Open], Closed, List):- ngb(X, Ngbs), append(Ngs, Open, Open1), collect(Open1, [X|Closed], List).
Differenslistor; sparar tid och minne • Exempel: konkatenering ?- append([1,2],[3,4],X). X=[1,2,3,4] • Standarddefinition: append([],L,L). append([X|L1], L2, [X|L3]):- append(L1,L2,L3).
Exempelkörning: append([a,b],[c,d],L) append([b],[d,e], L’) där L=[a|L’] append([],[d,e], L’’) där L’=[b|L’’] true där L’’=[d,e] • Går igenom hela första listan, element för element • Går det att hoppa direkt till slutet av den först listan och direkt lägga till den andra listan?
Kräver ett annat sätt att representera listor: differenslistor • Exempel: • [a,b] kan istället representeras av två listor • L1 = [a,b,c,d] • L2 = [c,d] • Paret L1-L2, d v s [a,b,c,d]-[c,d] representerar differensen mellan L1 och L2 • Kräver att L2 är ett suffix till L1
Samma lista kan representeras av flera olika par: • [a,b]-[] • [a,b,c]-[c] • [a,b,c,d|T]-[c,d|T] • [a,b,c|T]-[c|T] • Tomma listan kan representeras som L-L • Den andra listan i paret representerar listans slut • Slutet på listan är direkt tillgängligt
Differenslistor i appendDL/3: append_DL(A1-Z1, Z1-Z2, A1-Z2). • Tänk på A1, Z1, A2, Z2 som positioner i listan A1 Z1 A2 Z2 A2=Z1 L3 • Körning: ?- append_DL([a,b|T1]-T1, [c,d|T2]-T2, L). L = [a,b,c,d|T2]-T2, T1 = [d,e|T2] L1 L2
. . . . . . . . . . . . . . . . . A1 A1-Z1 . . . . . . . . . . . . Z1 (A2) Z1-Z2 . . . . . . . Z2 . . . . . . . . . . . . . . . . . A1-Z2
Svansrekursion • Rekursiva anrop tar upp minne • Minnet inte frigörs förrän anropet terminerar • Stort antal nästade rekursiva anrop kan leda till minnesbrist • I vissa fall kan nästade rekursiva anrop skrivas så att de har konstant minnesåtgång
Detta kräver att proceduren har en speciell form: svansrekursion • Bara ett rekursivt anrop • I slutet på procedurens sista klausul • Prolog upptäcker detta och exekverar svansrekursion som iteration • reverse/2 med svansrekursion och ackumulator: reverse(List, Reversed):- reverse(List, [], Reversed). reverse([], Reversed, Reversed). reverse([X|Rest], PartRev, TotalRev):- reverse(Rest, [X|PartRev], TotalRev).
Cachning • Ibland måste samma mål satisfieras om och om igen • Exempel: Fibonacci-tal • 1,1,2,3,5,8,13,21,34,55,89,… • Varje tal är summan av de två närmast föregående talen
Vi definierar fib(+N,?F), där F är det N-te Fibonacci-talet fib(1,1). fib(2,1). fib(N,F):- N>2, N1 is N-1, fib(N1, F1), N2 is N-2, fib(N2, F2), F is F1+F2. • Detta är dock inte särsiklt effektivt
Vi använder assert/1 för att spara varje uträknat Fibonacci-tal: fib2(1,1). fib2(2,1). fib2(N,F):- N>2, N1 is N-1, fib2(N1, F1), N2 is N-2, fib2(N2, F2), F is F1+F2, assert( fib2(N, F) ).
Arrayer • Skapa en struktur med 100 element: • functor(A,f,100) • Imperativa språk: • A[60]:=1 • Prolog: • arg(60,A,1) • A=f(_, …, 1, …, _)
Uppdatera arrayen? • Ha en lista av succesiva värden istället för ett värde, med ett ”hål” för framtida värdenm i slutet • X:=1 -> X=[1|Rest1] • X:=2 -> Rest1= [2|Rest2] • X:=3 -> Rest2 = [3|Rest3] • Resultat: X = [1,2,3|Rest3]