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?