TDDC74 Lab 02 – Listor 1 Översikt I denna laboration kommer ni att lära er mer om: • Mer komplexa rekursiva mönster, procedurer och processer. • Hur man kan hantera listor och andra enklare länkade strukturer. • Enklare testning mot referensimplementation. • En klassisk sorteringsalgoritm. 2 Värt att veta Denna laboration består av en del med 14 fristående uppgifter. Alla problem måste lösas på ett tillfredsställande sätt för att laborationen ska bli godkänd. Utöver informationen i föregående laboration gäller att • Laborationen berör kapitel 2 i kurslitteraturen. För mer läsning se även SICP+02 som finns på laborationssidan. • Många av uppgifterna har ekvivalenta primitiver i Racket. Nyttja dessa för att kontrollera era lösningar. • Ni ska kalla filen ni lämnar in för la02.rkt (kan man skönja ett mönster?). 1 3 Uppgifter, laboration 2A Uppgift 1 ----------------------------------------------------------------------Vi skriver följande definitioner i DrRacket. Vad för strukturer bildas? Förklara skillnaden mellan nedanstående definitioner för er själva. Det förväntas att dessa skillnader är självklara vid problem 10. (define (define (define (define foo (cons 2 bar (list 2 forex (cons tesco (list 3)) 3)) 2 (cons 3 4))) 2 bar)) Testa följande för egen del. (car (car (cdr (cdr (car (car foo) bar) foo) bar) (cdr (cdr −→ ??? −→ ??? −→ ??? −→ ??? forex)) −→ ??? tesco)) −→ ??? Till denna uppgift ska ni redovisa en skiss på hur parstrukturerna foo, bar forex, tesco ser ut (med box-pointer-diagram). Ni ska beskriva hur det ser ut efter att alla fyra define-raderna evaluerats. Rita inga nya strukturer, om det inte uttryckligen skapas några nya strukturer! Ni kan redovisa detta på papper vid labbtillfället, inlämnat i labbomslag, eller som scannad bildfil. Skrivarna vid labbsalarna har gratis scanningfunktioner. Uppgift 2 ----------------------------------------------------------------------Det är ofta användbart att kunna kontrollera om något är ett atomiskt värde, data som inte är sammansatta på något vis. I våra fall, där vi bygger listor och träd med cons, räknar vi en atom som det som varken är ett par eller den tomma listan. Implementera predikatet atom? som testar om något är ett atomiskt värde. (atom? 1) −→ #t (atom? ’a) −→ #t 2 (atom? (null? (atom? (atom? ’(1 2 3)) −→ #f ’()) −→ #t ’()) −→ #f ’(2 . 3)) −→ #f Uppgift 3 ----------------------------------------------------------------------Del 3A Som uppvärmning skapar vi en funktion som räknar elementen i en lista. Implementera funktionen count-list som gör detta. (count-list ’(1 4 9 16)) −→ 4 (count-list ’(1 (inner 3) 4)) −→ 3 Notera att vi i denna uppgift bara räknar antalet element i ”huvudlistan”. Vi går aldrig in i underlistor och räknar element där. Del 3B Ni kan testa er funktion genom att jämföra resultatet mot length med samma argument. Om er funktion är korrekt skriven, ska den och length alltid ge samma svar. Skriv ett predikat count-list-correct? som tar en lista och testar om de ger samma svar.1 Bifoga ett par representativa körexempel. Uppgift 4 ----------------------------------------------------------------------Implementera en funktion keep-if som tar ett predikat och en lista som inargument och returnerar en ny lista endast innehållande de element i listan som uppfyllde predikatet. Ni behöver inte undersöka listor-i-listor. (keep-if even? ’(1 2 3 4)) −→ (2 4) (keep-if (lambda (obj) (not (number? obj))) ’(one 2 three 4)) −→ (one three) (keep-if (lambda (obj) (and (list? obj) (= (length obj) 2))) ’(1 2 (1 2 3) (a b) (c d) x)) −→ ((a b) (c d)) 1 length används här som en referensimplementation. Det är inte ett ord som ni behöver kunna till tentan, men det är relevant i utveckling. I denna uppgift jämför vi funktioner som är i stort sett likvärdiga, vilket kan verka meningslöst. Tänk isåfall på fallet där vi har en gammal, långsam men fungerande algoritm och försöker utveckla en ny. Den gamla kan tjäna som referens. 3 Implementera även keep-if-correct? som tar ett predikat, en lista, och jämför resultatet med utdata från er funktion, med det från filter. Uppgift 5 ----------------------------------------------------------------------Del 5A Skapa en funktion som returnerar de n första elementen i en lista. Om listan är kortare än n ska hela listan returneras. (first-n 3 ’(1 4 9 16 25)) −→ (1 4 9) (first-n 1 ’(1 4 9 16 25)) −→ (1) (first-n 7 ’(1 4 9 16 25)) −→ (1 4 9 16 25) (first-n 2 ’(1 (4 9) 16 25)) −→ (1 (4 9)) (first-n 2 ’(one (four (nine sixteen)) twentyfive)) −→ (one (four (nine sixteen))) (first-n 4 (build-list 100000 values)) −→ (0 1 2 3) OBS! Argumentordning. Del 5B I Racket finns funktionen take, som tar de n första elementen ur en lista. (take ’(a b c d e f) 3) −→ (a b c) Pröva ett par exempel, och undersök om take och first-n skiljer sig åt på något sätt. Implementera first-n-correct? som tar ett antal element, en lista och kontrollerar om first-n ger några felaktiga svar. Se till att first-n-correct? ger korrekta svar för alla möjliga indata (antal och lista). Bifoga ett par körexempel. Uppgift 6 ----------------------------------------------------------------------Definiera funktionen enumerate som returnerar en lista med tal från from till to med avstånd step, inklusive gränserna. Indata är alltid heltal. (enumerate (enumerate (enumerate (enumerate 1 6 1) −→ (1 2 3 4 5 6) 1 20 5) −→ (1 6 11 16) -10 -2 2) −→ (-10 -8 -6 -4 -2) 5 1 19) −→ () 4 Uppgift 7 ----------------------------------------------------------------------Att kunna vända en lista är en rätt grundläggande uppgift2 . Här ska vi implementera funktioner som skapar en kopia av listan, men i omvänd ordning. Del 7A Implementera reverse-order-rek som en linjärt rekursiv funktion som tar en lista, och ger en version av listan i omvänd ordning. Ledning: det kan underlätta om ni implementerar en procedur som fungerar ungefär som append. Se kursboken sid 102-103. (reverse-order-rek ’(1 2 3 4)) −→ ’(4 3 2 1) (reverse-order-rek ’((1 2) (3 4))) −→ ’((3 4) (1 2)) Notera att underlistorna som dök upp som element är helt oförändrade (så (1 2) har inte vänts). Del 7B Ni ska även implementera reverse-order-iter. Den ska ha samma funktionalitet som reverse-order-rek, men vara iterativt rekursiv. Ni kan testa era implementationer genom att jämföra med reverse. (reverse-order-iter ’(1 2 3 4)) −→ ’(4 3 2 1) Ni behöver inte skriva ett predikat som testar dessa funktioner. Uppgift 8 ----------------------------------------------------------------------En klassisk byggsten inom funktionell programmering är map, som tar en funktion, något slags datastruktur, applicerar (”kör”) funktionen på varje element, och samlar ihop resultatet. I Racket kan man ta man en funktion (”dubblera ett tal”) och en lista med värden (siffror), och ger tillbaka en lista med dubblerade tal.3 Implementera en egen map-to-each-funktion. Den ska ta två argument: en procedur och en lista. Utdata ska vara resultatet av att funktionen applicerats på vart och ett av elementen i listan (för sig). (map-to-each sqrt ’(1 4 9 16 25)) −→ (1 2 3 4 5) I era testfall kan ni jämföra med Rackets inbyggda map. 2 Och övning i att hantera parstrukturer. I matematisk mening ger man bilden av listan under dubblerings-funktionen. Ordet map är just matematikens avbildning. 3 5 Uppgift 9 ----------------------------------------------------------------------Att en lista är sorterad är ofta avgörande för att kunna göra effektiva sökningar4 , eller för att vissa algoritmer ska kunna köras. I denna uppgift ska ni implementera funktionen insert-sort. Listan ska sorteras i stigande ordning med hjälp av insättningssortering5 . Ni behöver bara hantera listor som innehåller siffror. Ni kan jämföra utdata med (sort listan <). Ni behöver inte skriva ett predikat som testar detta. Det rekommenderas starkt att ni först skapar en procedur som tar ett element och en redan sorterad lista, och placerar in elementet på rätt plats i listan. Därefter kan ni bygga vidare på detta tills ni får en färdig insertion sort. (insert-at-asc-place 2 ’(1 4 7)) −→ (1 2 4 7) (insert-sort ’(2 7 1 4)) −→ (1 2 4 7) Uppgift 10 --------------------------------------------------------------------I detta språk är det vanligt förkommande att listor innehåller listor eller par6 . I Racket är listor bara parstrukturer. Listan (1 2) är samma sak som (1 . (2 . () )). I denna uppgift ska ni implementera count-all. count-all ska fungera som tidigare, men dessutom klara av att hantera listor-i-listor, par-i-listor, och andra typer av parstrukturer: (count ’(1 (count-all (count-all (count-all (two 3 4) 5)) −→ 3 ’(1 (two 3 4) 5)) −→ 5 (cons 1 2)) −→ 2 1) −→ 1 Vilka intressanta fall kan inträffa? Försök att göra kod med så få specialfall/villkor i cond som möjligt. 4 Överkurs (ej krav): Till exempel en binärsökning. Där letar man efter ett element genom att successivt dela listan på mitten fler och fler gånger. Om elementet vi letar efter är större än mittelementet, letar vi vidare i höger halva av listan. Eftersom listan är sorterad, vet vi att det måste finnas där, om det alls finns. Binärsökning i listor är dock inte effektivt att använda i Racket. Fundera gärna på varför. 5 http://en.wikipedia.org/wiki/Insert_sort 6 Till exempel kan man lagra (x,y)-koordinater i listor bestående av par, ( (x0 . y0 ) (x1 . y1 ) ...) 6 Uppgift 11 --------------------------------------------------------------------Att kunna bestämma om ett element förekommer i en parstruktur är en grundläggande operation. Skapa ett predikat, occurs? som returnar #t om dess första argument återfinns i en viss lista/parstruktur. Även jämförelser mellan rena atomer ska fungera. (occurs? (occurs? (occurs? (occurs? ’c ’c ’b ’x ’c) ’(a ’(a ’(a −→ #t (b . c) d (e . (f . g)))) −→ #t (b . c) d (e . (f . g)))) −→ #t (b . c) d (e . (f . g)))) −→ #f Jämför din lösning i count-all och den i occurs? Vad är gemensamt? Vad skiljer? Uppgift 12 --------------------------------------------------------------------Det är även bra att kunna byta ut element mot andra7 . Skapa subst-all som gör detta. Använd eq?8 för att jämföra om två objekt är samma. Implementera subst-all enligt nedan: (subst-all 3 ’number ’((Johannes 0 1 3 1 4 2 2 3 1) (Jalal 0 1 3 2 8 1 9 6 3) (Tjabo 0 8 5 8 6 4 3 6) (Zlatan 0 4 0 9 5 2 8 2 6))) −→ ’((Johannes 0 1 number 1 4 2 2 number 1) (Jalal 0 1 number 2 8 1 9 6 number) (Tjabo 0 8 5 8 6 4 number 6) (Zlatan 0 4 0 9 5 2 8 2 6)) Uppgift 13 --------------------------------------------------------------------Ett lite mer komplicerat exempel är keep-if-all. Denna ska behålla alla atomer som uppfyller predikatet. Om ett element som ska tas bort finns i en lista-i-listan spelar ingen roll. Fundera gärna på varför du inte kan använda mönstret från tidigare uppgifter rakt av. 7 8 Tänk er en värld utan ”find and replace”. http://docs.racket-lang.org/reference/eval-model.html#(part._model-eq) 7 (define (not-num? o) (not (number? o))) ;; o är ej ett tal (keep-if-all not-num? ’(one 2 three)) −→ (one three) (keep-if-all not-num? ’(one (2 three) 4) −→ (one (three)) Eftersom det kan förekomma par-i-listor och annat, måste funktionen dessutom klara att hantera fall som dessa (keep-if-all (keep-if-all (keep-if-all (keep-if-all not-num? not-num? not-num? not-num? ’(1 . 2)) −→ () ’(one (1 . three) (3 4))) −→ (one three ()) ’(one (one . 3) (3 4))) −→ (one (one) ()) 1) −→ () Som vanligt bygger vi upp nya strukturer, snarare än att ta bort i gamla. Fundera därför på vad resultatet borde vara när man stöter på olika slags data. Rita förslagsvis upp strukturerna ovan med box-pointer-diagram, eller skissa dem som träd. Uppgift 14 --------------------------------------------------------------------Implementera predikatet list-equal? som jämför två godtyckliga listor. De kan potentiellt sett innehålla listor-i-listor, och andra strukturer. Ni får enbart gå igenom listan en gång. Ni kan alltså inte (till exempel) jämföra längden på dem. Listorna kan innehålla godtycklig data, och ni ska använda eqv? för att jämföra element9 . För att kontrollera om er lösning är korrekt, jämför ni förslagsvis med equal?. (list-equal? (list-equal? (list-equal? (list-equal? (list-equal? 9 ’(1 2 ’(1 2 ’(1 2 ’(one ’(1 2 3 4) ’(1 2 3 4)) −→ #t 3 4) ’(1 2 3 . 4)) −→ #f 3 4) ’((1 2) 3 4)) −→ #f (2 (3 three)) 4) ’(one (2 (3 three)) 4)) −→ #t 3 4) ’(1 2 3)) −→ #f http://docs.racket-lang.org/reference/booleans.html 8 4 Inlämning 2 Även för denna laboration finns en färdig uppsättning första tester att köra. Se till att autotest-la02.rkt finns i samma katalog som filen ni skriver i, och skriv följande kod i slutet av er fil i Dr. Racket: (provide (all-defined-out)) Kör sedan autotest-la02.rkt. Får ni ett fel så läs vad felet säger! Om ni inte definierat era procedurer när ni kör koden kommer det bli omkring 30 fel. Det kan även vara intressant att ta en titt på hur autotest-filen ser ut inuti. 9 5 Appendix A Nya tillåtna primitiver: car list eqv? 6 cdr require let* cons null? eq? pair? Appendix B Tillåtet enbart i testfunktionerna: length take filter sort map equal? 10 list? symbol?