Programmierung 1 - Repetitorium WS 2002/2003 Programmierung 1 - Repetitorium Andreas Augustin und Marc Wagner Homepage: http://info1.marcwagner.info Programmierung 1 - Repetitorium Mittwoch, den 09.04.03 Kapitel 6 Konstruktortypen und Ausnahmen Programmierung 1 - Repetitorium 6.1 Varianten und Konstruktoren Ziel: Darstellung der drei geometrischen Formen Kreis, Quadrat und Dreieck als mathematische Objekte Das Paar < 1 , r > stellt einen Kreis mit Radius r dar. Das Paar < 2 , a > stellt ein Quadrat mit der Seitenlänge a dar. Das Paar < 3 , < a , b , c > > stellt ein Dreieck mit den Seitenlängen a,b,c dar. Die erste Komponente des Paares bezeichnet man als Variantennummer. Sie gibt die Form des geometrischen Objekts an. Die zweite Komponente des Paares bezeichnet man als Datum. Sie spezifiziert die Dimensionen des geometrischen Objekts. datatype shape = Circle of real | Square of real | Triangle of real * real * real Programmierung 1 - Repetitorium 6.1 Varianten und Konstruktoren Der Datentyp shape liefert uns jetzt die Konstruktoren Circle, Square und Triangle. Circle Square 4.0 3.0 Circle 4.0 Square 3.0 Triangle 4.0 3.0 5.0 Triangle ( 4.0 , 3.0 , 5.0 ) Berechnung des Flächeninhalts : fun area (Circle r) = Math.pi * r * r | area (Square a) = a * a | area (Triangle (a,b,c)) = let val s = (a+b+c)/2.0 in Math.sqrt(s*(s-a)*(s-b)*(s-c)) end Programmierung 1 - Repetitorium 6.1 Varianten und Konstruktoren Die Prozedur area ist mit drei Regeln definiert, die jeweils für eine der drei Varianten von shape zuständig sind. Die Muster der Regeln haben die Form einer Konstruktoranwendung. Die Deklaration von Konstruktoren ermöglicht es, die verschiedenen Varianten eines Konstruktortyps durch frei gewählte Namen zu bezeichnen. Konvention: Konstruktoren = Bezeichner mit Großbuchstaben beginnend Typen und Werte = Bezeichner mit Kleinbuchstaben beginnend Programmierung 1 - Repetitorium 6.2 Enumerationstyp datatype day = Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday Die Werte des Typs day werden durch nullstellige Konstruktoren beschrieben, die wie Konstanten verwendet werden können. fun weekend Saturday = true | weekend Sunday = true | weekend _ = false Typ: weekend : day → bool map weekend [ Monday , Wednesday , Friday , Saturday, Sunday ] = [ false , false , false , true, true ] Typen, die nur mit Hilfe von nullstelligen Konstruktoren definiert sind, werden als Enumerationstyp bezeichnet. vordeklarierter Enumerationstyp: datatype order = LESS | EQUAL | GREATER Programmierung 1 - Repetitorium 6.3 Typsynonyme type point = real * real point ist hierbei kein neuer Typ, sondern eine neue Bezeichnung für einen bereits existierenden Typ. sinnvolle Verwendungen : datatype object = Circle of point * real | Triangle of point * point * point Wir können auch einen neuen Typ einführen : datatype point = P of real * real P ( 2.0 , 3.0 ) : point fun mirror ( P ( x , y ) ) = P ( ~x , y ) Programmierung 1 - Repetitorium 6.4 Ausnahmen Ausnahmen sind Werte des Typs exn. Neue Ausnahmen deklariert man mithilfe von exception. exception New exception Newer of int New sowie Newer sind Ausnahmekonstruktoren, diese können wie normale Konstruktoren verwendet werden. Zusätzlich kann man mit Ausdrücken der Form raise e die Ausnahme e werfen (e vom Typ exn) raise Newer 6 !Uncaught exception: Newer 6 Raise-Ausdrücke liefern keinen Wert, daher können sie jeden Typ annehmen. Programmierung 1 - Repetitorium 6.4 Ausnahmen Geworfene Ausnahmen können mithilfe von Handle-Ausdrücken gefangen werden. ( raise New ) handle New => ( ) ( raise Newer 7 ) handle Newer x => 7 fun test f = f() handle Newer x => x | Overflow => ~1 Beim Programmieren mit Ausnahmen sind manchmal Sequenzialisierungen ( e1 ; ... ; en) hilfreich. Auswertung einer Sequenz: Alle Teilausdrücke e1 , ... , en von links nach recht auswerten. Bei Terminierung aller Ausdrücke, liefert die Sequenz den Wert des letzten Teilausdrucks en. (e1,...,en) = let val _ = e1 ... val _ = en-1 in en end Programmierung 1 - Repetitorium 6.5 Arithmetische Ausdrücke type var = int datatype exp = (Variablen als ganze Zahlen) C of int | V of var | A of exp*exp | M of exp*exp Jede Ausdrucksform ( Konstante, Variable, Addition, Multiplikation ) wird durch einen entsprechenden Konstruktor realisiert. Darstellung der rekursiven Struktur von exp: A M exp C int V var Programmierung 1 - Repetitorium 6.5 Arithmetische Ausdrücke ( 2 x + y ) * ( x + 3 ) val e = M ( A ( M ( C 2 , V 1 ) , V 2 ) , A ( V 1 , C 3 ) ) M A M C V 2 1 A V V C 2 1 3 datatype exp = C of int | V of var | A of exp*exp | M of exp*exp Um den Ausdruck auszuwerten, benötigen wir eine Umgebung, die den Variablen x und y Werte zuweist. { x → 5 , y → 3 } val env = fn 1 => 5 | 2 => 3 | _ => raise Unbound Programmierung 1 - Repetitorium 6.5 Arithmetische Ausdrücke Unser Ziel : die Evaluierungsprozedur eval : exp → env → int fun | | | eval eval eval eval ( ( ( ( C V A M c ) _ = c v ) env = env v (e,e‘) ) env = eval e env + eval e‘ env (e,e‘) env = eval e env * eval e‘ env Programmierung 1 - Repetitorium 6.6 Optionen datatype ‘a option = NONE | SOME of ‘a NONE ist die uneingelöste Option, SOME ist die eingelöste Option. fun get xs n = SOME (List.nth(xs,n)) handle Subscript => NONE liefert das n-te Element einer Liste als eingelöste Option, wenn es existiert. get [3,4,5] 2 = SOME 5 get [3,4,5] 3 = NONE fun valOf (SOME x) = x | valOf NONE = raise Option.Option erlaubt bequemen Zugriff auf eingelöste Optionen fun isSome NONE = false | isSome (SOME _) = true testet, ob es sich um eine eingelöste Option handelt Programmierung 1 - Repetitorium 6.7 Case-Ausdrücke und abgeleitete Formen fun sign x = case Int.compare (x,0) of LESS => ~1 | EQUAL => 0 | GREATER => 1 case e of M1 => e1 | ... | Mn => en = ( fn M1 => e1 | ... | Mn => en ) e if e1 then e2 else e3 = (fn true => e2 | false => e3) e1 Programmierung 1 - Repetitorium 6.8 Bäume informell und formal Größe des Baumes = Anzahl der Knoten des Baumes Pfad = Verbindung zwischen 2 bel. Knoten eines Baumes Länge des Pfades = Anzahl der Kanten des Pfades Tiefe des Knotens = Länge des Pfades von der Wurzel zu den Knoten Tiefe des Baumes = Max. Tiefe seiner Knoten Wurzel 3 Kante Vorgänger Marke 2 4 1 Knoten Blatt 1.Nachfolger 1 2.Nachfolger 3 0 3.Nachfolger 2 7 Programmierung 1 - Repetitorium 6.8 Bäume informell und formal Eigenschaften von Bäumen : 1. Zwischen 2 Knoten eines Baumes existiert immer genau ein Pfad. 2. Die Wurzel eines Baumes hat keinen Vorgänger und alle anderen Knoten haben genau einen Vorgänger. 3 2 [] 0 4 1 1 2 7 7 7 7 [1,1,1] [1] [2] [3] [1,1] [3,1] [3,2] [1,1,2] [1,1,3] Tiefe eines Knotens = Länge seiner Adresse u Adresse des Knotens ⇒ u@[n] Adresse seines n-ten Nachfolgers [3,3] Programmierung 1 - Repetitorium 6.8 Bäume informell und formal Bäume mit genau einem Knoten heißen atomar. Bäume mit mind. zwei Knoten heißen zusammenhängend. Die Blätter eines Baumes sind alle Knoten ohne Nachfolger. Jeder Baum hat genau eine Wurzel, mindestens ein Blatt und mind. einen Knoten. Ein Baum ist aus seinem Kopf (Marke der Wurzel) und seinen Unterbäumen (Teilbäume der Nachfolger der Wurzel) zusammengesetzt. ( x , [ t1 , ... , tn ] ) 1 (1,[(2,[]),(7,[]),(7,[])]) 2 7 7 Ein Baum heißt linear gdw. jeder Knoten höchstens einen Nachfolger hat. Ein Baum heißt binär gdw. jeder Knoten außer den Blättern genau zwei Nachfolger hat. Ein Baum heißt balanciert gdw. alle Blätter gleiche Tiefe haben. Jeder atomare Baum ist linear, binär und balanciert. Programmierung 1 - Repetitorium 6.9 Ein ausführbares Modell für Bäume datatype ‘a tree = T of ‘a * ( ‘a tree list ) 1 T(1,[T(2,[]),T(7,[]),T(7,[])]) 2 7 7 fun head (T(x,_)) = x liefert den Kopf des Baumes fun dst (T(_,ts)) n = List.nth(ts,n-1) liefert den n-ten Unterbaum von t fun atomic (T(_,ts)) = null ts testet, ob ein Baum atomar ist Programmierung 1 - Repetitorium 6.9 Ein ausführbares Modell für Bäume fun subtree t nil = t | subtree t (n::ns) = subtree (dst t n) ns liefert zu einem Baum und einer Liste den zugeordneten Teilbaum fun node t ns = (subtree t ns; true) handle Subscript => false testet für einen Baum, ob es sich bei einer Liste um einen Knoten des Baumes handelt fun leaf t ns = atomic (subtree t ns) handle Subscript => false testet für einen Baum, ob es sich bei einer Liste um ein Blatt des Baumes handelt fun label t ns = head (subtree t ns) liefert die Marke eines Knotens fun pred t nil = raise Subscript | pred t ns = (subtree t ns; rev(tl(rev ns))) liefert zu einem Baum und einem Knoten den Vorgänger des Knotens Programmierung 1 - Repetitorium 6.9 Ein ausführbares Modell für Bäume fun succ t ns n = let val ns‘ = ns@[n] in subtree t ns‘; ns‘ end liefert zu einem Baum, einem Knoten und einer Zahl n den n-ten Nachfolger des Knotens fun size (T(_,ts)) = foldl op+ 1 (map size ts) liefert die Größe eines Baumes ( = 1 + Summe der Größen seiner Unterbäume ) fun depth (T(_,ts)) = 1 + foldl Int.max -1 (map depth ts) liefert die Tiefe eines Baumes ( = 1 + max. Tiefe seiner Unterbäume ) Programmierung 1 - Repetitorium 6.10 Test auf Balanciertheit Ein Baum ist genau dann balanciert, wenn für jeden seiner Teilbäume t gilt, dass alle seine Unterbäume die gleiche Höhe haben bzw. alle Blätter die gleiche Tiefe haben. fun balanced t = let exception Unbalanced fun depthb (T(_,nil)) = 0 | depthb (T(_,t::tr)) = 1 + foldl forward (depth t) tr and forward (t,n) = if depthb t = n then n else raise Unbalanced in (depthb t; true) handle Unbalanced => false end depthb und forward sind verschränkt rekursiv, daher ist eine Deklaration mit and erforderlich. Programmierung 1 - Repetitorium 6.11 Linearisation und Projektionen Eine Liste heißt Linearisierung eines Baumes, wenn sie genau die Knoten des Baumes enthält (ohne Doppelauftreten), und wenn sie die Knoten jedes Teilbaumes als Segment enthält. Präfixlinearisierung : 1. Für jeden Teilbaum gilt : Die Wurzel erscheint vor den Knoten der Unterbäume. 2. Für jeden Teilbaum mit mindestens zwei Unterbäumen gilt : Die Knoten der Unterbäume erscheinen in der Reihenfolge der Unterbäume. fun pre (T(x,ts)) = x :: List.concat (map pre ts) Die Projektion einer Liste von Knoten erhält man, indem man jeden Knoten durch seine Marke ersetzt. fun project t nss = map (label t) nss Programmierung 1 - Repetitorium 6.11 Linearisation und Projektionen Postfixlinearisierung : Analog zur Präfixlinearisierung, nur mit dem Unterschied, dass die Wurzel eines Teilbaums diesmal nach dem Knoten seiner Unterbäume erscheint. fun post (T(x,ts)) = List.concat (map post ts) @ [x] 3 0 5 4 1 2 7 Präfix : [ 3 , 0 , 5 , 4 , 1 , 2 , 7 , 7 ] Postfix : [ 5 , 0 , 4 , 2 , 7 , 7 , 1 , 3 ] 7