ID1218 Johan Montelius Omtentamen: Tillämpad Programmering (ID1218) 2012-06-12 09:00-13:00 Förnamn: Efternamn: Regler Du får inte ha något materiel med dig förutom skrivmateriel. Mobiler etc, skall lämnas till tentamensvakten. Instruktioner Tentamen har totalt 200 poäng och skall skrivas på 4 timmar. Antalet poäng på en uppgift skall ge dig en vägledning till hur mycket tid uppgiften kan ta att lösa, planera din tid väl. Läs igenom hela tentamen innan du börjar. Svaren skall lämnas på dessa sidor, använd det utrymme som finns under varje uppgift för att skriva ner ditt svar. Om du inte får plats med sitt svar så kan du använda ytterligare sidor. Dessa sidor skall då vara märkta med namn och personnumer. Det skall även tydligt framgå i svaret under uppgiften att ytterligare sidor har använts. 1
Du skall lämna in hela denna tentamen. Svar skall skrivas på svenska. Betyg Erhållna poäng Skriv inte här, detta är för rättningen. n < 100 F 100 n < 120 E 120 n < 140 D 140 n < 160 C 160 n < 180 B 180 n A Uppgift 1 2 3 4 5 6 7 Σ Max 30 30 30 30 30 20 30 200 Poäng Totalt antal poäng: Betyg: 2
1 Funktionell Programmering[totalt 30 poäng] Det finns en sorteringsalgoritm som man nästan aldrig vill använda eftersom den har en del icke önskvärda egenskaper. I algoritmen så bubblar vi en lista tills vi har en sorterad lista. Att bubbla listan betyder att man går ner till slutet av listan och sen låter element byta plats om det behövs när man bygger upp den nya lista. Att bubbla skulle vi kunna beskriva som följer: Om listan är tom eller endast har ett element så returner vi listan annars, plockar vi ut det första elementet A, bubblar resten, som ger oss ett bubblat svar[b R] returnerar en lista bestående av: A följt av B och R om A är mindre än B B följt av A och R om A inte är mindre än B Att bara bubbla en lista ger oss inte en sorterad lista men det är ett steg på vägen. Vad vi vet är att det minsta elementet i listan ligger överst och det är det vi kan använda oss av. Vi kan nu beskriva bubble sort som följer: Om listan är tom så returnerar vi listan annars, bubblar vi listan och plockar ut det första elementet, resultatet är en lista som har detta element som sitt första element (vi vet ju att det är det minsta elementet) och svans den bubbelsorterade resten. Uppgiften är att implementera denna algoritm i Erlang i två steg och sen implementera en optimering. 1.1 en bubbla[10 poäng] Definiera en funktion bubble/1, som tar en lista och returnerar en bubblad lista. Funktionen behöver inte vara svansrekursiv. Exempel på användning: >bubble([]). [] >bubble([2,2,2,1]) [1,2,2,2]} >bubble([6,5,1,2,4,3]) [1,6,5,2,3,4]} 3
1.2 alla bubblor[10 poäng] Givet att vi har funktionen bubble/1 ovan, definiera en funktion bsort/1 som tillämpar denna rekursivt tills vi har en lista som är sorterad. Funktionen behöver inte vara svansrekursiv. Exempel på användning: >bsort([3,4,2,5,1]). [1,2,3,4,5] 1.3 upptäck att vi är klara[6 poäng] Det finns en förbättring som vi kan göra (förutom att byta till quick-sort): skriv om bubble/1 så att den returnerar antingen {done, List} om listan visade sig vara sorterad eller, {more, List} om några element bubblade. Idén är att kunna avbryta sorteringen tidigt om listan visar sig vara sorterad. 4
1.4 den färdiga funktionen[4 poäng] Antag nu att vi har definierat funktionen bubble/1 som returnerar en tuple som beskrivits ovan. Modifiera bsort/1 så att den använder den modifierade funktionen 2 Komplexitet[totalt 30 poäng] I dessa uppgifter skall du visa att du förstår hur vi kan beräkna tidskomplexiteten för funktioner. Vi använder bubble sort ovan som exempel och antar, även om så inte är fallet, att du har löst första uppgiften på ett effektivt sätt. 2.1 bubble[10 poäng] Antag att vi har den första lösningen utav bubble/1, utan de optimeringar som vi kräver i den mer effektiva lösning. Vilken är storleksfunktionen (size function), upprepningsfunktionen (recurrence relation) T bubble (n) och den asymptotiska tidskomplexiteten för funktionen bubble/1? 5
2.2 bsort[10 poäng] Vilka är storleksfunktionen, upprepningsfunktionen och den asymptotiska tidskomplexiteten för funktionen bsort/1? 2.3 optimeringar[10 poäng] Antag att vi gör de optimeringar som som krävs för den sista deluppgiften och implementerar dessa på ett korrekt sätt, hur påverkas funktionens tidskomplexitet. Beskriv i ord vad som förändras och varför. 3 Operationell semantik[totalt 30 poäng] Den operationella semantiken för Erlang kan beskrivas med hjälp av en abstrakt maskin, MiniErlang, vars omskrivningsregler ges i appendix. 3.1 att skapa en lista[10 poäng] Visa, utan att ta några genvägar, hur uttrycket [1,2] exekveras med hjälp av MiniErlang. 6
[1,2] ;... 3.2 ett program[10 poäng] Antag att vi utökar vår maskin med en regel som kan flytta ett helt uttryck E direkt till värdestacken om detta uttryck är fritt från funktionsanrop och representerar ett värde V. Vi kan alltså gör saker så som: E Es ; Vs Es ; V Vs [1,2] Es ; Vs Es ; [1,2] Vs Givet nedanstånde MiniErlang-program, visa exekveringen av uttrycket ap([1,2],[3,4]). app([], B) -> B; app([h T], B) -> [H app(t, B)]. 7
3.3 Utöka maskinen[10 poäng] Antag att vi har utökat MiniErlang maskinen så att den stödjer jämförelse av literaler genom den inbyggda funktionen eq/2. Vi gör detta genom att utöka maskinen med tre regler: eq(e 1,E 2 ) Es ; Vs E 1 E2 EQ Es ; Vs EQ Es ; V 2 V1 Vs Es ; true Vs om V 1 och V 2 är lika EQ Es ; V 2 V1 Vs Es ; false Vs om V 1 och V 2 är olika Vi antar här helt glatt att vi kan avgöra om två värden är lika, något som inte är helt orimligt. Vi vill naturligtvis använda vår nya kraft och ha en funktion som ger oss if-thenelse. Vi skulle kunna implementera en funktion if/3 på följande sätt. if(true, Then, Else) -> Then; if(false, Then, Else) -> Else. Detta fungerar naturligtvis men det ger oss vissa problem. Vår maskin är strikt i sin evalueringsordning vilket betyder att alla argument till en funktion evalueras innan funktionen anropas. Detta betyder att ett anrop if(false, loop(), ok) skulle loop:a (antag att anropet till loop() loopar) istället för att genast returnera ok. Vi skulle därför vilja ha en en specialregel som tar hand om evalueringen av if-funktionen. Vi måste naturligtvis evaluera själva testet för att gå vidare men alternativen skall bara evalueras om det verkligen behövs. Hur skulle en sådan regel, eller regler, se ut? if(test, Then, Else) Es ; Vs... : : 8
4 Högre-ordningens funktioner[30 poäng] I följande problem skall du visa att du kan hantera funktioner som första klassens objekt och högre-ordningens funktioner så som: filter/2, map/3 och foldl/3 och foldr/3, som finns beskrivna i appendix. 4.1 getem[5 poäng] Skriv en funktion getem(z, List), som givet en lista med tupler {X, Y}, returnerar en lista med de tupler vars andra element är större än Z. 4.2 addem[5 poäng] Skriv en funktion som tar en lista av tupler, {X,Y} där Y är en integer, och returnerar summan av alla talen Y. 9
4.3 medel för en kurs?[10 poäng] Antag att vi har en lista med element som representerar studenters resultat på kurser, {Name, Course, Grade}, där varje kurs representeras som en unik kursidentifierare (id1218,..) och betyget på skalan 3..5. Skriv en funktion, average(course, List), som tar en kursidentifierare och en lista med studenter och returnerar medelbetyget för den givna kursen. Om kursen inte har några studenter skall funktionen returnera na. 4.4 ett träd[10 poäng] Antag att vi kommer att göra ett antal olika beräkningar på löven i ett träd: summera löven, räkna antalet löv, plocka ut vissa etc. Antag att varje nod i trädet representeras{branch, Value, Left, Right} (en gren med ett värde, vänster och höger gren) eller nil (en tom gren). {branch, 3, {branch, 1, nil, {branch, 2, nil, nil}}, {branch, 4, nil, nil}} Istället för att att skriva en funktion för varje operation skall du Definiera en funktion, foldt(fun, Acc, Tree), som kan traversera ett träd och tillämpa en funktion på dess löv och en ackumulator, på samma sätt som foldl/3 gör på listor. Om Tree är trädet beskrivet ovan skulle man kunna göra som följer för att summera värdena eller räkna antalet värden: 10
> foldt(fun(v,a) -> V + A end, 0, Tree). 10 > foldt(fun(_,a) -> A + 1 end, 0, Tree). 4 5 Concurrency[totalt 30 poäng] 5.1 echo[10 poäng] Definiera en funktion start/0 som startar en echo-process. Processen skall kunna ta emot meddelanden på formen {echo, From, Message} där From är en processidentifierare och Message ett meddelande på någon form. Processen skall för varje echo-meddelande skicka ett meddelande {echo, Message} till processen From. Om man skickar echo-processen ett meddelande quit skall den avsluta. 5.2 vad är problemet[3 poäng] Processen kanske inte är så säker som vi vill ha den, vem som helst som känner till dess pid kan skicka konstiga meddelanden till den. Vad är problemet med att processer skickar konstiga meddelanden, de lär ju inte få något svar men det är väl deras problem? 11
5.3 hur löser vi det[2 poäng] Hur kan vi lösa problemet? 5.4 ännu säkrare[5 poäng] Vem som helst kan fortfarande skicka ett quit-meddelande. Gör om programmet så att den som skapar processen får en unik nyckel (använd make_ref()) som den kan använda när den vill stänga ner processen Endast quit-meddelanden med den rätta nyckeln skall stänga ner processen, alla andra skall ignoreras. 5.5 En NTP-server[10 poäng] NTP (Network Time Protocol) har du säkert använt för att synkronisera klockan på någon dator, du skall implementera en något förenklad version av protokollet. En klient skickar ett meddelande {request, From, T0} till en server. Denne skickar tillbaks ett meddelande{reply, T0, T1} där T0 är en tidsstäm- 12
pel när klienten skickade sitt medelande och T1 är tiden när servern tog emot meddelandet och skickade svaret (i det riktiga NTP-protokollet ges här två tider så att man inte måste skicka exakt när man tar emot). Klienten klockar när meddelandet kommer tillbaks, T2, och kan sen räkna ut hur den skall justera sin egen klocka med hjälp av formeln t=t 1 +((t 1 t 0 )+(t 2 t 1 ))/2 Om du undrar varför servern skickar tillbaks t 0 så är det för att klienten skall kunna ha flera förfrågningar ute och inte behöva hålla koll på när den skickade vilken förfrågan. Det är inte troligt att vi skulle kunna ställa vår klocka med någon större noggrannhet genom att göra det i Erlang men som en övning kan vi definiera hur en server-process är definierad och hur en klient skulle kunna uppdatera sin klocka. Antag att vi kan få en tidsstämpel med funktionen now() och att du kan ta skillnaden mellan två tidsstämplar med hjälp av funktionen now_diff(t1,t0) (t 1 t 0 ). Vi kan även anta att det finns en funktion now_adjust(t) som en process kan användas för att sätta sin klocka till tid T. Definiera en procedur ntp som skulle kunna fungera som servern huvud-loop, du behöver inte skriva all kod hur processen startas upp utan bara dess huvudloop. Definiera också en procedur sync(ntp) som en klient kan använda för att synkronisera sin inbyggda klocka; NTP är en pid till en ntp-server. 13
6 Värden, pekare och arrayer[totalt 20 poäng] 6.1 Legala uttryck[10 poäng] Vilka uttryck är legala uttryck i C ++ (som följer standarden C ++ 03 )? Svara enbart ja eller nej. 1. int a[3*7-1]; 2. int n=4; int a[n]; 3. int x=7; int* p = &x; 4. int x=32; int** y = &(&x)-5; 5. int x=2; int y = *(&(*(&x+1))); 6.2 värdet y:[5 poäng] Givet nedanstående uttryck, vad är värdet på variabeln y? int a[5] = {3,2,-1,5,4}; int y = *(&a[3] - 2); 6.3 värdet y:[5 poäng] Givet nedanstående uttryck, vad är värdet på arrayen b? int b[5] = {3,2,-1,5,4}; int* p = &b[1]; *(p + 2) = *p -4; 14
7 Klasser och objekt[totalt 30 poäng] I följande frågor antar vi att vi inkluderar biblioteket iostream och använder dess namespace std. Givet är också nedanstående definition av klassen I nt C ell. class IntCell { public: IntCell(int initialvalue = 0 ) { storedvalue = new int(initialvalue); } int getvalue() const { return *storedvalue; } void setvalue(int val) { *storedvalue = val;} private: int *storedvalue; }; 7.1 lite i farligaste laget[5 poäng] Vi implementerar följande funktion swap(intcell &x, IntCell &y) som låter x och y byta värden. Allting kompilerar och vi kör en del tester utan problem. Det finns dock ett problem med funktionen som vi inte upptäcker i första taget. Vad är problemet? void swap(intcell &x, IntCell &y) { IntCell tmp = IntCell(); } tmp = x; x = y; y = tmp; 15
7.2 vilka objekt modifieras[5 poäng] Givet programmet nedan, vad kommer att skrivas ut på cout och varför? Skriv en kort motivering till ditt svar. #include <iostream> using namespace std; int main() { IntCell a(2); IntCell b = a; IntCell c; c = b; a.setvalue(4); } cout << a.getvalue() << endl; cout << b.getvalue() << endl; cout << c.getvalue() << endl; 7.3 leave no one behind[5 poäng] Om vi har skapat två IntCell-objekt, a och b, och utför instruktionen a = b så kan vi få en minnesläcka; vi kommer att förlora pekaren till den int-objekt som är allokerad för a. Ett sätt att undvika detta är att skriva en explicit metod för tilldelning och av-allokera objektet innan vi sätter värdet till dess rätta värde. Om vi implementerar metoden så här kan vi ha ett stort problem om vi inte är försiktiga, vilket? IntCell& operator=(const IntCell& c) { delete storedvalue; 16
} storedvalue = c.storedvalue; return *this; 7.4 varför &[5 poäng] I definitionen nedan har vi definierat parametern till funktionen som IntCell &x, varför det, vi kommer att få exakt samma resultat om vi skriverintcell x. Varför väljer vi att göra som vi gör? int addfive(intcell &x) { } return x.getvalue() + 5; 7.5 varför const[5 poäng] I definitionen nedan har vi definierat parametern till funktionen som const IntCell &x, varför det, vi kommer att få exakt samma resultat om vi skriver IntCell &x. Varför väljer vi att göra som vi gör? int addfive(const IntCell &x) { } return x.getvalue() + 5; 17
7.6 kopiering[5 poäng] Ibland vill man använda en användardefinierad kopierings-konstruktor. Antag att vi definierar en sådan som följer för IntCell-klassen, vilka konsekvenser skulle detta ha i ett program? IntCell(const IntCell &c): storedvalue(new int(c.getvalue())) { } Appendix Här följer de transformeringsregler som definierar MiniErlang. En literal (integer) på uttrycksstacken flyttas över till värdestacken. i Es ; Vs Es ; i Vs Ett cons-uttryck delas upp, läggs på uttrycksstacken och åtföljs av en CONSinstruktion. [E 1 E 2 ] Es ; Vs E 1 E2 CONS Es ; Vs Ett funktionsanrop delas upp, läggs på uttrycksstacken och åtföljs av en CALLinstruktion. F(E 1,...,E n ) Es ; Vs E 1...... En CALL(F/n) Es ; Vs En CONS-instruktion tar de två översta värdena på värdestacken och bildar en cons-cell. Observera att värdena ligger i omvänd ordning på stacken. CONS Es ; V 2 V1 Vs Es ; [V 1 V 2 ] Vs En CALL-instruktion tar sina argument från värdestacken och lägger en kopia av matchande definitions kropp, E på uttrycksstacken där substitutionen från matchningen, S, har applicerats. Observera att värdena ligger på omvänd ordning på värdestacken, det översta värdet är alltså funktionens sista argument. 18
CALL(F/n) Es ; V n... V1 Vs S(E) Es ; Vs Följande Erlang-funktioner kan komma till användning. map(fun, List): returnerar en lista där funktionen applicerats på varje element i den givna listan. filter(fun, List): returnerar en lista av de element i listan för vilka funktionen returnerar true. foldl(fun, Acc, List): returnerar ett värde som fås genom att applicera funktionen på varje element, med början med första elementet, i listan och ett ackumulerat värde där Acc är det initiala ackumulerade värdet. foldl(fun, Acc, List) och foldr(fun, Acc, List): returnerar en värde som fås genom att applicera funktionen på varje element i listan och ett ackumulerat värde där Acc är det initiala ackumulerade värdet. Funktionen foldr börjar med det sista elementet och foldl med det första elementet 19