Ladda ner presentationen
Presentation laddar. Vänta.
Publicerades avJörgen Martinsson
1
ackumulatorer CM 3.7 AOP 8.3 (BBS 5.3, 6.2.2)
2
dagens föreläsning rekursion iteration ackumulatorer för effektivitet exempel
3
1. summera (repetition) summerar alla tal mellan N och M: summera(N, N, N). summera(N, M, Summa) :- N < M, N1 is N+1, summera(N1, M, Delsumma), Summa is N + Delsumma.
4
körning av summera ?- summera(1,5,X). 1 1 Call: summera(1,5,_454) ? 2 2 Call: 1<5 ? 2 2 Exit: 1<5 ? 3 2 Call: _981 is 1+1 ?... 4 2 Call: summera(2,5,_973) ? 7 3 Call: summera(3,5,_3556) ? 10 4 Call: summera(4,5,_6139) ? 13 5 Call: summera(5,5,_8722) ? … 17 2 Exit: 15 is 1+14 ? 1 1 Exit: summera(1,5,15) ? X = 15 ?
5
effektivitet… antag följande körning: ?- summera(1, 1000, X1). prolog måste då lösa: summera(2, 1000, X2), X1 is 1+X2 summera(3, 1000, X3), X2 is 2+X3, X1 is 1+X2 o.s.v. tills: summera(999, 1000, X999), X998 is 998+X999, …, X2 is 2+X3, X1 is 1+X2
6
effektivitet… detta ger en ganska lång ”att-göra-lista”! –när det tusende rekursiva anropet har nåtts, måste prolog ”komma ihåg” att göra 1000 additioner… istället kan vi använda ackumulatorer genom att: lägga till ett extra argument som representerar den delsumma vi fått hittills.
7
naturligt? vi tar det första talet (t ex 1) och lägger till det andra talet (2+1) sedan lägger vi till 3 till summan av föregående steg (3+3) osv: 4+6, 5+10, 6+15, …
8
summera med ackumulator accSummera(N, N, Acc, Summa) :- Summa is N + Acc. accSummera(N, M, Acc, Summa) :- N < M, N1 is N+1, Acc1 is N + Acc, accSummera(N1, M, Acc1, Summa).
9
körning av summera (igen) ?- summera(1,5,X). 1 1 Call: summera(1,5,_454) ? 2 2 Call: 1<5 ? 2 2 Exit: 1<5 ? 3 2 Call: _981 is 1+1 ?... 4 2 Call: summera(2,5,_973) ? 7 3 Call: summera(3,5,_3556) ? 10 4 Call: summera(4,5,_6139) ? 13 5 Call: summera(5,5,_8722) ? … 17 2 Exit: 15 is 1+14 ? 1 1 Exit: summera(1,5,15) ? X = 15 ?
10
fix summera2 kallar accSummera med ackumulatorn satt till 0 mer om detta snart!
11
körning av accSummera ?- summera2(1,5,X). 1 1 Call: summera2(1,5,_454) ? 2 2 Call: accSummera(1,5,0, _454) ? 3 3 Call: 1<5 ? 3 3 Exit: 1<5 ? … 6 3 Call: accSummera(2,5,1, _454) ? … 10 4 Call: accSummera(3,5,3, _454) ? 14 5 Call: accSummera(4,5,6, _454) ? 18 6 Call: accSummera(5,5,10, _454) ? 1 1 Exit: summera2(1,5,15) ? X = 15 ?
12
slutsatser lika många steg men: minnesutrymmet konstant i ackumulatorvarianten (oberoende av antalet iterationer)
13
imperativa språk… int räknare = 0; while (räknare < 10) { räknare = räknare + 1; } return räknare; prolog har inga ’storage variables’ (som t.ex. ’räknare’ ovan) därför måste vi använda rekursion i prolog
14
2. length (repetition) längden av en lista kan ges av predikatet length/2: length([],0). length([_|Tail], N) :- length(Tail, N1), N is 1 + N1.
15
length med ackumulator accLength([_|Tail],Acc,L) :- Acc1 is A+1, accLength(Tail,Acc1,L). accLength([],A,A).
16
sammanfattning prolog tillhandahåller inte iteration vi itererar med hjälp av den mer generella rekursionen fördelen med ”sann” iteration är effektivitet eftersom: för varje rekursivt anrop som inte terminerats måste en struktur (’stack frame’) hållas i minnet
17
sammanfattning (forts.) alltså: ett rekursivt program växer linjärt. en iterativ procedur använder en konstant mängd utrymme (oberoende av antal iterationer) ett sätt att göra rekursiva procedurer iterativa är att använda ackumulatorer
18
ytterligare effektivitet kan vi med hjälp av ackumulatorer ytterligare effektivisera programmeringen? japp, i vissa fall:
19
3. reverse [a,b,c,d] ska reverseras till [d,c,b,a] två versioner: –naiveRev/2 –accRev/3 (rev/2)
20
naiveRev basfall: –om vi reverserar [], så får vi tomma listan ([]) rekursiva fallet: –om vi reverserar [H|Tail], så slutar vi med listan vi får av att reversera Tail och sätta ihop den med H.
21
naiveRev/2 alltså: naiveRev([],[]). naiveRev([H|Tail],R) :- naiveRev(Tail,RevT), append(RevT,[H],R).
22
trace: naiveRev ?- naiveRev([a,b,c],R). 1 1 Call: naiveRev([a,b,c],_227) ? 2 2 Call: naiveRev([b,c],_699) ? 3 3 Call: naiveRev([c],_1069) ? 4 4 Call: naiveRev([],_1438) ? 4 4 Exit: naiveRev([],[]) ? 5 4 Call: append([],[c],_1069) ? 5 4 Exit: append([],[c],[c]) ? 3 3 Exit: naiveRev([c],[c]) ? 6 3 Call: append([c],[b],_699) ? 7 4 Call: append([],[b],_3515) ? 7 4 Exit: append([],[b],[b]) ? 6 3 Exit: append([c],[b],[c,b]) ? 2 2 Exit: naiveRev([b,c],[c,b]) ? 8 2 Call: append([c,b],[a],_227) ? 9 3 Call: append([b],[a],_5245) ? 10 4 Call: append([],[a],_5609) ? 10 4 Exit: append([],[a],[a]) ? 9 3 Exit: append([b],[a],[b,a]) ? 8 2 Exit: append([c,b],[a],[c,b,a]) ? 1 1 Exit: naiveRev([a,b,c],[c,b,a]) ? R = [c,b,a]
23
resultat 20 steg i tracen
24
accRev/3 accRev([H|Tail],A,R) :- accRev(Tail,[H|A],R). accRev([],A,A).
25
trace: accRev ?- accRev([a,b,c],[],R). 1 1 Call: accRev([a,b,c],[],_491) ? 2 2 Call: accRev([b,c],[a],_491) ? 3 3 Call: accRev([c],[b,a],_491) ? 4 4 Call: accRev([],[c,b,a],_491) ? 4 4 Exit: accRev([],[c,b,a],[c,b,a]) ? 3 3 Exit: accRev([c],[b,a],[c,b,a]) ? 2 2 Exit: accRev([b,c],[a],[c,b,a]) ? 1 1 Exit: accRev([a,b,c],[],[c,b,a]) ? R = [c,b,a]
26
resultat 8 steg i tracen
27
accRev listaackumulator [a,b,c][] [b,c][a], dvs: [a|[]] [c][b,a] [][c,b,a] ackumulatorn kopieras sedan till accRev/3 tredje argument –accRev([],A,A). vi effektiviserar alltså bort ’append’
28
ett vanligt ”fix” % dölj ackumulatorn vid användning rev(L,R) :- accRev(L,[],R). rev/2 anropas alltså med samma antal argument som vid naiveRev/2
29
jämförelse naiveRev (3 element):20 instruktioner accRev (3 element): 8 instruktioner naiveRev (8 element):90 instruktioner accRev (8 element):18 instruktioner och som sagt: minnesutrymmet växer linjärt i naiveRev, men är konstant i accRev
30
har vi sett det här förut? accRev igen: accRev([H|Tail],A,R) :- accRev(Tail,[H|A],R). skriv om som: accRev([H|Tail]) --> accRev(Tail),[H].
31
körning… ?- accRev([a,b,c],X,[]). 1 1 Call: accRev([a,b,c],_476,[]) ? 2 2 Call: accRev([b,c],_476,_1034) ? 3 3 Call: accRev([c],_476,_1538) ? 4 4 Call: accRev([],_476,_2042) ? 4 4 Exit: accRev([],_476,_476) ? 5 4 Call: 'C'(_476,c,_1538) ? 5 4 Exit: 'C'([c|_1538],c,_1538) ? 3 3 Exit: accRev([c],[c|_1538],_1538) ? 6 3 Call: 'C'(_1538,b,_1034) ? 6 3 Exit: 'C'([b|_1034],b,_1034) ? 2 2 Exit: accRev([b,c],[c,b|_1034],_1034) ? 7 2 Call: 'C'(_1034,a,[]) ? 7 2 Exit: 'C'([a],a,[]) ? 1 1 Exit: accRev([a,b,c],[c,b,a],[]) ? X = [c,b,a]
32
sammanfattning ackumulatorer är ett generellt sätt att med hjälp av rekursion implementera iteration iteration är utrymmeseffektivt (konstant under iterationerna) medan rekursion växer linjärt med antalet iterationer ibland är också iteration processeffektivt (t ex vid reversering av listor)
33
frågor etc: ponjo@ida.liu.se
Liknande presentationer
© 2024 SlidePlayer.se Inc.
All rights reserved.