1 Linköpings tekniska högskola Institutionen för datavetenskap Anders Haraldsson Tentamen i TDDC67 Funktionell programmering och Lisp och äldre kurser TDDC57 Programmering, Lisp och funktionell programmering TDDC80 Programmering, Lisp och funktionell programmering Fredag den 1 april 2012 kl 14-19 Hjälpmedel: Inga. Poänggränser: Maximalt kan erhållas 62p. För godkänt krävs ca 25p Allmänna direktiv: Skriv läsligt och använd väl valda benämningar på funktioner och parametrar! Indentera programkoden enligt vedertagen praxis! Börja varje huvuduppgift på ett nytt blad! Läs igenom hela tentan! Det är inte säkert att uppgifterna kommer i svårighetsgrad. Är du osäker på någon Common Lisp-funktion, beskriv hur du tror att den fungerar och vilka parametrar den tar. Om funktionen finns och gör ungefär det som du tror får du inga poängavdrag, även om parametrarna är i fel ordning. Lycka till!
2 Uppgift 1. Listor, grafisk representation, evaluatorn (10 poäng) 1a. (1p) Vad blir värdet i parentesformat av följande uttryck. Rita även upp värdet med den grafiska representationen med cons-celler och pekare. (cons (cons x ()) (list y z)) 1b. (1p) Skriv ett Lisp-uttryck som vid beräkning ger ett värde med följande struktur. Vad blir värdet i parentesformat? a d a b c 1c. (p) Rita upp den grafiska strukturen för det värde som erhålls av följande uttryck: (setq p (let ((temp (cons (cons 1 2) ))) (list temp temp temp))) Visa vad som händer i strukturen då man evaluerar följande uttryck. Vilket värde i parentesformat skriv ut av p. (setf (first (rest p)) 4) p =>? (setf (cdr (first p)) 5) p =>? 1d. (2p) Skriv ett Lisp-uttryck som skapar följande struktur. Hur ser utskriften ut om värdet av p skrivs ut? p a b 1e. (p) Vi har givet följande globala variabel. (setq test-data ( (+ 2 ) (quote (* 2 )))) Vad blir värdet av följande uttryck. Motivera! (funcall # (lambda (x y) (list y x)) (first test-data) (second test-data)) (eval (eval (third test-data))) (mapcar # (lambda (e) (if (numberp e) e (eval e))) test-data)
Uppgift 2. Olika modeller för att definiera funktioner (17 poäng) 2a. (7p) Skriv en rekursiv funktion ta-bort-antal, som tar bort de n första förekomsterna av en given symbol. Du skall ge två versioner, en som gör en rekursiv processlösning och en som gör en iterativ processlösning, och ange vilken lösning som följer vilken modell. (ta-bort-antal 2 bort (a bort b bort c bort bort d)) => (a b c bort bort d) ; ta bort de två första förekomsterna (ta-bort-antal 5 bort ny (a bort b bort c bort bort d)) => (a b c d) ; det fanns bara 4, och dessa togs bort Vad skiljer mellan en rekursiv processlösning och en iterativ processlösning? 2b. (p) Skriv en iterativ version av funktionen ta-bort-antal från uppgift 2a. 2c. (p) Skriv en funktion ta-bort-alla, som ersätter alla förekomster av en symbol på oavsett nivå i en lista. Vi kan i denna uppgift antaga att listan ej innehåller punkterade par. (ta-bort-alla gammal ny (bort (a bort) ((bort c) d) bort)) => ((a ((c) d)) 2d. (4p) Skriv en funktion byt-löv, som i ett binärt träd byter ut alla förekomster av ett löv (i form av en symbol) till ett annat löv. Det binära trädet representeras som punkterade par. (byt-löv gammal ny ((gammal. a). ((b. gammal). c))) => ((ny. a). ((b. ny). c)) ;utskrift i punkt-notation, se uppgift nedan (byt-löv gammal ny gammal) => ny Hur skriver Lisp ut uttrycket ((ny. a). ((b. ny). c)))?
4 Uppgift. Högre ordningens funktioner (9 poäng) a. (p) Skriv en högre ordningens funktion (byt-ut-villkor filter-pos-fn byt-ut-element-fn lista), som ersätter alla element som uppfyller villkoret av filter-pos-fn. Denna funktion tar positionen i listan där elementet finns. Elementet ersätts i så fall med det värde byt-ut-element-fn ger. Denna funktion i sin tur tar elementet som parameter. Positionen i listan startar med position 1. Se exemplet: (byt-ut-villkor # (lambda (pos) (<= pos 4)) # (lambda (e) (* e e)) (1 2 4 10 20 0)) => (1 4 9 16 10 20 0) ; dvs de 4 första elementen kvadreras b. (2p) Skriv en funktion ersätt-udda-pos, som tar ett element och en lista med tal. Funktionen skall skapa en ny lista där alla talen i udda position (dvs första, tredje etc) skall ersättas med elementet. Funktionen byt-ut-villkor skall användas i lösningen 1. (defun ersätt-udda-pos (nytt-element tal-lista) (byt-ut-villkor?a?b tal-lista)) Ange uttrycken för?a och?b i ovanstående funktionsdefinition. (ersätt-udda-pos udda (2 6 5 4)) => (udda udda 5 udda) ; dvs talen 2, 6 och 4 i udda positioner byts ut. c. (4p) Lexical closure. Skriv en funktion komplement, som tar en predikatfunktion (som ger sant eller falskt värde) och skapar en ny funktion, som gör komplementet till predikatfunktionen, dvs man får falskt istället för sant och vice versa. (defun positiv? (n) (>= n 0)) (positiv? 10) (setq negativ? (komplement # positiv?)) (funcall negativ? 10) => nil (funcall (komplement # (lambda (n) (> n 10))) 5) ; Är 5 inte större än 10. 1. Det finns en funktion oddp som testar om ett tal är udda.
Uppgift 4. Datastrukturer och pekarändringar (6 poäng) (6p) Antag vi har en tabell, representerad som en arraystruktur. Varje element är en sorterad rak lista av tal (kan vara tom). Skriv en funktion insert! som givet ett index i tabellen lägger in ett nytt tal i sorterad ordning på listan. Elementet skall destruktivt med strukturändrande funktioner läggas in på rätt plats. Inga onödiga cons-celler får användas. Exempel Antag vi har en tabell kallad *tab*. 5 0 1 2 4 5 5 10 Om vi nu utför (insert! *tab* 0 7) ; lägg in talet 7 på sorterad plats i den lista som finns i index 0 (insert! *tab* 2 15) ; lägg in talet 15 på sorterad plats i den lista som finns i index 2 ; som från början är tom och nu innehåller bara detta element erhåller vi följande struktur: 0 1 2 5 7 10 4 15 5 nya element
Uppgift 5. Övriga konstruktioner (5 poäng) (5p) Makrofunktioner. Vi önskar en repetitionsstruktur 6 (för styrvariabel start-uttryck slut-uttryck (sats 1... sats k )) där först start-uttryck och slut-uttryck skall beräkna och anger gränserna för styrvariabeln. Styrvariabeln skall först bindas till startvärdet, och sedan i varje varv uppdateras med 1, tills det blir större än slutvärdet. I varje varv utförs varje sats i. Funktionen för returnerar inget intressant värde, t ex nil. I nedanstående exempel introduceras två lokala variabler, sedan görs repetitionssatsen som skriver ut värdet på styrevariabeln och i en lista lägger in detta värde, som sedan returneras från hela let-uttrycket, (let ((n ) (res ())) (för i n (+ n ) ((print i) (setq res (cons i res)))) res) 4 5 6 => (6 5 4 ) ; dvs 4 repetitioner, där styrvariabel i ; går från till och med 6
7 Uppgift 6. Abstraktion (15 poäng) I denna uppgift skall ni lösa uppgifter genom användnigen av abstraktion. Vi önskar först en funktion log-value som tar ett logiskt uttryck och en värdetabell och sedan beräknar dess sanningsvärde. I stort samma som ni gjorde på laboration 2 Logikvärde. I sista uppgiften gäller det att förenkla logiska uttryck. Det logiska uttrycket skall kunna representeras på olika sätt. Själva evaluatorn skall skrivas abstrakt och göras oberoende av hur det logiska uttrycket representeras. Även värdetabellen skall vara abstrakt skriven så att vi kan ha olika representationer för den. Det logiska uttrycket definieras som: logisk konstant för det sanna respektive falska värdet logisk variabel uttryck med icke, och samt eller Du skall även i uppgifterna 6b och 6c definiera primitiver för två helt olika representationer för att på så sätt visa att evaluatorn är representationsoberoende. Läs igenom hela uppgiften innan du börjar! I uppgifterna behövs ej kod ges för alla funktionerna. Är funktioner av liknande art går det att skriva På samma sätt definieras.... Beskriv hur du tänkter dig uppdelningen mellan den abstrakta nivån och representationen. 6a. (4p) Beskriv en abstrakt datatyp för logikuttryck och variabeltabell. Bestäm en uppsättning primitiva funktioner (constructors, selectors, recognizers), dvs abstrakta funktioner. Definiera sedan själva evaluatorn med huvudfunktionen log-value samt eventuella hjälpfunktioner, t.ex. för att utföra de logiska operationerna och för värdetabellen som du använder med användning av dessa primitiva funktioner. Denna del skall vara oberoende av den valda representationen i uppgift 6b resp. 6c. Sedan skall de primitiva funktionerna definieras i följande 2 uppgifter på två helt olika sätt. 6b. (p) Representation 1: Definiera de primitiver du använde i uppgift 6a. Ett logiskt uttryck representeras som infix-uttryck i en lista med sant och falskt som symbolerna sann och falsk och de logiska operatorerna som icke, och samt eller. Värdetabell lagras som en associations-lista med par av en logisk variabel och dess sanningsvärde. Det var denna representation som använder i laboration 2. Exempel med den givna representationen: (log-value sann ()) => sann (log-value (icke a) ((a. sann))) => falsk (log-value (sann och (sann eller falsk)) ()) => sann (log-value ((icke (a och falsk)) eller (b och sann)) ((a. sann) (b. falsk))) => sann fortsättning nästa sida...
8 6c. (p) Representation 2: Definiera de primitiver du använde i uppgift 6a. Representationen är samma som Lisps syntax, dvs vi använder t och nil som sant resp. falskt och skriver uttrycket i prefix-notation. Värdetabellen skall nu vara en arraystruktur vars element är poster med två element, en logisk variabel och dess sanningsvärde. Posttypen skall vara binding och fältnamnen i posten är variable och value. Exempel med den givna representationen: (log-value t #()) ; värdetabellen är tom, dvs en tom arraystruktur. (log-value (not a) #(#S(binding :variable a :value t))) => nil ; värdetabellen är en arraystruktur med en post där a har värdet t (log-value (and t (or t nil)) #()) (log-value (or (not (and a nil)) (and b t)) #(#S(binding :variable a :value t) #S(binding :variable b :value nil))) 6d. (5p) Man vill kunna optimera logiska uttryck med variabler. Uttryck med enbart konstanter beräknas och uttryck med ett konstant sanningsvärde förenklas. Definiera en funktion simplify som tar ett logiskt uttryck och förenklar det. Grundalgoritmen skall fortfarande vara abstrakt och därmed kunna klara båda de ovan beskrivna representationerna. Inför ytterligare primitiver om du behöver. Du behöver inte skriva om kod utan kan basera så mycket som möjligt på det du gjort i de tidigare uppgifterna. Exempel med representation 1 och utan symboltabell: (simplify (x eller y)) => (x eller y) ; ingen förenkling möjlig (simplify (sann eller falsk)) => sann (simplify (sann och x)) => x (simplify ((icke sann) eller (x och y))) => (x och y) (simplify (sann och (x eller falsk)) => x, dvs värdet av x kommer att avgöra sanningsvärdet av hela uttrycket. (simplify ((x och y) och (icke sann))) => falsk