Fachrichtung 6.2 — Informatik Universität des Saarlandes Tutorenteam der Vorlesung Programmierung 1 Programmierung 1 (Wintersemester 2015/16) Lösungsblatt: Aufgaben für die Übungsgruppen: 14 (Kapitel 15 & 16) Hinweis: Dieses Übungsblatt enthält von den Tutoren für die Übungsgruppe erstellte Aufgaben. Die Aufgaben und die damit abgedeckten Themenbereiche sind für die Klausur weder relevant noch irrelevant. Reihungen Aufgabe 14.1 Schreiben Sie eine Prozedur swapPairwise: α array → unit, die in einer gegebenen Reihung die Komponenten vorne beginnend paarweise tauscht. Beispielsweise soll die Reihung [2, 3, 4, 5] durch diese Prozedur zu [3, 2, 5, 4] vertauscht werden. Bei einer ungeraden Anzahl an Komponenten darf die letzte Komponente unverändert bleiben. Lösung 14.1: fun swap a i j = Array . update (a , i , #1( Array . sub (a , j ) , Array . update (a , j , Array . sub (a , i )))) fun swapPairwise a = ( iter (( Array . length a ) div 2) 0 ( fn i ⇒ ( swap a i ( i + 1); i + 2)); ()) Aufgabe 14.2 (Sortieren durch Auswählen) Ein einfacher, als Sortieren durch Auswählen bezeichneter, Algorithmus geht beim Sortieren einer durch ihre linkeste und rechteste Position gegebenen nicht leeren Teilreihung (l, r) wie folgt vor: • Bestimme die Position m einer Komponente minimaler Größe. • Vertausche die Komponenten an den Positionen l und m. • Sortiere die Teilreihung (l + 1, r). Schreiben Sie eine Prozedur ssort: int array → unit, die Reihungen durch Auswählen sortiert. Bestimmen Sie die Laufzeit Ihrer Prozedur. Lösung 14.2: fun min a l r = if l=r then l else let val p = min a ( l+1) r in if Array . sub (a , p ) < Array . sub (a , l ) then p else l end fun swap a i j = Array . update (a , i , #1( Array . sub (a , j ) , Array . update (a , j , Array . sub (a , i )))) fun ssort ’ a l r = if l=r then () else ( 1 swap a l ( min a l r ); ssort ’ a ( l+1) r ) fun ssort a = ssort ’ a 0 ( Array . length a −1) Für l ≤ r hat die Prozedur min die Laufzeit O(r − l); swap hat konstante Laufzeit. Anwenden des Rekurrenzsatzes für polynomielle Rekurrenzen ergibt für ssort’ daher die Laufzeit O((r − l)2 ). Insbesondere hat ssort eine 2 quadratische Laufzeit O(|a| ), wobei |a| für die Länge einer Reihung a steht. Imperative Schlangen Aufgabe 14.3 Erweitern Sie imperative Schlangen um (a) eine Operation add: α queue → α → unit, die ein Element in konstanter Laufzeit vorn an eine Schlange anfügt. (b) eine Operation delete: α queue → unit, die das letzte Element einer Schlange in linearer Laufzeit entfernt. Wenn nötig, werfen Sie Empty. Lösung 14.3: structure Queue :> QUEUE = struct ... fun add (h , f ) x = let val neu = E (x ,! h ) in h := ref neu end fun delete (h , f ) = ( case !(! h ) of D ⇒ raise Empty | E (x , r ) ⇒ ( case ! r of D ⇒ let val d = ref D in ( h := d ; f := d ) end | E (x ’ ,r ’) ⇒ ( case !r ’ of D ⇒ let val d = ref D in ( r := d ; f := ref ( E (x , r ))) end | _ ⇒ delete ( ref r , f )))) end Aufgabe 14.4 Ergänzen Sie die Signatur und Implementierung der imperativen Schlangen aus dem Buch (S. 312) um eine Prozedur insertPosition: α queue → α → int → unit, die, gegeben eine Schlange, einen Wert x und eine Zahl n, einen neuen Eintrag mit Inhalt x an die n-te Position der Schlange einfügt. Für Position 0, soll das Element als Kopf der Schlange eingefügt werden, für Position 1 als zweites Element usw. Sollte die übergebene Position die Länge der Schlange überschreiten, soll der Eintrag wie gewohnt an das Ende der Schlange angefügt werden. Hinweis: Überlegen Sie sich zunächst eine sinnvolle Hilfsprozedur, die Sie innerhalb der Struktur deklarieren. Diese Prozedur soll es Ihnen ermöglichen, einen neuen Eintrag zwischen zwei bereits existierenden Einträgen der Schlange einzufügen. Lösung 14.4: fun insertPosition ’ (( h , f ): α queue ) x 0 ( pred ) = let val e = ref ( E (x ,! h )) in ( case !( pred ) of ( E (y , r )) ⇒ pred := ( E (y , e )) | D ⇒ h := e ) end | insertPosition ’ (h , f ) x n pred = 2 ( case !(! h ) of D ⇒ insert (h , f ) x | E (_ , r ) ⇒ insertPosition ’ ( ref r , f ) x ( n−1) (! h )) fun insertPosition q x n = insertPosition ’ q x n ( ref D ) Schleifen Aufgabe 14.5 (Kleine Gemeinheit) Was tut das folgende Programm? let val i = 0 val n = 1 val _ = while ( i < 10) do ( i = n + 1; n = n * i ) in n end Lösung 14.5: Es divergiert, da i < 10 immer wahr ist. Aufgabe 14.6 Nutzen Sie im Folgenden while-Schleifen. Verzichten Sie auf Rekursion. (a) Schreiben Sie eine Prozedur gauss: int → int, die die Summe aller Zahlen von 0 bis zu einer Eingabe n berechnet. (b) Schreiben Sie eine Prozedur fib: int → int, die die n-te Fibonacci-Zahl berechnet. (c) Schreiben Sie eine Prozedur prim: int → bool, die prüft, ob eine Eingabe n eine Primzahl ist. (d) Schreiben Sie eine Prozedur sum: int list → int, die die Zahlen einer Liste von Zahlen aufaddiert. (e) Schreiben Sie eine Prozedur foldli: (α * β → β) → β → α list → β, die Faltung von links implementiert. Lösung 14.6: (a) fun gauss n = let val sum = ref 0 val z = ref 0 in while ! z <= n do ( sum := ! sum + ! z ; z := ! z + 1); !z end (b) fun fib n = let val eins = ref 0 val zwei = ref 1 val buf = ref 0 val z = ref 1 in while ! z < n do ( buf := ! eins ; eins := ! zwei ; zwei := ! buf + ! zwei ; z := ! z + 1); ! eins end (c) fun prim n = let 3 val z = ref 2 val b = ref true in while ! z < if n else z := !b end n do ( mod ! z = 0 then b := false (); ! z + 1); (d) fun sum xs = let val x = ref 0 val xr = ref xs val sum = ref 0 in while not ( null (! xr )) do ( sum := ! sum + ! x ; x := hd (! xr ); xr := tl (! xr )); ! x + ! sum end (e) fun foldli f s xs = ( case xs of nil ⇒ s | ( y :: yr ) ⇒ let val x = ref y val xr = ref yr val akku = ref s in while not ( null ! xr )) do ( akku := f (! x , ! akku ); x := hd (! xr ); xr := tl (! xr )); f (! x , ! akku ) end ) Aufgabe 14.7 Betrachten Sie die folgenden Prozeduren. Geben Sie semantisch äquivalente Prozeduren an, die nicht rekursiv sind, sondern Schleifen benutzen. Hinweis: Stellen Sie zuerst sicher, dass die Prozedur endrekursiv ist. (a) fun pot 0 x a = a | pot n x a = pot ( n − 1) x ( a * x ) (b) fun square 0 = 0 | square n = square ( n − 1) + 2 * n − 1 (c) fun mystery n a b c d = if n + b + d < 0 then ( b + c ) * a else mystery ( n − 1) b a ( c * d ) ( d − a ) Lösung 14.7: (a) fun pot n x a = let val (n , a ) = ( ref n , ref a ) in ( while ! n > 0 do ( n := ! n − 1; a := ! a * x ); ! a ) end (b) Endrekursiv: fun square 0 a = a 4 | square n a = square ( n − 1) ( a + 2 * n − 1) Schleifen: fun square n = let in val (n , a ) = ( ref n , ref 0) ( while ! n > 0 do ( a := ! a + 2 * ! n − 1; n := ! n − 1); !a) end (c) fun mystery n a b c d = let val (n ,a ,b ,c , d ) = ( ref n , ref a , ref b , ref c , ref d ) in ( while ! n + ! b + ! d >= 0 do ( let val (a ’ ,b ’ ,c ’ ,d ’) = (! a , !b , !c , ! d ) in ( n := ! n − 1; a := b ’; b := a ’; c := c ’ * d ’; d := d ’ − a ’) end ); (! b + ! c ) * ! a ) end Aufgabe 14.8 (Reverse) Schreiben Sie eine Prozedur reverse: int array → unit, die eine Reihung mithilfe einer Schleife durch Umordnung Ihrer Komponenten reversiert. Lösung 14.8: Wir geben direkt eine polymorphe Prozedur zum Reversieren an. fun reverse a = let fun swap i j = Array . update (a , i , #1( Array . sub (a , j ) , Array . update (a , j , Array . sub (a , i )))); val l = ref 0; val r = ref ( Array . length a − 1); in while ! l <= ! r do swap (! l ) l := ! l + r := ! r − ) ( (! r ); 1; 1 end Halde/Linearer Speicher Aufgabe 14.9 Geben Sie die Werte aller allozierten Zellen an. Sie können dies (wie die Prozedur show) als Liste darstellen oder alternativ die verzeigerte Blockdarstellung benutzen. (a) val val val val a _ _ b = = = = new 10 update a 0 1 update a 1 1 iter 8 2 ( fn i ⇒ ( update a i ( sub a ( i−1) + sub a ( i−2)); i+1)) (b) val a = new 4 val _ = update a 0 ( a+2) val _ = update a 1 ( a+0) 5 val _ = update a 2 ( a+3) val _ = update a 3 ( a+1) Lösung 14.9: (a) [1,1,2,3,5,8,13,21,34,55] (b) a a+1 a+2 a+3 −−−−−−−−−−−−−−−−−−−−−− <−|−− | −−| → | |−−−−/\−−−−/\−−−−−/−−− \−−−−−\−−−−/ / \−−−−−−−−/ Aufgabe 14.10 Im Folgenden sollen Operationen auf der Halde (Buch S. 314) betrachtet werden. (a) Schreiben Sie eine Prozedur put: int → int → unit, die eine Zahl an eine Adresse schreibt. (b) Schreiben Sie eine Prozedur putints: int → int list → unit, die eine Folge von Zahlen hintereinander in die Halde schreibt. (c) Schreiben Sie eine Prozedur rev: int → int, die eine Liste reversiert. Sei dazu die Prozedur append: int → int → int, die zwei Listen konkateniert, gegeben. (d) Schreiben Sie eine Prozedur counter: unit → int, die in einer einzigen Speicherzelle einen Zähler implementiert, die beim n-ten Aufruf die Zahl n liefert. Die Halde sei leer. Lösung 14.10: (a) fun put a n = update a 0 n (b) fun putints a xs = foldl ( fn (x , z ) ⇒ ( update z 0 x ; z+1)) a xs (c) fun reverse a = if null a then a else append ( reverse ( tail )) ( cons ( head a ) ∼1) (d) fun counter () = update 0 0 (( sub 0 0) + 1) handle Address ⇒ ( new 1; update 0 0 0; 0) Aufgabe 14.11 (Lineare Darstellung von Listen) Sie haben in Kapitel 15.10 eine Möglichkeit kennen gelernt, Listen von Zahlen in einer Halde darzustellen. Auf diese können Sie die schon bekannten Prozeduren auf Listen aus Kapitel 4 anwenden. Allozieren Sie außer für tabulate keinen neuen Speicherplatz, wenn dies nicht unbedingt notwendig ist. (a) Schreiben Sie eine Prozedur length: address → int, die zu einer angegebenen Adresse die Länge der angegebenen verketteten Liste bestimmt. (b) Schreiben Sie eine Prozedur map: address → (int → int) → address, die auf die Liste einer angegebenen Adresse die Prozedur map anwendet. (c) Schreiben Sie eine Prozedur exists: address → (int → bool) → bool, die auf die Liste einer angegebenen Adresse die Prozedur exists anwendet. (d) Schreiben Sie eine Prozedur all: address → (int → bool) → bool, die auf die Liste einer angegebenen Adresse die Prozedur all anwendet. (e) Schreiben Sie eine Prozedur filter: address → (int → bool) → address, die auf die Liste einer angegebenen Adresse die Prozedur filter anwendet. Tipp: Sie benötigen eine Hilfsprozedur, um sich das letzte gültige Argument merken zu können. (f) Schreiben Sie eine Prozedur tabulate: int * (int → int) → address, die für eine Zahl n die Prozedur tabulate anwendet. 6 (g) Schreiben Sie eine Prozedur rev: address → address, die die Liste einer Adresse reversiert. Lösung 14.11: (a) fun length a = if a = ∼1 then 0 else 1 + length ( sub a 1) (b) fun map a f = if a = ∼1 then ∼1 else ( update a 0 ( f ( sub a 0)); map ( sub a 1) f ; a ) (c) fun exists f a = if a = ∼1 then false else f ( sub a 0) orelse exists f ( sub a 1) (d) fun all f a = if a = ∼1 then true else f ( sub a 0) andalso all f ( sub a 1) (e) fun filter ’ f a last = if a= ∼1 then ∼1 else if f ( sub a 0) then filter f ( sub a 1) a else if sub a 1 = ∼1 then ( update last 1 ∼1; last ) else ( update a 0 ( sub ( sub a 1) 0); update a 1 ( sub ( sub a 1) 1); filter ’ f a last ; a ) fun filter f a = filter ’ f a a (f) fun tabulate (n , f ) = putList ( List . tabulate (n , f )) (g) fun rev ’ a last = if a = ∼1 then ( last ) else ( let val res = rev ’ ( sub a 1) a in ( update a 1 last ; res ) end ) fun rev a = rev ’ a ∼1 Stapelmaschinen Aufgabe 14.12 Geben Sie an, wie sich der Stapel bei der Ausführung der folgenden Programme schrittweise entwickelt. Zum Beispiel soll zu [con 1, con 2, sub, halt] die Entwicklung [ ] → [1] → [1, 2] → [1] angegeben werden. (a) [con 1, con 2, getS 0, getS 1, add, putS 0, halt] (b) [con 3, con 4, con 5, con 6, getS 3, getS 2, add, getS 1, getS 0, sub, halt] Lösung 14.12: (a) [ ] → [1] → [1, 2] → [1, 2, 1] → [1, 2, 1, 2] → [1, 2, 3] → [3, 2] (b) [ ] → [3] → [3, 4] → [3, 4, 5] → [3, 4, 5, 6] → [3, 4, 5, 6, 6] → [3, 4, 5, 6, 6, 5] → [3, 4, 5, 6, 11] → [3, 4, 5, 6, 11, 4] → [3, 4, 5, 6, 11, 4, 3] → [3, 4, 5, 6, 11, ∼ 1] Aufgabe 14.13 Schreiben Sie Maschinenprogramme, die die folgenden Ausdrücke auswerten: (a) 20 + 7 − 3 (b) 20 + (7 − 3) (c) 1 + (2 + 3) ∗ (4 − 5) (d) if 2 ∗ 3 ≤ 12 − 3 then 3 else 5 (e) if (2 + 5) ≤ (1 ∗ 7) then (3 + 2) else (2 + 1) ∗ (3 + 4) Lösung 14.13: (a) [con 3, con 7, con 20, add, sub, halt] (b) [con 3, con 7, sub, con 20, add, halt] 7 (c) [con 5, con 4, sub, con 3, con 2, add, mul, con 1, add, halt] (d) [ con 3 , con 12 , sub , con 3 , con 2 , mul , leq , cbranch 3 , con 3 , branch 2 , con 5 , halt ] (e) [ con 7 , con 1 , mul , con 5 , con 2 , add , leq , cbranch 5 , con 2 , con 3 , add , branch 8 , con 4 , con 3 , add , con 1 , con 2 , add , mul , halt ] Aufgabe 14.14 (a) Schreiben Sie ein Programm, das zu einer natürlichen Zahl n mit einer Schleife die Fakultät n! berechnet. Schreiben Sie das Programm zuerst in SML und dann in M (dazu nehmen wir an, dass die Zahl n an der Position 0 auf dem Stapel liegt). Achten Sie darauf, dass Ihr Programm nur die Fakultät im Stapel zurücklässt. (b) Schreiben Sie ein Programm, das zu einer natürlichen Zahl n mit einer Schleife die Gauß-Summe für n berechnet. Lösung 14.14: (a) Zunächst schreiben wir unser Programm in SML: fun fac n = let val a = ref n val b = ref 1 in while ! a>=1 do ( b := (! a ) * (! b ) ; a := ! a−1 ); !b end Wir übersetzen nun unser Programm in M. Dazu nehmen wir an, dass unser n an der Position 0 auf dem Stapel liegt. Man kann das Programm also mit exec(con n :: xs) starten und es berechnet die Fakultät. [ con 1 , getS 0 , cbranch 10 , getS 1 , getS 0 , mul , putS 1 con 1 , getS 0 , sub , putS 0 , branch ∼10 , putS 0 , halt ] (b) fun sum n = let val a = ref n val b = ref 0 in while ! a>=1 do ( b := (! a ) + (! b ) ; a := ! a−1 ); !b end Wir übersetzen nun unser Programm in M und nehmen an, dass unser n an der Position 0 auf dem Stapel liegt. Startet man das Programm also mit exec(con n :: xs), so berechnet es die Summe der Zahlen von 1 bis n. [ con 0 , getS 0 , cbranch 10 , getS 1 , getS 0 , add , putS 1 con 1 , getS 0 , sub , putS 0 , branch ∼10 , putS 0 , halt ] 8 Aufgabe 14.15 Übersetzen Sie das folgende W -Programm in M : var n var res := 1 while 1 <= n do res := res * n n := n − 1 end Lösung 14.15: Das hier angegebene Programm geht davon aus, dass das Argument n schon auf dem Stack liegt. Die Zeile var n wird nicht übersetzt. [( con 1) , ( getS 0) ,( con 1) , leq , ( cbranch 10) , ( getS 1) , ( getS 0) , mul , ( putS 1) , ( con 1) , ( getS 0) , sub , ( putS 0) , ( branch ∼12) , halt ] (* (* (* (* res := 1 *) while 1<=n do *) res := res * n *) n := n−1 *) Aufgabe 14.16 Scheiben Sie ein Programm (in W und M), das zu n die Liste [n, ..., 0] berechnet. Lösung 14.16: In W: var n var c := 0 var xs := −1 while c <= n do xs := new (c , xs ) ; c := c+1 end return xs In M: val list = [ con 0 , con ∼1 , getS 0 , getS 1 , leq , cbranch 10 , getS 2 , getS 1 , new 2 , putS 2 , con 1 , getS 1 , add , putS 1 , branch ∼12 , putS 1 , putS 0 , halt ] Aufgabe 14.17 Schreiben Sie ein Maschinenprogramm, dass die Länge einer Liste berechnet. Halten Sie sich dabei an folgende Skizze in W: var xs var n := 0 while 0 <= xs do n := n + 1 xs := xs .1 end return n xs.1 liefert den Wert in der zweiten Zelle des Blocks, dessen Adresse der Wert der Variablen xs ist. Lösung 14.17: val prog = [ con 0 , getS 0 , con 0 , leq , cbranch 9 , con 1 , getS 1 , add , putS 1 , getS 0 , getH 1 , putS 0 , branch ∼11 , putS 0 , halt ] 9 Aufgabe 14.18 (Eigene Prozeduren aufrufen) Schreiben Sie ein Programm in M, welches mithilfe einer Prozedur, die das Minimum zweier Zahlen berechnet, den Ausdruck min(17, 8) − min(5, 7) berechnet. Lösung 14.18: [ proc (2 , 9) , arg 1 , arg 2 , leq , cbranch 3 , arg 2 , return , arg 1 , return , con 7 , con 5 , call 0 , con 8 , con 17 , call 0 , sub , halt ] Aufgabe 14.19 Übersetzen Sie die folgende Prozedur nach M: fun fac n = if n < 2 then 1 else fac ( n − 1) * n Nehmen Sie dabei an, dass die Befehlssequenz für die Prozedur im Programmspeicher mit der Adresse 0 beginnt. Lösung 14.19: val fac = [ proc (1 , 14) , con 1 , arg 1 , leq , cbranch 3 , con 1 , return , con 1 , arg 1 , sub , call 0 , arg 1 , mul , return ] Z. B. liefert nun der Aufruf exec (fac @ [con 4, call 0, halt]) die int list [24]. Aufgabe 14.20 Übersetzen Sie die folgende Prozedur nach M: fun fib n = if n < 2 then n else fib ( n − 2) + fib ( n − 1) Lösung 14.20: val fib = [ proc (1 , 17) , con 1 , arg 1 , leq , cbranch 3 , arg 1 , return , con 1 , arg 1 , sub , call 0 , con 2 , arg 1 , sub , call 0 , add , return ] Z. B. liefert nun der Aufruf exec (fac @ [con 4, call 0, halt]) int list [3]. Aufgabe 14.21 (Rätsel) Was tut das folgende Programm? Das Programm erwartet, dass zwei Argumente auf dem Stack liegen, bevor es ausgeführt wird. [( getS 0) , ( con 1) , ( getS 1) , leq , ( cbranch 2) , ( branch 8) , ( getS 0) , mul , ( con 1) , ( getS 1) , sub , ( putS 1) , ( branch ∼11) , halt ] Lösung 14.21: Das Programm berechnet xn , wobei x das erste Argument und n das zweite Argument auf dem Stack ist. 10