Beispiellösung zum Übungsblatt aus Wiederholungstutorium 4

Werbung
Fachrichtung 6.2 — Informatik
Universität des Saarlandes
Tutorenteam der Vorlesung Programmierung 1
Programmierung 1 (Wintersemester 2015/16)
Wiederholungstutorium Lösungsblatt 4
(Sortieren und Konstruktoren)
Hinweis: Dieses Übungsblatt enthält von den Tutoren für das Wiederholungstutorium erstellte Aufgaben.
Die Aufgaben und die damit abgedeckten Themenbereiche sind für die Klausur weder relevant noch irrelevant.
1
Vergleichsprozeduren
Aufgabe 4.1 (Case-Käse)
Schreiben Sie eine Prozedur kaese: int → string, die zu einer gegebenen Zahl eine Zeichenkette nach
folgenden Regeln liefert.
kaese 0 = " null "
kaese 1 = " eins "
kaese 2 = " zwei "
Für alle anderen Zahlen soll kaese die Zeichenkette ’’kaese’’ liefern. Schreiben Sie kaese mit einem case
Ausdruck.
Lösung 4.1:
fun kaese x = case x of
0 ⇒ " null "
| 1 ⇒ " eins "
| 2 ⇒ " zwei "
| _ ⇒ " kaese "
Aufgabe 4.2
Betrachten Sie folgende case-Struktur:
case xs of
nil ⇒ 0
| [( x , y )] ⇒ x+y
| [( x ,5) , (7 , y )] ⇒ x * y
| ( _ :: _ :: xr ) ⇒ ∼1
Welche der Muster der 4 Regeln des case-Ausdrucks treffen den Wert [(2, 5), (7, 3)]? An welche Werte werden
die Variablen der Muster dabei gebunden?
Aufgabe 4.3 (Lexikalische Ordnung für Listen)
Schreiben Sie eine polymorphe Prozedur listcmp: (α * α → order) → α list * α list → order, die
zwei Listen gemäß einer Vergleichsprozedur lexikalisch vergleicht.
Lösung 4.3:
fun
|
|
|
listcmp
listcmp
listcmp
listcmp
cmp
cmp
cmp
cmp
( nil , nil ) = EQUAL
( xs , nil ) = GREATER
( nil , ys ) = LESS
( x :: xr , y :: yr ) = case cmp (x , y ) of
EQUAL ⇒ listcmp cmp ( xr , yr )
1
|
s
⇒ s
Aufgabe 4.4 (Zeichenketten vergleichen)
Schreiben Sie eine Vergleichsprozedur strcmp: string * string → order, die zwei Zeichenketten gemäß der
lexikalischen Ordnung vergleicht. Gehen Sie dazu wie folgt vor.
(a) Schreiben Sie eine Vergleichsprozedur charcmp: char * char → order, die zwei Zeichen gemäß ihrer
Ordnungszahlen vergleicht.
(b) Schreiben Sie strcmp unter Verwendung von charcmp und listcmp aus der vorherigen Aufgabe.
Lösung 4.4:
(a) fun charcmp (x , y ) = Int . compare ( ord x , ord y )
(b) fun strcmp (x , y ) = listcmp charcmp ( explode x , explode y )
Aufgabe 4.5 (Member)
Schreiben Sie eine polymorphe Prozedur member: (α * α → order) → α → α list → bool, die testet,
ob ein gegebener Wert in einer Liste vorkommt.
Lösung 4.5:
fun member cmp x nil
= false
| member cmp x ( y :: yr ) = case cmp (x , y ) of
EQUAL ⇒ true
|
s
⇒ member cmp x yr
2
Sortieren
Aufgabe 4.6 (Sortieren durch Einfügen)
Verdeutlichen Sie sich den Algorithmus von Sortieren durch Einfügen und lösen Sie folgende Aufgaben.
(a) Muss der Algorithmus für alle Argumente gleich viel Arbeit leisten?
(b) Wenn nicht, wann ist der Algorithmus am effizientesten/ineffizientesten? Geben Sie Beispiele für Listen
der Länge 5 an, die aufsteigend sortiert werden sollen.
(c) Implementieren Sie Sortieren durch Einfügen durch eine Prozedur isort: int list → int list, die
eine Liste aus ganzen Zahlen strikt aufsteigend sortiert.
(d) Implementieren Sie nun den Algorithmus polymorph unter Verwendung einer an pisort übergebenen
Vergleichsprozedur cmp. Dabei soll pisort das Typschema (α * α → order) → α list → α list
besitzen und beliebige Listen aufsteigend sortieren.
(e) Verdeutlichen Sie sich, wie die bereits fertige polymorphe Prozedur pisort jetzt verwendet werden kann,
um eine andere Sortierprozedur (z.B. absteigend sortierend) zu erhalten.
(f) Schreiben Sie eine Prozedur downsort: int list → int list, die Listen von ganzen Zahlen absteigend
sortiert. Verwenden Sie pisort.
Lösung 4.6:
(a) Nein, da in jedem Schritt in die bereits sortierte Teilliste von der zu sortierenden Liste die restlichen
Elemente einsortiert werden, kommt es darauf an, an welcher Stelle diese einzufügen sind.
2
(b) sort [1,2,3,4,5] ist am ineffizientesten, da das einzusortierende Element in jedem Schritt am Ende der
sortierten Teilliste einzufügen ist. Dahingegen ist sort [5,4,3,2,1] am effizientesten, da immer vorne
eingefügt wird.
(c) fun sort xs = let
fun insert (x , nil )
= [x]
| insert (x , y :: yr ) = if x<y then x :: y :: yr
else if x=y then y :: yr
else y :: insert (x , yr )
in foldl insert nil xs end
(d) fun sort cmp xs = let
fun insert (x , nil )
= [x]
| insert (x , y :: yr ) = case cmp (x , y ) of
GREATER ⇒ y :: insert (x , yr )
|
_
⇒ x :: y :: yr
in foldl insert nil xs end
(e) Die übergebene Vergleichsprozedur entscheidet darüber, wie die Elemente sortiert werden.
(f) fun mycmp (x , y ) = if x<y then GREATER
else if x>y then LESS
else EQUAL
val downsort = sort mycmp
Aufgabe 4.7 (Sortieren durch Mischen)
Schreiben Sie eine polymorphe Sortierprozedur pmsort: (α * α → order) → α list → α list, die durch
Mischen sortiert.
Lösung 4.7:
fun pmsort cmp nil = nil
| pmsort cmp [ x ] = [ x ]
| pmsort cmp xs = let
fun split xs = foldl ( fn (x ,( us , vs )) ⇒ ( x :: vs , us )) ( nil , nil ) xs
fun merge xs nil = xs
| merge nil ys = ys
| merge ( x :: xr ) ( y :: yr ) = case cmp (x , y ) of
GREATER ⇒ y :: merge ( x :: xr ) yr
| EQUAL ⇒ x :: y :: merge xr yr
| LESS ⇒ x :: merge xr ( y :: yr )
val ( us , vs ) = split xs
in merge ( pmsort cmp us ) ( pmsort cmp vs ) end
Aufgabe 4.8 (Für fleißige Meistersortierer oder zum überspringen)
(a) Schreiben Sie eine Prozedur
partition : int → int list → int list * int list
die zu einer Zahl x und einer Liste xs zwei Listen us und vs wie folgt liefert:
1. sort(us@vs) = sort xs
2. Alle Elemente von us sind kleiner als x und alle Elemente von vs sind größer oder gleich x.
Schreiben Sie partition mit foldl.
(b) Schreiben Sie eine Prozedur qsort: int list → int list, die Listen über int gemäß dem Algorithmus
Quicksort sortiert.
3
Quicksort sortiert Listen gemäß der Prozedur partition, die eine Liste in zwei Teillisten zerlegt und ihre
durch Rekursion berechnete Sortierung mit Konkatenation wieder zusammenfügt:
qsort(x :: xr) = (qsort us) @ [x] @ (qsort vs)
(c) Schreiben Sie eine Prozedur
ppartition : (α * α → order ) → α list → α list * α list * α list
die eine Liste xs gemäß einer Vergleichsprozedur f in drei Listen us, vs, ws zerlegt, sodass der Vergleich f
für die Elemente von us LESS, von vs EQU AL und von ws GREAT ER liefert.
(d) Knifflig! Implementieren Sie Quicksort mithilfe von ppartition polymorph.
Lösung 4.8:
(a) fun partition r xs = let
fun spread (x ,( us , vs )) = if x<r then ( x :: us , vs )
else ( us , x :: vs )
in foldl spread ( nil , nil ) xs end
(b) fun qsort nil = nil
| qsort ( x :: xr ) = let val ( us , vs ) = partition x xr
in qsort us @ [ x ] @ qsort vs end
(c) fun ppartition cmp r xs = let
fun spread (x ,( us , vs , ws )) = case cmp (x , r )
GREATER ⇒
| EQUAL
⇒
| LESS
⇒
in foldl spread ( nil , nil , nil ) xs end
(d) fun pqsort cmp nil = nil
| pqsort cmp ( x :: xr ) =
let
val ( us , vs , ws ) = ppartition cmp x xr
in
pqsort cmp us @ [ x ] @ pqsort cmp vs @ pqsort cmp ws
end
4
of
( us , vs , x :: ws )
( us , x :: vs , ws )
( x :: us , vs , ws )
3
Konstruktoren
Aufgabe 4.9 (Osterschmuck mit Konstruktoren)
Ihr bester Freund Dieter Schlau bereitet sich schon früh auf die Osterzeit vor. Er möchte bereits jetzt einen
Osterstrauch festlich schmücken und plant dafür erstmal in SML, wie er dies realisieren könnte.
Seien die Datentypen f arbe und strauch wie folgt gegeben:
datatype farbe = GELB | WEISS | GRUEN
datatype strauch = S of int
Dabei zeigt f arbe, welche Farbe der Osterschmuck haben kann, mit dem man den Osterstrauch schmücken kann.
Die ganze Zahl von strauch gibt an, wie viel Osterschmuck noch an den Strauch passt. Ignorieren Sie, dass man
den Osterschmuck einfach etwas enger an den Osterstrauch hängen könnte.
(a) Deklarieren Sie einen Datentyp schmuck, der den vorhandenen Osterschmuck darstellt. Osterschmuck
besteht aus Ostereiern und kleinen Stoffschäfchen. Ostereier können alle gegebenen Farben haben. Für
Stoffschäfchen braucht es keine Farbangabe, da diese immer weiß sind.
(b) Deklarieren Sie eine Prozedur schaefchen: strauch → strauch ∗ schmuck list, die einen Osterstrauch
bekommt und eine Liste mit 8 Stoffschäfchen zurückgibt, zusammen mit den leer geblieben Ästen des
Strauches (d.h. wenn der Eingabestrauch für 10 Schäfchen freie Äste hat, soll der Rückgabestrauch 2 freie
Äste haben). Reicht der Strauch nicht mehr für 8 Schäfchen, so sollen nur so viele Schäfchen zurückgegeben
werden wie Äste da sind, zusammen mit dem jetzt aufgebrauchten Platz, also S 0.
(c) Wir können das Schmücken der Strauchs durch Prozeduren vom Typ strauch → strauch ∗ schmuck
darstellen. Hier soll der zurückgegebene Strauch für ein Schmuckstück weniger reichen als der Eingabestrauch.
Falls der Eingabestrauch nicht mehr genug Platz für weiteren Schmuck hat, soll eine NotEnoughBoughs
Exception geworfen werden. Definieren Sie einen Typ anhaengen, der das Schmücken des Strauches auf
diese Weise darstellt.
Hinweis: Sie sollen hier das Schlüsselwort type verwenden, nicht datatype.
(d) Deklarieren Sie eine Prozedur verwandle : f arbe → anhaengen, die zu einer gegebenen Farbe eine Prozedur
zurückgibt, die Ostereier der Farbe an einen Strauch hängt.
(e) Deklarieren Sie eine Prozedur schmuecken : strauch → f arbe option → strauch ∗ schmuck list, die ein
Osterei der entsprechenden Farbe an den Strauch hängt. Falls die übergebene Farbe N ON E ist, sollen bis
zu 8 Stoffschäfchen an den Strauch gehängt werden. Sollten während des Schmückens die Äste ausgehen, (es
werden z.B. nur 5 Schäfchen an den Strauch gehangen), so soll die NotEnoughBoughs Exception geworfen
werden. Bleiben Äste übrig, so soll diese zurückgegeben werden.
(f) Und nun zum krönenden Abschluss: Deklarieren Sie eine Prozedur schmueckAutonomous : strauch →
f arbe option list → strauch ∗ schmuck list die (unter Verwendung von schmuecken) entsprechend der
f arbe option list den Strauch schmückt und zurückgibt. Falls während des Schmückens die Äste ausgehen,
sollen automatisch neue Äste hinzugestellt werden. Mit val newboughs = S 20 können immer neue Äste
hinzugestellt werden. Es dürfen maximal 7 Äste weggeworfen werden, d.h. Sie dürfen sich nicht einfach für
jeden Schmuckvorgang neue Äste nehmen.
Lösung 4.9:
(a) datatype schmuck = EIER of farbe | STOFFSCHAEFCHEN
(b) fun schaefchen ( S x ) =
iter ( Int . min (x ,8)) (( S x ) , nil ) ( fn (( S x ) , zs ) ⇒ ( S ( x−1) , STOFFSCHAEFCHEN :: zs ))
(c) exception NotEnoughBoughs
type anhaengen = strauch → strauch * schmuck
(d) fun verwandle f ( S x ) = if x=0 then raise NotEnoughBoughs
else ( S ( x−1) , EIER f )
(e) fun schmuecken ( S x ) NONE = if x < 8 then raise NotEnoughBoughs
else schaefchen ( S x )
| schmuecken t ( SOME f ) = let val (a , b )=verwandle f t
5
in (a ,[ b ]) end
(f) fun sc hm ue ck Au to no mo us t nil
= (t , nil )
| sc hm ue ck Au to no mo us t ( f :: fs ) = let
val (a , b ) = schmuecken t f
handle NotEnoughBoughs ⇒ sc hm ue ck Au to no mo us newboughs ( f :: fs )
val (c , d ) = s chm ue ck Au to no mo us a fs
in (c , b@d ) end
Aufgabe 4.10 (Natürliche Zahlen)
Wir stellen die natürlichen Zahlen dar mit den Werten folgendes Konstruktortyps:
datatype nat = O | S of nat
So ergibt sich z.B. 0 7→ O, 1 7→ SO, 2 7→ S(SO), 3 7→ S(S(SO)).
(a) Deklarieren Sie eine Prozedur code: int → nat, die die Darstellung einer natürlichen Zahl liefert und
eine Prozedur decode: nat → int, sodass decode (code n) = n für alle natürlichen Zahlen gilt.
(b) Deklarieren Sie Prozeduren nat → nat → nat für folgende Berechnungen, ohne dabei Operationen für
int zu verwenden:
(i) Addition m + n.
(ii) Multiplikation m · n.
(iii) Potenzierung mn .
(iv) Kleiner-als m < n.
Lösung 4.10:
(a) fun
|
fun
|
(b)
code 0 = O
code n = S ( code ( n−1))
decode
O
= 0
decode ( S x ) = 1 + decode x
(i) fun natadd O n = n
| natadd m O = m
| natadd ( S m ) ( S n ) = S ( natadd m ( S n ))
(ii) fun natmul O n = O
| natmul m O = O
| natmul ( S m ) ( S n ) = natadd ( S m ) ( natmul ( S m ) n )
(iii) fun natpot O n = O
| natpot m O = S O
| natpot ( S m ) ( S n ) = natmul ( S m ) ( natpot ( S m ) n )
(iv) fun
|
|
|
natleq
natleq
natleq
natleq
O O =
O n =
m O =
(S m)
false
true
false
( S n ) = natleq m n
Aufgabe 4.11 (Ziffern)
Betrachten Sie folgenden Konstruktortyp, der natürliche Zahlen durch einzelne Ziffern darstellt:
datatype digit = E of int | M of int * digit
So wird die Zahl 13 etwa durch M(1,E 3) dargestellt.
(a) Geben Sie die Konstruktordarstellung der Zahl 9042 an.
6
(b) Schreiben Sie eine Prozedur stell: digit → int zur Berechnung der Stelligkeit einer digit-Zahl.
(c) Schreiben Sie eine Prozedur rev: digit → digit, die eine digit-Zahl reversiert. Deklarieren Sie zunächst
eine endrekursive Hilfsprozedur rev’: digit → digit → digit.
(d) Knifflig! – Schreiben Sie eine Prozedur add: digit → digit → digit, die zwei digit-Zahlen addiert.
Verwenden Sie weder mod noch div. Orientieren Sie sich an dem schriftlichen Additionsverfahren, das
Ziffern einzeln von rechts nach links zusammenzählt.
Lösung 4.11:
(a) M(9, M(0, M(4, E 2)))
(b) fun stell ( E x ) = 1
| stell ( M (x , m )) = 1 + stell m
(c) fun rev ( E z ) = ( E z )
| rev ( M (z , e )) = let fun rev ’ a ( E z ’) = M (z ’ , a )
| rev ’ a ( M (z ’ , d )) = rev ’ ( M (z ’ , a )) d
in rev ’( E z ) e end
(d) fun add d1 d2 = let
fun add ’ ( E z1 ) ( E z2 ) = if z1+z2 < 10 then ( E ( z1+z2 ))
else ( M (( z1+z2 ) − 10 , E 1))
| add ’ ( M ( z1 , e )) ( E z2 ) = if z1+z2 < 10
then ( M (( z1+z2 ) , e ))
else ( M (( z1+z2 ) − 10 , add ’ e ( E 1)))
| add ’ ( E z1 ) ( M ( z2 , e )) = if z1+z2 < 10 then ( M (( z1+z2 ) , e ))
else ( M (( z1+z2 ) − 10 , add ’ e ( E 1)))
| add ’ ( M ( z1 , e )) ( M ( z2 , h )) = if z1+z2 < 10 then ( M (( z1+z2 ) , add ’ e h ))
else
( M (( z1+z2 ) − 10 , add ’ ( add ’ e ( E 1)) h ))
in rev ( add ’ ( rev d1 ) ( rev d2 )) end
Aufgabe 4.12 (Ausdrücke und Umgebungen)
Wir betrachten folgende Deklaration für den Konstruktortyp exp.
datatype exp = C of int | V of string | A of exp * exp | M of exp * exp
(a) Stellen Sie den Ausdruck (3x + 2)y durch die Werte des Konstruktortyps exp dar.
(b) Schreiben Sie eine Prozedur subexps: exp → exp list, die zu einem Ausdruck die Liste aller Teilausdrücke liefert.
(c) Schreiben Sie eine Prozedur eval: (string → int) → exp → int, die einen Ausdruck gegeben einer
Umgebung auswertet. Die Umgebung wird dabei durch eine Prozedur string → int realisiert, die
Bezeichner an einen Wert bindet.
Lösung 4.12:
(a) M(A(M(C 3,V "x"),C 2),V "y")
(b) fun subexps e = e ::( case e of
A (e ,e ’) ⇒ subexps e @ subexps e ’
| M (e ,e ’) ⇒ subexps e @ subexps e ’
|
_
⇒ nil )
(c) fun
|
|
|
eval
eval
eval
eval
env
env
env
env
( V x ) = env x
(C c) = c
( M (e ,e ’)) = eval env e * eval env e ’
( A (e ,e ’)) = eval env e + eval env e ’
7
4
Ausnahmen und Optionen
Aufgabe 4.13 (Inception)
Betrachten Sie folgendes Programm. Geben Sie das Typschema an. Zu was wertet die Prozedur aus?
fun inception x = if null x
then let exception Exception in Exception end
else let val deception = tl x
in inception ( deception ) end
Lösung 4.13:
Die Prozedur inception: α list → exn gibt immer die Ausnahme Exception zurück.
Aufgabe 4.14 (Someone?)
Sei exception E of int vordeklariert. Sind folgende Programme gültig? Zu was werten sie aus?
(a) ((fn x ⇒ E x) (1 div 0); NONE) handle E x ⇒ SOME x
(b) ((fn x ⇒ raise E x) 1; NONE) handle E x ⇒ SOME x
(c) ((fn E x ⇒ x | _ ⇒ 0) (E 1); NONE) handle E x ⇒ SOME x
(d) (let exception F in raise E 1; NONE end) handle E x ⇒ SOME x
Welches der Programme bietet uns eine praktikable, funktionale Struktur zur Verwendung von Ausnahmen?
Lösung 4.14:
(a) uncaught exception Div
(b) val it: SOME 1 – Praktikabel und funktional zum Werfen und Fangen von Ausnahmen.
(c) val it: NONE
(d) val it: SOME 1
Aufgabe 4.15 (Auf Listen)
(a) Schreiben Sie eine Prozedur last: α list → α option, die das letzte Element einer Liste liefert.
(b) Deklarieren Sie eine Prozedur length: α list → int, die die Länge einer Liste bestimmt. Verwenden
Sie keine Hilfsprozeduren außer hd und tl und verwenden Sie genau eine Regel für die Prozedur.
(c) Schreiben Sie eine Prozedur double: (α * α → order) → α list → α option, die eine Liste auf
Mehrfachauftreten überprüft und gegebenenfalls das mehrfach auftretende Element als Option zurückgibt.
(d) Schreiben Sie eine Prozedur find: (α * α → order) → α → α list → α option, die mithilfe einer
Vergleichsprozedur prüft, ob ein Element x in einer Liste vorkommt. Ist dies der Fall, soll SOM E x
zurückgegeben werden, andernfalls N ON E.
Lösung 4.15:
(a) fun last nil = NONE
| last [ x ] = SOME x
| last ( x :: xr ) = last xr
(b) fun length xs = 1 + length ( tl xs ) handle Empty ⇒ 0
(c) fun findDouble comp xs = let exception Double of α
fun comp ’ (x , y ) = case comp (x , y ) of
EQUAL ⇒ raise Double x
|
v
⇒ v
in ( List . sort comp ’ xs ; NONE )
handle Double x ⇒ SOME x
8
end
(d) fun find cmp x nil = NONE
| find cmp x ( y :: yr ) = case cmp (x , y ) of
EQUAL ⇒ SOME x
|
_
⇒ find cmp x yr
Aufgabe 4.16
(a) Schreiben Sie eine Prozedur optionizer: α option list → α list, welche eine option-Liste in eine
Liste übersetzt. Sobald jedoch ein N ON E in der Liste auftritt, wird die bereits übersetzte Liste verworfen
und ab dem N ON E eine neue Liste aufgebaut.
Zum Beispiel: optionizer[SOME a, NONE, SOME b] ; [b]
(b) Schreiben Sie eine Prozedur multioptionater: (int option * int option) list → int list, welche eine Liste aus int option-Tupeln bekommt. Diese sollen miteinander multipliziert werden und in
einer Liste ausgegeben werden. Befindet sich ein N ON E in einer Tupelkomponente, soll die Ausnahme
urgh geworfen werden.
Zum Beispiel: multioptionater[(SOME 4,SOME 0),(SOME 2,SOME 3),(SOME 1,SOME 4)] ; [0,6,4]
Lösung 4.16:
(a) fun optionizer xs = let
fun opt ’ nil ys = ys
| opt ’ ( SOME x :: xr ) ys = opt ’ xr ( x :: ys )
| opt ’ ( NONE :: xr ) ys = opt ’ xr nil
in opt ’ xs nil end
(b) exception urgh
fun multioptionater xs = map ( fn (x , y ) ⇒ case (x , y ) of
( SOME x , SOME y ) ⇒ x * y
| _ ⇒ raise urgh ) xs
9
Herunterladen