Info 1.1, Kap. 2, Funktionale Programmierung - G-CSC

Werbung
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
Herunterladen