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