Kapitel 2 Funktionale Programmierung Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Kapitel 2 Funktionale Programmierung Die Welt als Ausdruck Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Funktionales Programmieren Erstes Programmierparadigma: - einfache Prinzipien - sehr sauber (klare Semantik) - weniger ist mehr - die Wenigsten werden es kennen Wesentliche Techniken der Informatik: - Komposition als Programmiertechnik - Rekursion als Programmiertechnik - Induktion als Verifikationstechnik Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Funktionales Programm Ein funktionales Programm besteht aus den Teilen - Funktionen - Ausdrücke, die Funktionen verwenden. Eine Funktion besteht aus 2 Teilen - Kopf: Deklaration der Parameter und des Ergebnisses - Rumpf: Definition des Ergebnisses als Ausdruck. Semantik eines funktionalen Programms - Wert des Ausdrucks. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Beispiel Deklaration: Fahrenheit2Celsius : Zahl -> Zahl (Kopf) Fahrenheit2Celsius(f) = (f – 32) / 1,8 (Rumpf) Aufruf: Fahrenheit2Celsius(104) + 5 (Ausdruck) • Semantik des Programms: 45 • Annahmen: „Zahl”, „+”, „–”, „/” sind vordefiniert Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Funktion: Definition Relation: Teilmenge eines kartesischen Produkts R D x W (D: Definitionsmenge, W: Wertemenge) (Bsp.: D: W: Menge der Menschen, Menge der Haustiere, R: mag) Abbildung: Relation, die einem Element aus dem Definitionsbereich ein Element des Wertebereichs zuordnet. Man schreibt f: { D→W x ↦ f(x) Funktion: Eine Abbildung, die jedem Element aus D genau ein Element aus W zuordnet. Eine Funktion ist eine eindeutige Abbildung. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Funktion: Eigenschaften Quantoren: : : 1: „für alle” „es existiert ein” „es existiert genau ein” Eine Abbildung f aus D in W ist • „eindeutig” für x D W 1 f(x) • „eineindeutig” („injektiv”) f(x) • f von D auf W („surjektiv”) x D f(x) W, f(x) W x D 1 x W, D, Ist eine Abbildung injektiv und surjektiv, so nennt man sie bijektiv. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Definition Ausdruck Arithmetischer Ausdruck: Arithmetischer Ausdruck: z. Bsp: 8+4 oder ((4 + (3 * 7)) – (16 * (7 + 8))) Programm 2.1 #include <iostream> int main () { std::cout << } //E/A-Funktionen ((4+(3*7)) – (16*(7+8))) << std::endl; Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Definition Ausdruck Ganze Zahlen Arithmetischer Wie werden Zahlen Ausdruck: gespeichert? Datentyp int für ganze Zahlen z z = ± ( z1 z2 Vorzeichen m ! i a b i z=± … Mantisse zm )b zi {0,1,…,b-1} Basis i=0 Achtung: Es gibt nur endlich viele int Zahlen typisch: -2147483648 bis 2147483648 = 231 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Definition Ausdruck Ganze Zahldarstellung Zahlen Arithmetischer Wie • werden Üblich: Zahlen Dezimalzahlen, Ausdruck: gespeichert? d.h. Darstellung zur Basis b = 10. • In Rechnern werden Zahlen aber binär dargestellt (Zuse), d.h. b = 2. ± z1 z2 Darstellung Basis Zahlen Vorzeichen … dezimal 10 2 15 Mantisse 16 33 0,5 0,25 0,2 zm dual 2 10 1111 10000 100001 0,1 0,01 0,˙0˙0˙1˙1 zi Ziffern hexadezimal 16 2 F 10 21 0,8 0,4 0,˙3 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Definition Ausdruck Zahlen Gleitkommazahlen Wie Arithmetischer werdenfloat Zahlen Ausdruck: gespeichert? Datentypen und double für relle Zahlen x ± z1 z2 … zm b ± e1 e2 … en zi Ziffern Exponent: m Stellen Vorzeichen Mantisse: m Stellen Vorzeichen Mantisse zi Ziffern Basis Achtung: Es gibt nur endlich viele float und double Zahlen! Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Definition Ausdruck Zahlen Gleitkommazahlen Wie Arithmetischer werdenfloat Zahlen Ausdruck: gespeichert? Datentypen und double für relle Zahlen x … ± z1 z2 zm b ± e1 e2 … en zi Ziffern Exponent: m Stellen Vorzeichen Mantisse: m Löcher. Stellen z. Bsp. float Vorzeichen Mantisse Die Zahldarstellung hat Basis zi Ziffern 1,93456 1,93457 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Komposition Funktion <Rückgabetyp> <Funktionsname> (<formale Parameter>) { <Funktionsrumpf> } #include <iostream> int quadrat (int x) { return x*x; } int main () { std::cout << quadrat(3) << std::endl; return 0; } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Komposition Bsp: Berechnung der Fläche eines Kreises mit Durchmesser d KreisFläche : Z -> Z KreisFläche(d) = π ∗ (d/2) 2 ZylinderVolumen : Z x Z -> Z ZylinderVolumen(h, d) = KreisFläche(d) * h • Funktionsdefinition enthält beliebige Ausdrücke insbesondere Aufrufe von anderen Funktionen Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Bsp.: Rauminhalt Zylinder Programm 2.2 #include <iostream> using namespace std; double Quadrat (double q) { return q*q; } double KreisFlaeche (double d) Quadrat (d/2); } { return 3.141592653589793*Quadrat double ZylinderVolumen (double d, h) { return KreisFlaeche (d) * h; } int main () { cout << ZylinderVolumen (1.0, 3.0) << endl; } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt ggT Der größte gemeinsame Teiler zweier Zahlen n, m te natürliche Zahl, die beide Zahlen ohne Rest teilt. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt ist die größ- ggT Jedes m läßt sich darstellen als m = q ˙n + r, mit dem Quotienten q = m mod n und dem Rest r. Falls r = 0, ist m durch n teilbar und ggT(m,n) = n. Falls r > 0 und es gäbe ein s teilt, dann gilt: , das sowohl n als auch r ohne Rest q·n+r n r m = =q + =q·k+ℓ∈N s s s s Also teilt s auch m. Das größte s mit dieser Eigenschaft ist ggT von m, n und r : ggT(m,n) = ggT(n, m mod n) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Rekursion Berechnung des größten gemeinsamen Teilers mit dem Euklidischen Algorithmus ggT: N x N -> N ggT(a,b) = a, falls a = b ggT(a, b-a), falls a < b ggT(a-b, b), falls a > b • Rekursion: Die Definition einer Funktion verwendet die Funktion. • Die Katze beißt sich in den Schwanz. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Terminierung ist ein Problem! Bsp: schlecht: → schlecht(f) = schlecht(f+1) schlecht(5) Programm terminiert nicht wg. Rekursion! Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Korrektheit • Ein Programm ist korrekt, falls - partiell korrekt (d.h. liefert richtiges Ergebnis, falls es terminiert) - terminiert. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Theorie • Es gibt funktionale Programme, bei denen die Terminierung nicht entscheidbar ist. • Folgerung: Es gibt Funktionen, die nicht berechenbar sind. Es gibt Probleme, die keine Lösung haben! • Analogie: Gödelscher Unvollständigkeitssatz Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Terminierungsbeweise • Induktion bei Funktionen auf Identität: → Falls n = 0, dann Identität(n) = 0, andernfalls Identität(n) = 1 + Identität(n-1) • Induktionsprinzip: – Induktionsanfang: Beweise Terminierung für Eingabe 0 – Induktionsschluß: Beweise Terminierung für Eingabe n+1 unter Voraussetzung der Terminierung für Eingabe n (Induktionsvoraussetzung) Das Induktionsprizip ist eine grundlegende Eigenschaft der ganzen Zahlen Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Terminierung der Identität Beweis: • Induktionsanfang (IA) – Identität(0) terminiert, da „0“ terminiert • Induktionsvoraussetzung (IV) – n: Identität(n) terminiert • Induktionsschritt (IS) – Identität (n+1) = 1 + Identität(n) (laut Definition) – terminiert, da „Identität(n)“ terminiert (siehe IV) • Identität terminiert für alle Eingaben! Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt ◊ Partielle Korrektheit Beweis: • Induktionsanfang (IA) – Identität(0) liefert als Ergebnis 0 - laut Definition • Induktionsvoraussetzung (IV) – Identität(n) liefert als Ergebnis n • Induktionsschritt (IS) Identität(n+1) = 1 + Identität(n) (laut Defintion) = 1 + n (laut IV) = n + 1 (Kommutativität) • Identität(n) liefert als Ergebnis n für alle n aus Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt . ◊ Semantik (Bedeutung) • Substitionsmodell 1. Berechne Werte der Argumente der Funktion. 2. Ersetze im Rumpf jeden formalen Parameter durch den entsprechenden Wert. 3. Werte den Rumpf der Funktion aus. Bsp.: Quadrat(3) = *(3,3) = 9 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fakultät #include <iostream> int fak (int n) { if (n <= 1) return 1; else return n*fak(n-1); } fak(3) -> if (3 ≤ 1) return 1; else return 3*fak(3-1); -> 3 * fak(3-1) -> 3 * fak(2) -> 3 * if (2 ≤ 1) return 1; else return 2*fak(2-1); -> ... -> 3 * 2 * fak(1) -> ... -> 3 * 2 * 1 (Def. if) -> 6 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Lineare Rekursion • In jedem Rekursionsschritt ruft sich die Funktion einmal selber auf. • Beispiel: fak(5) = 120 fak(5) 24*5 fak(4) 6*4 fak(3) 2*3 fak(2) 1*2 fak(1)=1 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fiboanacci-Zahlen Fibonacci-Zahlen #include Leonardo<iostream> Fibonacci (1202): Modell für das Wachstum einer Kaninchenpopulation • Zum Zeitpunkt t = 0, 2 Kaninchen, davon die Hälfte weiblich. • Jedes Weibchen wirft einmal im Monat ein weibliches Junges. • Die jungen Weibchen sind nach einem Monat geschlechtsreif. Monat 1 2 a3n+1 4an 5 6 7 fortpflanzungsfähige Weibchen 1 1 √ a 2 1+ 5 ≈ → Φ = =3 n→∞ b 2 5 8 13 Nachwuchs w. 0 1 1 1.618 2 3 5 8 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fiboanacci-Zahlen Fibonacci-Zahlen #include <iostream> Zahlenfolge, bei der das nächste Glied die Summe der beiden vorangegangenen ist. a0 = 1 a1 = 1 ai+1 = ai + ai–1 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, … Eigenschaften: an+1 an √ 1+ 5 a ≈ 1.618 → Φ= = n→∞ b 2 Φ ist der Goldene Schnitt Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fiboanacci-Zahlen Fibonacci-Zahlen Goldener Schnitt #include <iostream> Teilung einer Strecke, so daß das Verhältnis der beiden Teile gleich dem der längeren Teilstrecke zur Gesamtstrecke ist. a+ b = a √ √ a b √ 1+ 5 a 1+ 5 a 2 1 + 5 a ≈ 1.618 Φ=Ï= =wird ≈ 1.618 Mit Φ = an+ hieraus Ï – Ï – 1 = 0 und = 1 = ≈ 1.618 Φ = → b 2 b 2 an n→∞ b 2 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fiboanacci-Zahlen Fibonacci-Zahlen Goldener Schnitt #include <iostream> a+ b = a an+1 an √ 1+ 5 a ≈ 1.618 → Φ= = n→∞ b 2 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt a b Fibonacci-Zahlen #include <iostream> long int fib (long int n) { if (n<=1) return 1; else return fib(n-2) + fib(n-1); } int main () { std::cout << "Fibonacci: " << fib(5) << "\n"; return 0; } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Baumrekursion fib(5) = (fib(4) + fib(3)) = ((fib(3) + fib(2)) + (fib(2) + fib(1))) = (((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) = ((((fib(1) + fib(0)) + 1) + (1 + 1)) + ((1 + 1 ) = ((((1 + 1) + 1 ) + 2) + (2 + 1)) = ((( 2 + 1) + 2 ) + 3 ) = ((( 3 + 2 ) + 3 ) =(5 +3) =8 Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt + fib(0)) + 1)) + 1)) Baumrekursion fib(n) ruft sich in jedem Schritt zweimal selbst auf fib(n) fib(n-2) fib(n-1) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Algorithmus fib(n) ruft sich in jedem Schritt zweimal selbst auf fib(n) fib(n-4) fib(n-6) fib(n-5) fib(n-2) fib(n-1) fib(n-3) fib(n-3) fib(n-5) fib(n-4) fib(n-2) fib(n-5) fib(n-4) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt fib(n-4) fib(n-3) Rechenaufwand • Anzahl der Auswertungen von fib bei Aufruf von fib(n): A(n) • fib(n) ruft fib(n-2) zweimal auf: A(n) ≥ 2 A(n-2) • Bezeichnet „/” die ganzzahlige Division ohne Rest, dann gilt: (1) A(n) ≥ 2n/2, n > 1, n . • Beweis durch Induktion: Induktionsanfang n = 2: A(2) = A(1) + A(0) = 2 ≥ 22/2 √ Induktionsannahme: (1) gelte für alle m ≤ n für ein festes n Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Rechenaufwand • Bezeichnet „/” die ganzzahlige Division ohne Rest, dann gilt: (1) A(n) ≥ 2n/2, n > 1, n . • Beweis durch Induktion: Induktionsanfang n = 2: A(2) = A(1) + A(0) = 2 ≥ 22/2 √ Induktionsannahme: (1) gelte für alle m ≤ n für ein festes n Induktionsschluß: A(n+1) ≥ 2 A(n-1) ≥ 2 2(n-1)/2 = 2(n+1)/2 => exponentielles Wachstum Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Komplexität Sei n ein typischer Problemparameter, etwa der Index des zu bestimmenden Folgenglieds, die Anzahl der Einträge in einer Kartei (Suchprozesse), die Anzahl von Unbekannten in einem Gleichungssystem. R(n) bezeichne den Bedarf an Ressourcen für die Berechnung z. Bsp. • Rechenzeit, • Anzahl der auszuführenden Operationen, • Speicherbedarf, • Plattenplatz. Man schreibt R(n) = O(f(n)), falls es von n unabhängige Konstanten c0, n0 gibt, so daß gilt: R(n) ≤ c0 f(n) n ≥ n0. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Komplexität -Beispiele • R(n) = O(1) konstante Komplexität • R(n) = O(log n) logarithmische Komplexität • R(n) = O(n) lineare Komplexität • R(n) = O(n log n) linear-logarithmische Komplexität • R(n) = O(n2) quadratische Komplexität • R(n) = O(np) polynomiale Komplexität • R(n) = O(2n) exponentielle Komplexität Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Iteratives Vorgehen Fibonacci-Folge: 1, 1, 2, 3, 5, 8, 13, 21, … Idee: Erzeuge die Folge von Anfang an: an = an-1 + an-2 . Dazu muß der Index n mitgeführt werden. Hierzu führen wir einen zusätzlichen Parameter ein: Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Fibonacci iterativ int { fibiter (int n, int i, int ai, int aii) if (i>=n) return ai; else return fibiter(n,i+1,ai+aii,ai); } int { fib (int n) return fibiter(n,1,1,1); } int main () { std::cout << "Fibonacci: " << fib(5) << "\n"; return 0; } Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Komplexität Die iterative Berechnung der Fibonacci-Zahl an ist von linearer Komplexität O(n). Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Newtonverfahren Wurzelberechnung mit dem Newtonverfahren Problem: f : R → R sei eine „glatte“ Funktion, a die Gleichung lösen (1) R. Wir wollen f (x) = a . Beispiel: f (x) = x2 Berechnung von Quadratwurzeln. ! Mathematik: (a) ist die positive Lö̈sung von x2 = a . Informatik: Algorithmus zur Berechnung des Werts von ! (a) . Ziel: Konstruiere ein Iterationsverfahren mit folgender Eigenschaft: zu einem Startwert x0 ≈ x finde x1, x2, . . ., welche die Lösung x immer besser approximieren. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Newtonverfahren Gleichung (1) ist gleichbedeutend mit (2) f (x) − a = 0 Wir suchen also eine Nullstelle der Funktion g (x) := f (x) – a oder im Falle der Quadratwurzel von g (x) := x2 - a . Hierzu nutzt man das Newtonverfahren. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Newtonverfahren Approximiere die Funktion durch ihre Tangente im Punkt x: f (x + h) ≈ f (x) + h ∗ f ′ (x). Da wir f (x+h) = 0 anstreben, können wir ausgehend von einem Startpunkt x0 eine nächste Approximation x1 bestimmen durch 0 = f (x0 ) + h ∗ f ′ (x0 ) = f (x0 ) + (x0 − x1 ) ∗ f ′ (x0 ), und damit f (x0 ) x1 = x0 + ′ f (x0 ) bzw. xi+1 f (xi ) = xi + ′ . f (xi ) Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Newtonverfahren Für Quadratwurzel: xi+1 1 a = (xi + ) 2 xi Abbruchkriterium: | f (xn ) − a |< ϵ für eine vorgegebene kleine Zahl ℇ > 0. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Quadratwurzelberechnung #include <iostream> using namespace std; double Betrag(double x) { return ((x<0) ? -x : x);} bool gut_genug (double x, double a) { return (Betrag(x*x-a) <= 1e-15); } double wurzeliter (int i, double xn, double a) { if (gut_genug(xn,a)||(i>1000)) return xn; cout << "i= " << i << ", x= " << xn << endl; wurzeliter(i+1,(xn+a/xn)*0.5,a); return 0; } double wurzel (double a) { return wurzeliter(1,1.0,a); } int main () { cout.precision(20); cout << wurzel(2) << endl; return 0;} Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt Komplexität Übersicht Funktionales Prog. Grundelemente: • Auswertung von arithmetischen und bedingten Ausdrücken, • Konstruktion neuer Funktionen. Namen treten auf als • Bezeichner von Funktionen, • formale Parameter in Funktionen. Substitutionsmodell • Ersetze formale Parameter durch aktuelle Werte und werte Funktionsrumpf aus. Berechenbarkeit • Alles, was berechenbar ist, kann mit funktionaler Programmierung umgesetzt werden. Programmieren 1 G. Wittum, G-CSC, Universität Frankfurt