Algorithmen und Datenstrukturen Werner Struckmann Wintersemester 2005/06 10. Funktionale und deduktive Algorithmen 10.1 Partielle und totale Funktionen 10.2 Funktionale Algorithmen 10.3 Prädikatenlogik 10.4 Deduktive Algorithmen Einführung Grundidee: I Ein Algorithmus wird durch eine Funktion f realisiert. I Die Eingabe des Algorithmus ist ein Element w aus dem Definitionsbereich von f . I Die Ausgabe des Algorithmus ist der Wert f (w ) aus dem Wertebereich von f . 10.1 Partielle und totale Funktionen 10-1 Einführung I Mathematische Funktionen sind häufig deklarativ definiert: Sie beinhalten keine Angaben zur Durchführung ihrer Berechnung. I Beispiele: f (n, m) = n · m, f (n) = n!. I Wie kann ein Algorithmus den Funktionswert f (w ) berechnen? I Können alle berechenbaren Probleme derart gelöst werden? 10.1 Partielle und totale Funktionen 10-2 Partielle und totale Funktionen Eine partielle Funktion f : A −→ B ordnet jedem Element x einer Teilmenge Df ⊆ A genau ein Element f (x ) ∈ B zu. Die Menge Df heißt Definitionsbereich von f . f ist eine totale Funktion, wenn Df = A gilt. Beispiel: f : R −→ R, Df = R \ {0}, 1 f (x ) = x Algorithmen können undefinierte Ausdrücke enthalten und müssen nicht in jedem Fall terminieren, d. h.: Algorithmen berechnen partielle Funktionen! 10.1 Partielle und totale Funktionen 10-3 Definition von Funktionen I Wenn der Definitionsbereich einer Funktion endlich ist, lässt sie sich durch Angabe aller Funktionswerte in einer Tabelle definieren. I Beispiel: ∧: B×B→B false false true true 10.1 Partielle und totale Funktionen false true false true false false false true 10-4 Definition von Funktionen I In den meisten Fällen wird eine Funktion f : A → B durch einen Ausdruck, der zu jedem Element aus Df genau einen Wert von B liefert, beschrieben. I Beispiel: max : R × R → R x max(x , y ) = y x≥y x<y = if x ≥ y then x else y fi 10.1 Partielle und totale Funktionen 10-5 Rekursive Definitionen (Wiederholung) Die Funktion f : N −→ N wird durch 1 1 f (n ) = n f 2 f (3n + 1) n = 0, n = 1, n ≥ 2, n gerade, n ≥ 2, n ungerade. rekursiv definiert. 10.1 Partielle und totale Funktionen 10-6 Auswertung von Funktionen (Wiederholung) Funktionsdefinitionen können als Ersetzungssysteme gesehen werden. Funktionswerte lassen sich aus dieser Sicht durch wiederholtes Einsetzen berechnen. Die Auswertung von f (3) ergibt f (3) → f (10) → f (5) → f (16) → f (8) → f (4) → f (2) → f ( 1) → 1. Terminiert der Einsetzungsprozess stets? 10.1 Partielle und totale Funktionen 10-7 Formen der Rekursion (Wiederholung) I Lineare Rekursion, I Endrekursion, I Verzweigende Rekursion (Baumrekursion), I Geschachtelte Rekursion, I Verschränkte Rekursion (wechselseitige Rekursion). 10.1 Partielle und totale Funktionen 10-8 Funktionen höherer Ordnung Funktionen können selbst Argumente oder Werte sein. In diesem Fall spricht man von Funktionen höherer Ordnung oder Funktionalen. f : (A1 → A2 ) × A3 → B g : A → (B 1 → B 2 ) h : (A1 → A2 ) → (B1 → B2 ) 10.1 Partielle und totale Funktionen 10-9 Funktionen höherer Ordnung Beispiele: I Summe: b X f (i ) i =a I Komposition von Funktionen: I Auswahl zwischen Funktionen: I Bestimmtes Integral: 10.1 Partielle und totale Funktionen Z f ◦g if p then f else g fi b f (x ) dx a 10-10 Funktionale Algorithmen I Ein Algorithmus heißt funktional, wenn die Berechnungsvorschrift mittels einer Sammlung (partieller) Funktionen definiert wird. I Die Funktionsdefinitionen dürfen insbesondere Rekursionen und Funktionen höherer Ordnung enthalten. 10.2 Funktionale Algorithmen 10-11 Funktionale Algorithmen Beispiel: f ( 0) = 0 f ( 1) = 1 f (n) = nf (n − 2) Wenn wir als Datenbereich die Menge der ganzen Zahlen zugrunde legen, berechnet dieser Algorithmus die Funktion f : Z → Z mit Df = N und 0 n gerade n −1 f (n ) = 2 Y (2i + 1) n ungerade i =0 10.2 Funktionale Algorithmen 10-12 Funktionale Programmiersprachen Programmiersprachen, die in erster Linie für die Formulierung funktionaler Algorithmen gedacht sind, heißen funktional. Funktionale Programmiersprachen sind beispielsweise I Lisp, I Scheme, I ML, SML und I Haskell. Man kann in vielen imperativen/objektorientierten Programmiersprachen funktional programmieren – und umgekehrt! 10.2 Funktionale Algorithmen 10-13 Lisp und Scheme I Lisp wurde Ende der 50er Jahre von John McCarthy entwickelt. I Im Laufe der Jahre wurden viele Lisp-Dialekte, u. a. Common Lisp und Scheme definiert. I Die erste Version von Scheme stammt aus dem Jahre 1975. Autoren waren Guy Lewis Steele Jr. und Gerald Jay Sussman. I Lisp und Scheme werden in der Regel interpretiert, nicht compiliert. 10.2 Funktionale Algorithmen 10-14 Algorithmus von Euklid Funktional geschrieben hat der Algorithmus von Euklid die Form: ggT(a , 0) = a ggT(a , b ) = ggT(b , a mod b ) Beispiel: ggT(36, 52) → ggT(52, 36) → ggT(36, 16) → ggT(16, 4) → ggT(4, 0) → 4 10.2 Funktionale Algorithmen 10-15 Scheme: Algorithmus von Euklid Der funktionale Algorithmus von Euklid lautet beispielsweise als Scheme-Programm: (define (ggT a b) (if (= b 0) a (ggT b (remainder a b)))) (ggT 36 52) 4 10.2 Funktionale Algorithmen 10-16 Terme Terme sind aus I Konstanten, I Variablen, I Funktions- und I Relationssymbolen zusammengesetzte Zeichenketten. Terme besitzen einen Typ. Beispiele: I I Die Konstanten . . . , −2, −1, 0, 1, 2, . . . sind int-Terme. 13 − √ 2 + 3 ist ein real-Term. I 4 · (3 − 2) + 3 · i ist ein int-Term, falls i eine Variable vom Typ int ist. I Ist b ein bool-Term und sind t , u int-Terme, so ist auch if b then t else u fi ein int-Term. 10.2 Funktionale Algorithmen 10-17 Funktionsdefinitionen Die Gleichung f (x1 , . . . , xn ) = t (x1 , . . . , xn ) heißt Definition der Funktion f vom Typ τ, wenn gilt: I f ist der Funktionsname. I x1 , . . . , xn sind Variable, die formale Parameter genannt werden. Die Typen von x1 , . . . , xn seien τ1 , . . . , τn . I t ist ein Term, der die Variablen x1 , . . . , xn enthält. Der Typ von t sei τ. I f : τ1 × · · · × τn → τ heißt Signatur von f . Der Fall n = 0 ist zugelassen. In diesem Fall liefert die Auswertung stets das gleiche Ergebnis. Die Funktion entspricht somit einer Konstanten. 10.2 Funktionale Algorithmen 10-18 Funktionsdefinitionen Beispiele: ZylVol(h , r ) π max(x , y ) f (p , q , x , y ) g (x ) h (p , q ) 10.2 Funktionale Algorithmen = h · π · r2 (Signatur ZylVol: real×real→real) = 3.1415926535 . . . (Signatur: π →real) = if x > y then x else y fi (Signatur max: int×int→int) = if p ∨ q then 2 · x + 1 else 3 · y − 1 if (Signatur f: bool×bool×int×int→int) = if even(x ) then x2 else 3 · x + 1 fi (Signatur g: int→int) = if p then q else false fi (Signatur h: bool×bool→bool) 10-19 Funktionsanwendungen I Unter einer Anwendung (Applikation) einer Funktion f (x1 , . . . , xn ) = t (x1 , . . . , xn ) versteht man einen Ausdruck f (a1 , . . . , an ). I Für die formalen Parameter werden Ausdrücke (aktuelle Parameter) eingesetzt, die den richtigen Typ besitzen müssen. I Die Auswertung liefert eine Folge f (a1 , . . . , an ) → t (a1 , . . . , an ) → · · · . I Es muss genau festgelegt werden, wie und in welcher Reihenfolge auftretende (Teil-)Ausdrücke ausgewertet werden. I Diese Folge muss nicht terminieren. 10.2 Funktionale Algorithmen 10-20 Funktionsanwendungen f (p , q, x , y ) = if p ∧ q then 2 · x + 1 else 3 · y − 1 fi f (false , false , 3, 4) = if false ∧ false then 2 · x + 1 else 3 · y − 1 fi = if false then 2 · 3 + 1 else 3 · 4 − 1 fi = 3 · 4 − 1 = 11 x g (x ) = if even(x ) then else 3 · x + 1 fi 2 1 g (1) = if even(1) then else 3 · 1 + 1 fi 2 =3·1+1=4 10.2 Funktionale Algorithmen 10-21 Funktionsanwendungen h (p , q) = if p then 8 else 8/q fi h (true , 2) = if true then 8 else 8/2 fi =8 h (false , 2) = if false then 8 else 8/2 fi =4 h (false , 0) = if false then 8 else 8/0 fi undefiniert h (true , 0) = if true then 8 else 8/0 fi =8 Bei der Auswertung des Terms if b then t1 else t2 fi wird zunächst der boolesche Term b ausgewertet, und dann, abhängig vom Ergebnis, t1 oder t2 . 10.2 Funktionale Algorithmen 10-22 Funktionsdefinitionen Eine Funktion kann auch in mehreren Gleichungen definiert werden, jeweils für einen Teil der Argumente. Beispiel: f (0) = 0 f (1) = 2 f (−1) = 2 f (x ) = if x > 1 then x (x − 1) else − x (x − 1) fi Die Auswertung erfolgt dabei von oben nach unten, wobei die erste passende linke Seite für die Berechnung angewendet wird. Es kommt daher auf die Reihenfolge der Gleichungen an. 10.2 Funktionale Algorithmen 10-23 Funktionsdefinitionen Folgendes Gleichungssystem definiert eine andere Funktion. f (−1) = 2 f (x ) = if x > 1 then x (x − 1) else − x (x − 1) fi f (1) = 2 f (0) = 0 Hier sind die letzten beiden Gleichungen überflüssig. Man kann mehrere Definitionsgleichungen immer in einer Gleichung zusammenfassen, indem man geschachtelte if-then-else-fi Konstrukte verwendet. 10.2 Funktionale Algorithmen 10-24 Funktionsdefinitionen Das erste Beispiel oben lässt sich in einer Gleichung schreiben: f (x ) = if x = 0 then 0 else if x = 1 then 2 else if x = −1 then 2 else if x > 1 then x (x − 1) else − x (x − 1) fi fi fi fi Die Schreibweise mit mehreren Gleichungen ist in der Regel übersichtlicher. 10.2 Funktionale Algorithmen 10-25 Funktionsdefinitionen Ein Wächter (guard) ist eine Vorbedingung, die für die Anwendung einer Definitionsgleichung erfüllt sein muss. Beispiel: f ( 0) = 0 f ( 1) = 2 f (−1) = 2 x > 1 :f (x ) = x (x − 1) f (x ) = −x (x − 1) 10.2 Funktionale Algorithmen 10-26 Funktionsdefinitionen Eine Funktionsdefinition kann unvollständig sein. Beispiel: f ( 0) = 0 f ( 1) = 2 f (−1) = 2 x > 1 : f ( x ) = x ( x − 1) Gegenüber dem vorigen Beispiel fehlt hier die letzte Gleichung. Es gibt daher keine Berechnungsvorschrift für Werte < −1. D. h., die Funktion ist dort nicht definiert. 10.2 Funktionale Algorithmen 10-27 Funktionsdefinitionen Funktionen können unter Verwendung von Hilfsfunktionen definiert werden. Beispiel: Volumen(h , r , a , b , c ) = ZylVol(h , r ) + QuadVol(a , b , c ) ZylVol(h , r ) = h · KreisFl(r ) KreisFl(r ) = πr 2 QuadVol(a , b , c ) = a · b · c Einsetzen führt zu einer einzeiligen Definition: Volumen(h , r , a , b , c ) = h πr 2 + a · b · c 10.2 Funktionale Algorithmen 10-28 Auswertung von Funktionen Volumen(3, 2, 5, 1, 5) = ZylVol(3, 2) + QuadVol(5, 1, 5) = 3 · KreisFl(2) + QuadVol(5, 1, 5) = 3π22 + QuadVol(5, 1, 5) = 3π 2 2 + 5 · 1 · 5 = 12π + 25 ≈ 62.699111843 Alternativ kann einem Term ein Name gegeben werden, der dann (mehrfach) verwendet werden kann: f (a , b ) = x · x where x = a + b ist gleichbedeutend mit f (a , b ) = (a + b ) · (a + b ). 10.2 Funktionale Algorithmen 10-29 Applikative Algorithmen Ein applikativer (funktionaler) Algorithmus ist eine Liste von Funktionsdefinitionen f1 (x1,1 , . . . , x1,n1 ) = t1 (x1,1 , . . . , x1,n1 ), f2 (x2,1 , . . . , x2,n2 ) = t2 (x2,1 , . . . , x2,n2 ), .. .. .=. fm (xm,1 , . . . , xm,nm ) = tm (xm,1 , . . . , xm,nm ). Die erste Funktion ist die Bedeutung (Semantik) des Algorithmus. Die Funktion wird für eine Eingabe (a1 , . . . , an1 ) wie beschrieben ausgewertet. Die Ausgabe ist f1 (a1 , . . . , an1 ). 10.2 Funktionale Algorithmen 10-30 Gültigkeit und Sichtbarkeit Beispiel: f (a , b ) = g (b ) + a g (a ) = a · b b=3 f (1, 2) = g (2) + 1 = 2 · b + 1 = 2 · 3 + 1 = 7 Die globale Definition von b wird in der Definition von f durch die lokale Definition verdeckt. Es treten also auch hier die Fragen nach dem Gültigkeits- und dem Sichtbarkeitsbereich von Variablen auf, wie wir sie in Kapitel 2 bei den imperativen Algorithmen angetroffen haben. 10.2 Funktionale Algorithmen 10-31 Undefinierte Funktionswerte Die Fakultätsfunktion ist definiert durch: Fac(0) = 1 Fac(n) = n · Fac(n − 1) Für negative Parameter terminiert die Berechnung nicht: Fac(−1) = −1 · Fac(−2) = −1 · −2 · Fac(−3) = · · · Die Funktion Fac ist also partiell. Es gibt drei mögliche Ursachen für undefinierte Funktionswerte: I Die Parameter führen zu einer nicht terminierenden Berechnung. I Eine aufgerufene Funktion ist für einen Parameter undefiniert (zum Beispiel Division durch 0). I Die Funktion ist unvollständig definiert. 10.2 Funktionale Algorithmen 10-32 Komplexe Datentyen Komplexe Datentypen (Datenstrukturen) entstehen durch Kombination elementarer Datentypen und besitzen spezifische Operationen. Sie können vorgegeben oder selbstdefiniert sein. Die grundlegenden Datentypen werden auch Atome genannt. Übliche Atome sind die Typen int, bool, float und char sowie Variationen davon. Es gibt in Bezug auf das Anwendungsspektrum eine Unterscheidung in I generische Datentypen: Sie werden für eine große Gruppe ähnlicher Problemstellungen entworfen und sind oft im Sprachumfang enthalten (Liste, Keller, Feld, Verzeichnis, . . . ). I spezifische Datentypen: Sie dienen der Lösung einer eng umschriebenen Problemstellung und werden im Zusammenhang mit einem konkreten Problem definiert (Adresse, Person, Krankenschein, . . . ). 10.2 Funktionale Algorithmen 10-33 Generische Datentypen der funktionalen Programmierung In der funktionalen Programmierung spielen die folgenden generischen Datentypen eine hervorgehobene Rolle: I Listen, I Texte (Liste von Zeichen), I Tupel, I Funktionen. 10.2 Funktionale Algorithmen 10-34 Listen Die Datenstruktur funktionaler Sprachen und Programmierung. Die funktionale Programmierung wurde anfangs auch Listenverarbeitung genannt. Lisp steht für „List Processor“. Beispiele: I Liste von Messwerten, geordnet nach Aufzeichnungszeitpunkt, z. B. Zimmertemperatur (° C) im Informatikzentrum nach Ankunft: [17.0, 17.0, 17.1, 17.2, 17.4, 17.8]. I Alphabetisch geordnete Liste von Namen z. B. Teilnehmer der kleinen Übung: [„Baltus“, „Bergmann“, „Cäsar“]. I Alphabetisch geordnete Liste von Namen mit Vornamen, d. h. Liste von zweielementigen Listen mit Namen und Vornamen: [[„Kundera“, „M.“], [„Hesse“, „S.“], [„Einstein“, „A.“]]. 10.2 Funktionale Algorithmen 10-35 Listen Syntax und Semantik: I [D ] ist der Datentyp der Listen, d. h. der endlichen Folgen, über D . I Notation: [x1 , x2 , . . . , xn ] ∈ [D ] für x1 , x2 , . . . , xn ∈ D . Beispiele: I [real]: Menge aller Listen von Fließkommazahlen, z. B. Messwerte, Vektoren über R. I [char]: Menge aller Listen von Buchstaben, z. B. Namen, Befunde, Adressen. I [[char]]: Menge aller Listen von Listen von Buchstaben, z. B. Namensliste. 10.2 Funktionale Algorithmen 10-36 Typische Listenoperationen I []: leere Liste. I e: l: Verlängerung einer Liste l nach vorn um ein Einzelelement e , z. B. 1 : [2, 3] = [1, 2, 3]. I length(l): Länge einer Liste l , z. B. length ([4, 5, 6]) = 3. I I I head(l): erstes Element e einer nichtleeren Liste l = e : l 0 , z. B. head ([1, 2, 3]) = 1. tail(l): Restliste l 0 einer nichtleeren Liste l = e : l 0 nach Entfernen des ersten Elementes, z. B. tail ([1, 2, 3]) = [2, 3]. last(l): letztes Element einer nichtleeren Liste, z. B. last ([1, 2, 3]) = 3. 10.2 Funktionale Algorithmen 10-37 Typische Listenoperationen I I I init(l): Restliste einer nichtleeren Liste nach Entfernen des letzten Elements, z. B. init ([1, 2, 3]) = [1, 2]. l++l’: Verkettung zweier Listen l und l 0 , z. B. [1, 2] + +[3, 4] = [1, 2, 3, 4]. l!!n: Das n-te Element der Liste l , wobei 1 ≤ n ≤ length (l ), z. B. [2, 3, 4, 5]!!3 = 4. Vergleichsoperationen = und ,: (e1 : t1 ) = (e2 : t2 ) ⇔ e1 = e2 ∧ t1 = t2 , l1 , l2 ⇔ ¬(l1 = l2 ). [i , i + 1, i + 2, . . . , j − 1, j ] [i , . . . , j ] = [] 10.2 Funktionale Algorithmen falls i ≤ j , falls i > j . 10-38 Typische Listenoperationen Die folgende Funktion berechnet rekursiv das Spiegelbild einer Liste. mirror :[int ] → [int ] mirror ([]) = [] mirror (l ) = last (l ) : mirror (init (l )) mirror ([1, 2, 3, 4]) = 4 : mirror (init ([1, 2, 3, 4])) = 4 : mirror ([1, 2, 3]) = 4 : (3 : mirror ([1, 2])) = 4 : (3 : (2 : mirror ([1]))) = 4 : (3 : (2 : (1 : mirror ([])))) = 4 : (3 : (2 : (1 : []))) = 4 : (3 : (2 : [1])) = 4 : (3 : [2, 1]) = 4 : [3, 2, 1] = [4, 3, 2, 1] 10.2 Funktionale Algorithmen 10-39 Typische Listenoperationen Die folgende Funktion berechnet rekursiv das Produkt der Elemente einer Liste. prod :[int ] → int prod ([]) = 1 prod (l ) = head (l ) · prod (tail (l )) Die folgende Funktion konkateniert rekursiv eine Liste von Listen. concat :[[t ]] → [t ] concat ([]) = [] concat (l ) = head (l ) + +concat (tail (l )) concat ([[1, 2], [], [3], [4, 5, 6]]) = [1, 2, 3, 4, 5, 6] 10.2 Funktionale Algorithmen 10-40 Sortierverfahren Alle Algorithmen aus den vorhergehenden Kapiteln lassen sich auch funktional beschreiben, häufig sehr viel eleganter. Als Beispiel betrachten wir zwei Sortierverfahren. Wiederholung: Es sei eine Ordungsrelation ≤ auf dem Elementdatentyp D gegeben. I I I I Eine Liste l = (x1 , . . . , xn ) ∈ [D ] heißt sortiert, wenn x1 ≤ x2 ≤ · · · ≤ xn gilt. Eine Liste l 0 = [D ] heißt Sortierung von l ∈ [D ], wenn l und l 0 die gleichen Elemente haben und l 0 sortiert ist. Eine Sortierung l 0 von l heißt stabil, wenn sie gleiche Listenelemente nicht in ihrer Reihenfolge vertauscht. l = [5, 9, 3, 8, 8], l 0 = [3, 5, 8, 8, 9] (nicht stabil wäre l 00 = [3, 5, 8, 8, 9]) Ein Sortieralgorithmus heißt stabil, wenn er stabile Sortierungen liefert 10.2 Funktionale Algorithmen 10-41 Sortieren durch Einfügen Beim Sortieren durch Einfügen wird die Ordnung hergestellt, indem jedes Element an der korrekten Position im Feld eingefügt wird. insert (x , []) = [x ] x ≤ y :insert (x , y : l ) = x : y : l insert (x , y : l ) = y : insert (x , l ) Für das Sortieren einer unsortierten Liste gibt es zwei Varianten: sort 1([]) = [] sort 1(l ) = insert (head (l ), sort 1(tail (l ))) sort 2([]) = [] sort 2(l ) = insert (last (l ), sort 2(init (l ))) Welche dieser Algorithmen sind stabil? 10.2 Funktionale Algorithmen 10-42 Sortieren durch Auswählen Beim Sortieren durch Auswählen wird das kleinste (größte) Element an den Anfang (das Ende) der sortierten Liste angefügt. Die folgende Funktion löscht ein Element aus einer Liste: delete (x , []) = [] x = y :delete (x , y : l ) = l delete (x , y : l ) = y : delete (x , l ) Für das Sortieren einer unsortierten Liste gibt es wieder zwei Varianten: sort 3(l ) = x : sort 3(delete (x , l )) where x = min(l ) sort 4(l ) = sort 4(delete (x , l )) + +[x ] where x = max (l ) Wie lauten min und max ? Was lässt sich über die Stabilität dieser beiden Algorithmen aussagen? 10.2 Funktionale Algorithmen 10-43 Extensionale und intensionale Beschreibungen Bisher wurden Listen durch Aufzählung oder Konstruktion beschrieben. Man spricht von einer extensionalen Beschreibung. Mengen werden implizit durch einen Ausdruck der Form {t (x ) | p (x )} angegeben. Beispiel: {x 2 | x ∈ N ∧ x mod 2 = 0} = {4, 16, . . .} Analog hierzu bedeutet [t (x ) | x ← l , p (x )] die Liste aller Werte t (x ), die man erhält, wenn x die Liste l durchläuft, wobei nur die Elemente aus l ausgewählt werden, die der Bedingung p (x ) genügen. [t (x ) | x ← l , p (x )] ist eine intensionale Definition. t (x ) ist ein Term. x ← l heißt Generator und p (x ) ist eine Auswahlbedingung. 10.2 Funktionale Algorithmen 10-44 Intensionale Beschreibungen [x | x ← [1 . . . 5]] = [1, 2, 3, 4, 5] [x 2 | x ← [1 . . . 5]] = [1, 4, 9, 16, 25] [x 2 | x ← [1 . . . 5], odd (x )] = [1, 9, 25] Eine intensionale Beschreibung kann auch mehrere Generatoren enthalten: [x 2 − y | x ← [1 . . . 3], y ← [1 . . . 3]] = [0, −1, −2, 3, 2, 1, 8, 7, 6] [x 2 − y | x ← [1 . . . 3], y ← [1 . . . x ]] = [0, 3, 2, 8, 7, 6] [x 2 − y | x ← [1 . . . 4], odd (x ), y ← [1 . . . x ]] = [0, 8, 7, 6] [x 2 − y | x ← [1 . . . 4], y ← [1 . . . x ], odd (x )] = [0, 8, 7, 6] Man vergleiche die Effizienz der beiden letzten Beschreibungen. 10.2 Funktionale Algorithmen 10-45 Intensionale Beschreibungen teiler (n) = [i | i ← [1 . . . n], n mod i = 0] teiler (18) = [1, 2, 3, 6, 9, 18] ggT (a , b ) = max ([d | d ← teiler (a ), b mod d = 0]) ggT (18, 8) = max ([d | d ← [1, 2, 3, 6, 9, 18], 8 mod d = 0]) = max ([1, 2]) = 2 primzahl (n) = (teiler (n) = [1, n]) primzahl (17) = (teiler (17) = [1, 17]) = true concat (l ) = [x | l 0 ← l , x ← l 0 ] concat ([[1, 2, 3],[4, 5, 6]]) = [1, 2, 3, 4, 5, 6] 10.2 Funktionale Algorithmen 10-46 Tupel Tupel sind Listen fester Länge. Beispiele: I I I (1.0, −3.2) als Darstellung für die komplexe Zahl 1 − 3.2i . (4, 27) als Abbildung eines Messzeitpunkts (4 ms ) auf einen Messwert (27 V ). (2, 3.4, 5) als Darstellung eines Vektors im R3 . Der Typ t eines Tupels ist das kartesische Produkt der seiner Elementtypen: t = t1 × t2 × . . . × tn Schreibweise für Elemente des Typs t : (x1 , x2 , . . . , xn ) Man nennt (x1 , x2 , . . . , xn ) ein n-Tupel. Tupel sind grundlegende Typen aller funktionalen Sprachen. 10.2 Funktionale Algorithmen 10-47 Tupel Auf der Basis von Tupeln lassen sich spezifische Datentypen definieren: I I date: int × text × int . Datumsangaben mit Werten wie (2, „Mai“, 2001). Es dürfen nur gültige Werte aufgenommen werden. rat: int × int . Rationale Zahlen mit Werten wie (2,3) für 23 . Das 2-Tupel (Paar) (1, 0) stellt keinen gültigen Wert dar. Beispiele für Funktionen auf rat: ratAdd , ratMult :rat × rat → rat kuerze :rat → rat kuerze (z , n) = (z div g , n div g ) where g = ggT (z , n) ratAdd ((z1 , n1 ), (z2 , n2 )) = kuerze (z1 n2 + z2 n1 , n1 n2 ) ratMult ((z1 , n1 ), (z2 , n2 )) = kuerze (z1 z2 , n1 n2 ) 10.2 Funktionale Algorithmen 10-48 Funktionen höherer Ordnung I Funktionen als Datentypen machen es möglich, Funktionen auf Funktionen anzuwenden. I Eine Funktion f : A → B ist vom Typ A → B . I Die Operation → sei rechtsassoziativ, d. h. A → B → C = A → (B → C ) 10.2 Funktionale Algorithmen 10-49 Currying I Das Currying vermeidet kartesische Produkte: Eine Abbildung f : A ×B →C kann als eine Abbildung f : A → (B → C ) = A → B → C gesehen werden. I Beispiel: f : int × int mit f (x , y ) = x + y entspricht fg : int → int → int mit f (x ) = gx : int → int und gx (y ) = x + y . Hintereinanderausführung: (fg (x ))(y ) = gx (y ) = x + y = f (x , y ) 10.2 Funktionale Algorithmen 10-50 Funktionen höherer Ordnung Funktionen können als Werte und Argumente auftreten. Beispiel: Ein Filter, der aus einer Liste diejenigen Elemente auswählt, die einer booleschen Bedingung genügen. Spezifikation: filter (p , l ) = [x | x ← l , p (x )] Definition: filter filter (p , []) : (t → bool ) × [t ] → [t ] = [] p (x ) :filter (p , x : l ) = x : filter (p , l ) filter (p , x : l ) = filter (p , l ) 10.2 Funktionale Algorithmen 10-51 Funktionen höherer Ordnung Fortsetzung zum Filter, Anwendung: p : int → bool even(i ) :p (i ) p (i ) = true = false filter (p , [1 . . . 5]) = [2, 4] 10.2 Funktionale Algorithmen 10-52 Deduktive Algorithmen deduktiver Algorithmus logische Aussagen Anfrage Auswertungsalgorithmus für Anfragen Logisches Paradigma Die wichtigste logische Programmiersprache ist Prolog. 10.3 Prädikatenlogik 10-53 Prädikatenlogik I Grundlage des logischen Paradigmas ist die Prädikatenlogik. I Beispiel einer Aussage: „Susanne ist Tochter von Petra“. I Eine Aussageform ist eine Aussage mit Unbestimmten: x ist Tochter von y . I Durch eine Belegung der Unbestimmten kann eine Aussageform in eine Aussage transformiert werden: x ← Susanne , y ← Petra . I Statt natürlichsprachiger Aussagen und Aussageformen, werden in deduktiven Algorithmen atomare Formeln verwendet: Tochter (x , y ). 10.3 Prädikatenlogik 10-54 Prädikatenlogik Alphabet: I Konstante: a , b , c , . . .. I Unbestimmte/Variable: x , y , z , . . .. I Prädikatssymbole: P , Q , R , . . . mit Stelligkeit. I Logische Verknüpfungen: ∧, ⇒, . . .. Atomare Formeln: P (t1 , . . . , tn ). Fakten: Atomare Formeln ohne Unbestimmte. Regeln haben die Form (αi ist atomare Formel): α1 ∧ α 2 ∧ · · · ∧ α n ⇒ α 0 α1 ∧ α2 ∧ · · · ∧ αn wird als Prämisse, α0 als Konklusion der Regel bezeichnet. 10.3 Prädikatenlogik 10-55 Beispiel Zwei Fakten: I Tochter (Susanne , Petra ) I Tochter (Petra , Rita ) Eine Regel mit Unbestimmten: I Tochter (x , y ) ∧ Tochter (y , z ) ⇒ Enkelin(x , z ) Die Ableitung neuer Fakten erfolgt analog zur Implikation: 1. Finde eine Belegung der Unbestimmten einer Regel, so dass auf der linken Seite (Prämisse) bekannte Fakten stehen. 2. Die rechte Seite ergibt den neuen Fakt. 10.3 Prädikatenlogik 10-56 Beispiel Belegung der Unbestimmten der Regel: x ← Susanne , y ← Petra , z ← Rita Anwendung der Regel ergibt neuen Fakt: Enkelin(Susanne , Rita ) (Erste) Idee deduktiver Algorithmen: 1. Definition einer Menge von Fakten und Regeln sowie einer Anfrage in Form einer zu prüfenden Aussage; z. B. Enkelin(Susanne , Rita ). 2. Prüfen und Anwenden der Regeln, bis keine neuen Fakten mehr erzeugt werden können. 3. Prüfen, ob Anfrage in Faktenmenge enthalten ist. 10.3 Prädikatenlogik 10-57 Deduktive Algorithmen I Ein deduktiver Algorithmus D besteht aus einer Menge von Fakten und Regeln. I Aus einem deduktiven Algorithmus sind neue Fakten ableitbar. Die Menge aller Fakten F (D ) enthält alle direkt oder indirekt aus D ableitbaren Fakten. I Ein deduktiver Algorithmus definiert keine Ausgabefunktion wie applikative oder imperative Algorithmen. Erst die Beantwortung von Anfragen liefert ein Ergebnis. I Eine Anfrage γ ist eine Konjunktion von atomaren Formeln mit Unbestimmten: γ = α1 ∧ α2 ∧ · · · ∧ αn 10.4 Deduktive Algorithmen 10-58 Beispiel: Addition zweier Zahlen Fakten: I suc (n, n + 1) für alle n ∈ Regeln: 1. true ⇒ add (x , 0, x ) 2. add (x , y , z ) ∧ suc (y , v ) ∧ suc (z , w ) ⇒ add (x , v , w ) Anfrage: add (3, 2, 5) liefert true. Auswertung: I Regel 1 mit der Belegung x = 3: add (3, 0, 3) I Regel 2 mit der Belegung x = 3, y = 0, z = 3, v = 1, w = 4: add (3, 1, 4) I Regel 2 mit der Belegung x = 3, y = 1, z = 4, v = 2, w = 5: add (3, 2, 5) 10.4 Deduktive Algorithmen 10-59 Beispiel: Addition zweier Zahlen I add (3, 2, x ) liefert x = 5. I add (3, x , 5) liefert x = 2. I add (x , y , 5) liefert (x , y ) ∈ {(0, 5), (1, 4), (2, 3), (3, 2), (4, 1), (5, 0)}. I add (x , y , z ) liefert eine unendliche Ergebnismenge. I add (x , x , 4) liefert x = 2. I add (x , x , x ) liefert x = 0. I add (x , x , z ) ∧ add (x , z , 90) liefert (x , z ) = (30, 60). Deduktive Algorithmen sind deklarativ (s. oben). Im Vergleich zu applikativen und imperativen Algorithmen sind sie sehr flexibel – und häufig ineffizient. 10.4 Deduktive Algorithmen 10-60 Auswertungsalgorithmus Dieser informelle nichtdeterministische Algorithmus wertet Anfragen aus: 1. Starte mit der Anfrage γ (anzusehen als Menge atomarer Formeln). 2. Suche Belegungen, die entweder I einen Teil von γ mit Fakten gleichsetzen (Belegung von Unbestimmten von γ) oder I einen Fakt aus γ mit einer rechten Seite einer Regel gleichsetzen (Belegungen von Unbestimmten in einer Regel). Setze diese Belegung ein. 3. Wende passende Regeln rückwärts an, ersetze also in γ die Konklusion durch die Prämisse. 4. Entferne gefundene Fakten aus der Menge γ. 5. Wiederhole diese Schritte bis γ leer ist. 10.4 Deduktive Algorithmen 10-61 Beispiel: Addition zweier Zahlen 1. add (3, 2, 5). 2. add (3, y 0 , z 0 ), suc (y 0 , 2), suc (z 0 , 5). 3. y 0 = 1, dadurch Fakt suc (1, 2) streichen. 4. add (3, 1, z 0 ), suc (z 0 , 5). 5. z 0 = 4, dadurch Fakt suc (4, 5) streichen. 6. add (3, 1, 4). 7. add (3, y 00 , z 00 ), suc (y 00 , 1), suc (z 00 , 4). 8. y 00 = 0, z 00 = 3 beide Fakten streichen. 9. add (3, 0, 3) streichen, damit zu bearbeitende Menge leer, also 10. true . 10.4 Deduktive Algorithmen 10-62 Beispiel: Addition zweier Zahlen 1. add (3, 2, x ). 2. add (3, y 0 , z 0 ), suc (y 0 , 2), suc (z 0 , x ). 3. y 0 = 1, dadurch Fakt suc (1, 2), streichen. 4. add (3, 1, z 0 ), suc (z 0 , x ). 5. add (3, y 00 , z 00 ), suc (y 00 , 1), suc (z 00 , z 0 ), suc (z 0 , x ). 6. y 00 = 0, dadurch Fakt suc (0, 1), streichen. 7. add (3, 0, z 00 ), suc (z 00 , z 0 ), suc (z 0 , x ). 8. z 00 = 3, dadurch Regel 2 erfüllt, streichen. 9. suc (3, z 0 ), suc (z 0 , x ). 10. z 0 = 4, dadurch Fakt suc (3, 4) streichen. 11. suc (4, x ). 12. x = 5, die zu bearbeitende Menge ist leer und eine Lösung für x bestimmt. 10.4 Deduktive Algorithmen 10-63 Deduktive Algorithmen I Für eine Anfrage können unendlich viele Lösungen existieren: add (x , y , z ). I Die Bestimmung aller möglichen Berechnungspfade kann durch Backtracking erfolgen. Das angegebene Verfahren ist sehr vereinfacht: I I I 10.4 Deduktive Algorithmen Wie wird verhindert, dass ein unendlich langer Weg eingeschlagen wird? Was ist mit Negationen? 10-64