• Keine Ergebnisse gefunden

Rekursion/Funktionale Programmierung: Einführung und Begriffe

N/A
N/A
Protected

Academic year: 2022

Aktie "Rekursion/Funktionale Programmierung: Einführung und Begriffe"

Copied!
85
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Rekursion/Funktionale Programmierung:

Einführung und Begriffe

5.1 Einführung und Begriffe

5.2 Beispiele rekursiver Methoden in Java 5.3 Ein Blick auf funktionales Programmieren

(2)

Quersumme

Als einführendes Beispiel sehen wir uns zwei statische Java-Methoden zur Berechnung der Quersumme zweier positiver Zahlen an.

6 → 6

15 → 1 + 5 = 6

789 → 7 + 8 + 9 = 24 → 2 + 4 = 6

(3)

Quersumme (iterative Methode)

static int g(int n) { int s = n;

while (s > 9) { int i = s;

s = 0;

do {

s = s + i%10;

i = i / 10;

} while (i > 0);

}

return s;

}

(4)

Quersumme (rekursive Methode)

static int f(int n) {

return n <= 9 ? n : f( f(n/10) + n%10 );

}

(5)

Quersumme

• Iterative Methode: 10 Zeilen

• Rekursive Methode: 1 Zeile (Weniger als 1 Zeile ist nicht möglich.)

• Rekursion ist eine mächtige Programmiertechnik.

• Wir haben das imperative und das objektorientierte Paradigma kennengelernt.

• In diesem Abschnitt sehen wir uns die Rekursion als Programmiertechnik sowie das funktionale/deklarative Paradigma an.

(6)

Quersumme

Der obige rekursive Algorithmus zur Berechnung der Quersumme lautet als Haskell-Programm:

f :: Integer -> Integer f n | n <= 9 = n

| otherwise = f(f(n ‘div‘ 10) + (n ‘mod‘ 10)) f 789

6

(7)

Partielle und totale Funktionen

Eine partielle Funktion

f : A −→ B

ordnet jedem Element x einer Teilmenge DfA 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}, f (x) = 1 x

Algorithmen können undefinierte Ausdrücke enthalten und müssen nicht in jedem Fall terminieren, d. h.:

(8)

Definition von Funktionen

• Wenn der Definitionsbereich einer Funktion endlich ist, lässt sie sich durch Angabe aller Funktionswerte in einer Tabelle definieren.

• Beispiel: ∧ : B × B → B

false false false false true false true false false true true true

(9)

Definition von Funktionen

• In vielen Fällen wird eine Funktion f : AB durch einen Ausdruck, der zu jedem Element aus Df genau einen Wert von B liefert, beschrieben.

• Beispiel: max : R × R → R

max(x, y) =

(x xy y x < y

= if xy then x else y fi

(10)

Rekursive Definitionen

Die Funktion f : N −→ N wird durch

f (n) =









1 n = 0,

1 n = 1,

f n2

n ≥ 2, n gerade, f (3n + 1) n ≥ 2, n ungerade.

rekursiv definiert.

(11)

Auswertung von Funktionen

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?

(12)

Formen der Rekursion

• Lineare Rekursion

In jedem Zweig einer Fallunterscheidung tritt die Rekursion höchstens einmal auf.

Bei der Auswertung ergibt sich eine lineare Folge von rekursiven Aufrufen.

• Endrekursion

Der Spezialfall einer linearen Rekursion bei dem in jedem Zweig die Rekursion als letzte Operation auftritt. Endrekursionen können sehr effizient implementiert

werden.

(13)

Formen der Rekursion

• Verzweigende Rekursion oder Baumrekursion n

k

=





1 k = 0, k = n,

n − 1 k − 1

+

n − 1 k

0 < k < n.

• Geschachtelte Rekursion f (n,m) =





m + 1 n = 0,

f (n − 1, 1) n 6= 0,m = 0, f (n − 1,f (n,m − 1)) n 6= 0,m 6= 0.

(14)

Formen der Rekursion

• Verschränkte Rekursion oder wechselseitige Rekursion Der rekursive Aufruf erfolgt indirekt.

even(n) =

(true n = 0, odd(n − 1) n > 0.

odd(n) =

(false n = 0,

even(n − 1) n > 0.

(15)

Fakultät

Definition:

n! =

(1 n ≤ 0 n · (n − 1)! sonst Auswertung:

4! = 4 · 3! = 4 · 3 · 2! = 4 · 3 · 2 · 1! = 4 · 3 · 2 · 1 = 24

(16)

Fakultät

class Fakultaet {

static long fak(long n) { if (n <= 0)

return 1;

else

return n * fak(n-1);

}

static long f(long n) {

return n <= 0 ? 1 : n * f(n-1);

}

public static void main(String[] args) { for (int i = 0; i <= 20; i++) {

System.out.println(i + "! = " + fak(i));

}

(17)

Fibonacci-Folge

Definition:

fib(n) =





0 n = 0

1 n = 1

fib(n − 1) + fib(n − 2) n ≥ 2 Funktionswerte:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...

(18)

Fibonacci-Folge

Auswertung:

fib 2 fib 1 fib 0 fib 4

fib 3

fib 1 fib 2

fib 1 fib 0

fib 3

fib 1 fib 2

fib 1 fib 0 fib 5

1 1 1

1 0

0

(19)

Fibonacci-Folge

class Fibonacci {

static long fib(long n) { if ((n == 0) || (n == 1))

return n;

else

return fib(n-1) + fib(n-2);

}

public static void main(String[] args) { for (int i = 0; i <= 20; i++) {

System.out.println(i + ": " + fib(i));

} }

(20)

Fibonacci-Folge

Die Anzahl der Aufrufe g(n) ist gegeben durch die folgende Rekurrenzgleichung:

g(n) =

(1 n = 0,n = 1 1 + g(n − 1) + g(n − 2) n ≥ 2

Die Funktion g wächst exponentiell. Beispielsweise ist g(5) = 15, vgl. obiges Aufrufdiagramm.

Zur Lösung von Rekurrenzgleichungen s. Abschnitt 4.4 in Struckmann/Wätjen.

(21)

Fibonacci-Folge/Dynamische Algorithmen

Am Aufrufdiagramm von fib(5) erkennen wir, dass etliche Funktionswerte mehrfach berechnet werden. Die Idee der dynamischen Algorithmen ist es, Ergebnisse von Berechnungen zu speichern, so dass die Berechnungen nicht mehrfach durchgeführt werden müssen.

Die folgende Methode realisiert die Fibonacci-Folge mit einem dynamischen

Algorithmus, indem sie bereits berechnete Funktionswerte in einem Array speichert.

Die Array-Elemente werden mit dem Wert −1 vorbesetzt. Die Methode verwendet eine Hilfsmethode.

Implementieren Sie die beiden Algorithmen und vergleichen Sie die Laufzeiten bei der Berechnung einiger Funktionswerte, zum Beispiel fib(40).

(22)

Fibonacci-Folge

static long fib(int n) { if (n==0||n==1) return n;

long[] a = new long[n+1];

a[0]= 0;

a[1]= 1;

for (int i=2; i<=n; i++) a[i]=-1;

return f(a, n);

}

static long f(long[] a, int n) { if (a[n]>=0) return a[n];

return a[n]=f(a,n-1)+f(a,n-2);

(23)

Algorithmus von Euklid

Rekursive Version:

ggT(a,b) =

(a, falls b = 0,

ggT(b,a mod b), falls b 6= 0.

Auswertung:

ggT(36, 52) → ggT(52, 36) → ggT(36, 16) → ggT(16, 4) → ggT(4, 0) → 4

ggT(36, 52) = 4

(24)

Algorithmus von Euklid

class Euklid {

static long ggt(long a, long b) { if (b == 0)

return a;

else

return ggt(b, a % b);

}

public static void main(String[] args) { System.out.println(ggt(36,52));

} }

(25)

Exponentiation

rekursiv:

bn =

(1 n = 0 b · bn−1 n ≥ 1 iterativ:

bn = b · b · ... · b

| {z }

n-mal

schnell (rekursiv):

bn =





1 n = 0

bn/22

n ≥ 1, n gerade b · bn−1 n ≥ 1, n ungerade

(26)

Schnelle Exponentiation

long exp(long b, long n) { if (n == 0)

return 1;

else if (n % 2 == 0) { long l = exp(b, n / 2);

return l * l;

} else

return b * exp(b, n - 1);

}

(27)

Summe

Es soll die Summe der Zahlen von a bis b, d. h. Pb

i=a i berechnet werden. Es gilt

b

X

i=a

i = a +

b

X

i=a+1

i.

long sum(long a, long b) { if (a == b)

return b;

else

return a + sum(a + 1, b);

(28)

Skalarprodukt

Es soll das Skalarprodukt der Vektoren a und b rekursiv berechnet werden.

int skalar(int[] a, int[] b, int n) { if (n < 0)

return 0;

else {

return a[n] * b[n] + skalar(a, b, n - 1);

} }

(29)

Skalarprodukt

int skalar(int[] a, int[] b) { // eine ineffiziente Lösung int l = a.length;

if (l == 0) return 0;

else {

int[] aa = new int[l - 1];

int[] bb = new int[l - 1];

for (int i = 0; i <= l - 2; i++) { aa[i] = a[i];

bb[i] = b[i];

}

return a[l - 1] * b[l - 1] + skalar(aa, bb);

}

(30)

Semantik rekursiv definierter Funktionen 1

(∗) f : N → N f (n) =

(0 n = 0, f (n + 1) n > 0.

• Operationelle Semantik/Ersetzungssystem:

f (0) = 0

f (1) = f (2) = f (3) = ...

• Die Auswertung von (∗) terminiert für n > 0 nicht. Daher ist D(f ) = {0} und

(31)

Semantik rekursiv definierter Funktionen 2

• Denotationale Semantik/Gleichungssystem: Wir fassen (∗) als Gleichung für eine unbekannte Funktion f auf.

• Für eine partielle Funktion f bedeutet f (k) = f (l), dass die Werte f (k) und f (l) beide undefiniert oder aber definiert und gleich sind.

• Alle Funktionen f : N → N mit D(f ) = {0} und f (0) = 0 oder D(f ) = {0, 1, 2, ...} = N und

f (0) = 0,f (1) = f (2) = f (3) = ... = a

mit a ∈ N erfüllen die Gleichung (∗). Die operationelle Lösung ist die Lösung mit dem kleinsten Definitionsbereich. Die Lösung von (∗) ist also nicht eindeutig.

(32)

Semantik rekursiv definierter Funktionen 3

a : N × N → N a(m,n) =





n + 1 m = 0,

a(m − 1, 1) m 6= 0,n = 0, a(m − 1,a(m,n − 1)) m 6= 0,n 6= 0.

Outermost: a(1, 1) = a(0,a(1, 0)) = a(1, 0) + 1 = a(0, 1) + 1 = (1 + 1) + 1 = 3 Innermost: a(1, 1) = a(0,a(1, 0)) = a(0,a(0, 1)) = a(0, 2) = 3

Mixed: a(1, 1) = a(0,a(1, 0)) = a(0,a(0, 1)) = a(0, 1) + 1 = 3 a ist sog. die Ackermann-Funktion. Es gilt:

(33)

Semantik rekursiv definierter Funktionen 4

f : N × N → N f (m,n) =

(1 m = 0, f (m − 1,f (1, 0)) m 6= 0.

Outermost: f (1, 0) = f (0,f (1, 0)) = 1 (1, 0) ∈ D(f ) Innermost: f (1, 0) = f (0,f (1, 0)) = f (0,f (0, f (1, 0))) = ... (1, 0) ∈/ D(f )

(34)

Rekursion/Funktionale Programmierung:

Beispiele rekursiver Methoden in Java

5.1 Einführung und Begriffe

5.2 Beispiele rekursiver Methoden in Java 5.3 Ein Blick auf funktionales Programmieren

(35)

Newton-Verfahren

Es soll rekursiv die Nullstelle der Funktion f (x) = x3 + 3x + 5

mithilfe des Newton-Verfahrens berechnet werden:

f 0(x) = 3x2 + 3 xn+1 = xnf (xn)

f 0(xn) –80

–60 –40 –20 0 20 40 60 80

y

–4 –3 –2 –1 1 2 3 4

x

(36)

Newton-Verfahren

class Newton { double eps;

static double f(double x) { return x*x*x+3*x+5;

}

static double fs(double x) { return 3*x*x+3;

}

Newton(double eps) { this.eps = eps;

(37)

Newton-Verfahren

double newton(double x) { System.out.println(x);

if (Math.abs(f(x)) <= eps) return x;

else

return newton(x - f(x) / fs(x));

}

public static void main(String[] args) { Newton n = new Newton(0.00001);

System.out.println(n.newton(1.0));

}

(38)

Intervallschachtelung

Problem: Gegeben seien eine stetige reelle Funktion f und Intervallgrenzen a,b ∈ R,a < b. Wir wollen annehmen, dass f (a) und f (b) unterschiedliches

Vorzeichen aufweisen. Nach dem Zwischenwertsatz besitzt f im Intervall axb dann wenigstens eine Nullstelle.

Es soll eine Java-Methode

double bisek(double a, double b, double eps)

programmiert werden, die a und b sowie eine Fehlerschranke eps als Eingaben akzeptiert und eine Nullstelle von f im gegebenen Intervall als Ergebnis liefert.

(39)

Intervallschachtelung

Lösungsidee: Wir halbieren das Intervall schrittweise, sodass die Bedingung, dass f an den Intervallgrenzen Werte mit unterschiedlichen Vorzeichen annimmt, erhalten bleibt. Damit befindet sich nach jedem Schritt eine Nullstelle von f im aktuellen Intervall. Das Verfahren bricht ab, wenn die Intervallgrenzen nahe genug beieinander liegen.

(40)

Intervallschachtelung

static double bisek(double a, double b, double eps) { double m = (a + b) * 0.5;

if (Math.abs(a - b) < eps) return m;

else if (f(a) * f(m) > 0) a = m;

else

b = m;

return bisek(a, b, eps);

}

(41)

Rekursionen

• Rekursive Problemlösungen dienen in erster Linie der Einsicht in das Problem.

Häufig ist jedoch die rekursive Formulierung auch effizient (z. B. Quicksort) oder eine iterative Formulierung nur schwierig zu finden.

• Wir geben jetzt drei Beispiele – einen Geldwechselalgorithmus, die „Türme von Hanoi“ sowie das Acht-Damen-Problem – an, für die rekursive Lösungen auf der Hand liegen, iterative aber nur schwer zu finden sind.

(42)

Wechseln eines Geldbetrags

Es soll die Anzahl der Möglichkeiten, ein 10-Cent-Stück in 1-, 2- oder 5-Cent-Stücke zu wechseln, berechnet werden. Die gesuchte Anzahl beträgt 10:

• 5+5

• 5+2+2+1

• 5+2+1+1+1

• 5+1+1+1+1+1

• 2+2+2+2+2

• 2+2+2+2+1+1

• 2+2+2+1+1+1+1

• 2+2+1+1+1+1+1+1

(43)

Wechseln eines Geldbetrags

Problem: Es gibt k ≥ 1 verschiedene Münzen mit den Beträgen b0,b1, ... ,bk−1 mit 0 < b0 < b1 < ... < bk−1. Auf wie viele Arten kann der Betrag a gewechselt werden?

Idee: Wir verwenden einen rekursiven Algorithmus. Wenn die größte Münze nicht verwendet wird, ist die gesuchte Anzahl gleich der Anzahl der Möglichkeiten, den

Betrag mit den k − 1 anderen Münzen zu wechseln. Wenn die größte Münze benutzt wird, ist die gesuchte Zahl gleich der Anzahl der Möglichkeiten, abk−1 mit allen k Münzen zu wechseln.

Beobachtung: Bei jedem Schritt wird entweder die Anzahl der verwendeten Münzsorten oder aber der zu wechselnde Betrag kleiner.

(44)

Wechseln eines Geldbetrags

Als Funktion erhalten wir:

f (a,k) =





0, a < 0 oder k = 0,

1, a = 0,

f (a,k − 1) + f (a − bk−1,k), sonst.

(45)

Wechseln eines Geldbetrags

class Wechsel {

static int[] b = {1,2,5,10,20,50,100,200};

static int wechsel(int a, int k) { if ((a < 0) || (k == 0))

return 0;

else if (a == 0) return 1;

else

return wechsel(a, k - 1) + wechsel(a - b[k - 1], k);

}

public static void main(String[] args) { System.out.println(wechsel(10,3));

}

(46)

Türme von Hanoi

Problem: Gegeben seien n Scheiben unterschiedlichen Durchmessers, die der Größe nach geordnet zu einem Turm geschichtet sind. Die größte Scheibe liegt dabei unten.

Der Turm steht auf Platz 1. Unter Verwendung des Hilfsplatzes 3 soll der Turm auf Platz 2 transportiert werden.

Beim Transport sind die folgenden Bedingungen einzuhalten:

• In jedem Schritt darf nur eine Scheibe – und zwar die oberste eines Turms – bewegt werden.

(47)

Türme von Hanoi

Beispiel: n = 3

Wir führen die folgenden Schritte aus:

Von 1 nach 2.

Von 1 nach 3.

Von 2 nach 3.

Von 1 nach 2.

Von 3 nach 1.

Von 3 nach 2.

Von 1 nach 2.

(48)

Türme von Hanoi

Lösungsidee: Wir benutzen den folgenden rekursiven Algorithmus:

• Transportiere n − 1 Scheiben von 1 nach 3.

• Transportiere die verbliebene Scheibe von 1 nach 2.

• Transportiere n − 1 Scheiben von 3 nach 2.

(49)

Türme von Hanoi

class Hanoi {

static void hanoi(int n, int a, int z) { if (n > 1)

hanoi(n - 1, a, 6 - (a + z));

System.out.println("Von " + a + " nach " + z + ".");

if (n > 1)

hanoi(n - 1, 6 - (a + z), z);

}

public static void main(String[] args) { hanoi(3,1,2);

} }

(50)

Das Acht-Damen-Problem

Problemstellung: Es sind acht Damen so auf einem Schachbrett aufzustellen, dass sie sich gegenseitig nicht bedrohen.

Beispiel:

|D| | | | | | | |

| | | | | | |D| |

| | | | |D| | | |

| | | | | | | |D|

| |D| | | | | | |

| | | |D| | | | |

(51)

Das Acht-Damen-Problem

Lösungsidee:

• Das Spielfeld wird als eindimensionales Feld der Länge 8 gespeichert. Es gilt brett[j] == i genau dann, wenn sich in Zeile i und Spalte j eine Dame befindet.

• Wir schreiben drei Methoden:

◦ ausgabe gibt eine gefundene Lösung auf dem Bildschirm aus.

◦ bedroht überprüft, ob die zuletzt gesetzte Dame von einer bereits vorher platzierten Dame geschlagen werden kann.

◦ setze versucht, eine Dame in die nächste Spalte zu stellen. Diese Methode arbeitet rekursiv von links nach rechts.

(52)

Das Acht-Damen-Problem

/** Gibt das Schachbrett auf dem Bildschirm aus. */

public static void ausgabe(int[] brett) {

System.out.println(); // Leerzeile vorher for (int i=0; i < 8; i++) { // Anzahl der Zeilen

for (int j=0; j < 8; j++) // Anzahl der Spalten

System.out.print("|" + ((i == brett[j]) ? ’D’ : ’ ’));

System.out.println("|"); // Zeilenende }

System.out.println(); // Leerzeile hinterher }

(53)

Das Acht-Damen-Problem

/** Testet, ob die Dame in "spalte" von einer anderen geschlagen werden kann. */

public static boolean bedroht(int[] brett, int spalte) { // Teste zuerst, ob eine Dame in derselben Zeile steht.

for (int i=0; i < spalte; i++) if (brett[i] == brett[spalte])

return true;

// Teste dann, ob in der oberen Diagonale eine Dame steht.

for (int i = spalte-1, j = brett[spalte]-1; i >= 0; i--,j--) if (brett[i] == j)

(54)

Das Acht-Damen-Problem

// Teste danach, ob in der unteren Diagonale eine Dame steht.

for (int i = spalte-1, j = brett[spalte]+1; i >= 0; i--,j++) if (brett[i] == j)

return true;

// Wenn das Programm hier ist, steht die Dame "frei".

return false;

}

Die beiden letzten for-Anweisungen besitzen Initialisierungs- und Update-Listen.

(55)

Das Acht-Damen-Problem

/** Sucht rekursiv eine Lösung des Problems. */

public static boolean setze(int[] brett, int spalte) { // Sind wir fertig?

if (spalte == 8) { ausgabe(brett);

return true;

}

(56)

Das Acht-Damen-Problem

// Suche die richtige Position für die neue Dame.

for (int i=0; i < 8; i++) {

brett[spalte] = i; // Probiere jede Stelle aus.

if (bedroht(brett,spalte)) // Falls Dame nicht frei steht, continue; // versuche die nächste Stelle.

boolean success = setze(brett,spalte+1);

if (success) // <--- return true; // <--- }

// Wenn das Programm hier angekommen ist, // stecken wir in einer Sackgasse.

(57)

Das Acht-Damen-Problem

/** Initialisiert das Schachbrett und ruft die Methode "setze" auf. */

public static void main(String[] args) {

int[] feld = {0,0,0,0,0,0,0,0}; // Initialisiere Spielfeld.

setze(feld,0); // Starte am linken Rand.

}

(58)

Das Acht-Damen-Problem

• Das obige Programm bricht ab, sobald es eine Lösung gefunden hat.

• Wenn alle Lösungen berechnet werden sollen, müssen lediglich die beiden durch

<---

gekennzeichneten Zeilen aus der Methode setze entfernt werden. Das Programm gibt dann 92 Stellungen aus, von denen sich aber etliche nur durch Drehungen oder Spiegelungen unterscheiden.

(59)

Sortieren

• Wir sehen uns jetzt den Algorithmus „Quicksort“ als Beispiel für einen effizienten rekursiven Algorithmus an.

• Zur Einführung wiederholen wir zunächst kurz das Bubblesort-Verfahren.

• Beide Algorithmen und ihre Eigenschaften werden in der Vorlesung „Algorithmen und Datenstrukturen“ ausführlich besprochen.

(60)

Bubblesort

Comparable[] bubbleSort(Comparable[] objs) { boolean sorted;

do {

sorted = true;

for (int i = 0; i < objs.length-1; ++i) { if (objs[i].compareTo(objs[i + 1]) > 0) {

Comparable tmp = objs[i];

objs[i] = objs[i + 1];

objs[i + 1] = tmp;

sorted = false;

} }

} while (!sorted);

(61)

Quicksort

Problem: Gegeben ist ein Feld objs, dessen Elemente paarweise vergleichbar sind.

Das Teilfeld objs[l] .. objs[r] ist zu sortieren.

Grundidee des Algorithmus:

• Suche ein Pivotelement, z. B. objs[k] mit k = l+r2 .

• Teile das Feld, sodass alle Elemente des linken Felds kleiner als das Pivotelement und alle Elemente des rechten Felds größer oder gleich dem Pivotelement sind.

• Wende das Verfahren rekursiv auf die beiden Teilfelder an.

(62)

Quicksort

Comparable[] quickSort(Comparable[] objs, int l, int r) { if (l < r) {

int i = l, j = r,

k = (int) ((l + r) / 2);

Comparable pivot = objs[k];

(63)

Quicksort

do {

while (objs[i].compareTo(pivot) < 0) i++;

while (pivot.compareTo(objs[j]) < 0) j--;

if (i <= j) {

Comparable t = objs[i];

objs[i] = objs[j];

objs[j] = t;

i++;

j--;

}

} while (i < j);

objs = quickSort(objs, l, j);

objs = quickSort(objs, i, r);

}

return objs;

(64)

Wrapper-Klassen

Problem:

• Primitive Datentypen sind keine Referenztypen.

• Bubblesort und Quicksort können daher in der obigen Form nicht zum Sortieren von Zahlen verwendet werden.

Lösung:

Man verwende Wrapper-Klassen!

(65)

Wrapper-Klassen

• Eine Wrapper-Klasse kapselt einen primitiven Wert in einer objektorientierten Hülle und stellt Methoden zum Zugriff auf diesen zur Verfügung. Der Wert kann nicht verändert werden.

• Wrapper-Klassen ermöglichen es, primitive Datentypen und Referenztypen einheitlich zu behandeln.

• Zu jedem primitiven Datentyp existiert eine Wrapper-Klasse:

Boolean, Char, Byte, Short, Integer, Long, Float, Double, Void.

(66)

Wrapper-Klassen

• Konstruktor mit dem jeweiligen Typ als Parameter:

Integer(int i) Float(float f) . . .

• Konstruktor mit einer Zeichenkette als Parameter:

Integer(String s) Float(String s) . . .

(67)

Wrapper-Klassen

• Einige Methoden der Klasse Integer:

int intValue()

int String toString()

int compareTo(Integer anotherInteger) static int parseInt(String s)

static int parseInt(String s, int radix)

• Einige Konstante:

MIN_VALUE MAX_VALUE NaN

POSITIVE_INFINITY

(68)

Quicksort

int n = ...;

Integer[] a = new Integer[n];

a[0] = new Integer(5);

a[1] = new Integer(-5);

a[2] = new Integer(0);

a[3] = new Integer(1);

...

Sort.quickSort(a,0,a.length-1);

(69)

Eine Schnittstelle zum Sortieren

interface Sortieren {

Comparable[] sort(Comparable[] objs);

}

class BubbleSort

implements Sortieren { Comparable[] feld;

...

}

class QuickSort

implements Sortieren { Comparable[] feld;

...

}

(70)

Autoboxing und Unboxing 1

• Um Werte der primitiven Datentypen als Objekte zu behandeln, müssen sie in Wrapper-Klassen eingewickelt werden.

• Um z. B. einen int-Wert als Objekt behandeln zu können, muss ein Integer-Objekt erzeugt werden:

Integer iWrapper = new Integer(4500)

• Um den eingewickelten Wert auszulesen, muss eine entsprechende Methode der Wrapper-Klasse aufgerufen werden:

int iValue = iWrapper.intValue()

(71)

Autoboxing und Unboxing 2

• Beispiel:

Object obj = 4500; // Autoboxing int i = (Integer) obj; // Unboxing

• Autoboxing und Unboxing gibt es für alle Wrapper-Klassen:

Object obj = 3.14; // Autoboxing double d = (Double) obj; // Unboxing

(72)

Autoboxing und Unboxing 3

int n = ...;

Integer[] a = new Integer[n];

a[0] = 5; // Autoboxing a[1] = -5; // Autoboxing a[2] = 0; // Autoboxing a[3] = 1; // Autoboxing ...

Sort.quickSort(a,0,a.length-1);

int s = 0;

for (int i = 0; i < n; ++i)

(73)

Rekursion/Funktionale Programmierung:

Ein Blick auf funktionales Programmieren

5.1 Einführung und Begriffe

5.2 Beispiele rekursiver Methoden in Java 5.3 Ein Blick auf funktionales Programmieren

(74)

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 : (A1A2) × A3B

g : A → (B1B2)

h : (A → A ) → (B → B )

(75)

Funktionen höherer Ordnung

Beispiele:

• Summe:

b

X

i=a

f (i)

• Komposition von Funktionen: fg

• Auswahl zwischen Funktionen: if p then f else g fi

• Bestimmtes Integral:

Z b a

f (x) dx

(76)

Funktionale Algorithmen

Ein Algorithmus heißt funktional, wenn die Berechnungsvorschrift mittels einer Sammlung von Funktionen definiert wird. Die Funktionsdefinitionen dürfen insbesondere Rekursionen und Funktionen höherer Ordnung enthalten.

(77)

Funktionale Algorithmen

Beispiel:

f (0) = 0 f (1) = 1

f (n) = nf (n − 2)

Wenn wir als Datenbereich die Menge der ganzen Zahlen zugrundelegen, berechnet dieser Algorithmus die Funktion f : Z → Z mit Df = N und

f (n) =











0 n gerade

n−1 2

Y(2i + 1) n ungerade

(78)

Funktionale Programmiersprachen

Programmiersprachen, die in erster Linie für die Formulierung funktionaler

Algorithmen gedacht sind, heißen funktional. Funktionale Programmiersprachen sind beispielsweise

• Lisp, ML, SML,

• Scheme,

• Scala (Erweiterung von Java) und

• Haskell.

(79)

Lisp und Scheme

• Lisp wurde Ende der 50er Jahre von John McCarthy entwickelt.

• Im Laufe der Jahre wurden viele Lisp-Dialekte, u. a. Common Lisp und Scheme definiert.

• Die erste Version von Scheme stammt aus dem Jahre 1975. Autoren waren Guy Lewis Steele Jr. und Gerald Jay Sussman.

• Lisp und Scheme werden in der Regel interpretiert, nicht compiliert.

(80)

Algorithmus von Euklid

Funktional geschrieben hat der

Algorithmus von Euklid die Form:

ggT(a, 0) = a

ggT(a,b) = ggT(b,a mod b)

(81)

Algorithmus von Euklid

Auswertung:

ggT(36, 52) → ggT(52, 36) → ggT(36, 16) → ggT(16, 4) → ggT(4, 0) → 4

ggT(36, 52) = 4

(82)

Scheme: Algorithmus von Euklid

Der funktionale Algorithmus von Euklid lautet als Scheme-Programm:

(define (ggT a b) (if (= b 0)

a

(ggT b (remainder a b)))) (ggT 36 52)

4

(83)

Haskell

• Haskell ist eine funktionale Programmiersprache.

• Haskell Brooks Curry (1900–1982) war ein amerikanischer Mathematiker und Logiker.

• Erste Überlegungen zur Programmiersprache Haskell stammen aus dem Jahr 1987.

Die Version Haskell 98 wurde 2003 veröffentlicht. Die aktuelle Version Haskell’

(Haskell Prime, Haskell 2010) stammt vom Dezember 2009.

• Für Haskell existieren freie Compiler und Interpreter (s. http://haskell.org).

• Es gibt etliche sog. Sprachderivate.

Beispiele: Parallel Haskell, Haskell++ (objektorientiert), ...

(84)

Haskell: Algorithmus von Euklid

Der funktionale Algorithmus von Euklid lautet als Haskell-Programm:

ggT :: Integer -> Integer -> Integer ggT a b | b == 0 = a

| otherwise = ggT b (a ‘mod‘ b) ggT 36 52

4

(85)

Haskell: Quicksort

Der Quicksort-Algorithmus lautet als Haskell-Programm:

qsort [] = []

qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger where

smaller = [a | a<-xs, a<=x]

larger = [a | a<-xs, a>x]

In der Vorlesung „Programmieren für Fortgeschrittene“ erfahren Sie mehr zu diesem Thema und können die Sprache „Haskell“ erlernen.

Referenzen

ÄHNLICHE DOKUMENTE

10– 12 MZH 1090 Tarek Online Alexander I Alle Tutorien haben einen Zoom-Raum (für Präsenztutorien als Backup) — siehe Webseite I Diese Woche alle Tutorien online —

I Algebraische Datentypen I Typvariablen und Polymorphie I Funktionen höherer Ordnung I I Rekursive und zyklische Datenstrukturen I Funktionen höherer Ordnung II.. I Teil

I Tutorien: Mo 10-12 MZH 5210 Christian Maeder Mo 16-18 MZH 1380 Rene Wagner Di 8-10 MZH 1100 Diedrich Wolter Di 10-12 MZH 1380 Diedrich Wolter Di 10-12 MZH 1400 Bernd Gersdorf Di

werden gleichzeitig definiert (Rekursion!) I Namen f, y und Parameter (x) überlagern andere I Es gilt die Abseitsregel. I Deshalb: Auf gleiche Einrückung der lokalen

Christoph Lüth &amp; Dennis Walter Universität Bremen Wintersemester

I Eigenschaften von Werten des Typen (insb. ihre innere Struktur) können nur über die bereitgestellten Operationen beobachtet werden. Zur Implementation von ADTs in

I Signatur: Typ und Operationen eines ADT I Axiome: über Typen formulierte Eigenschaften. I Spezifikation = Signatur

Christoph Lüth &amp; Dennis Walter Universität Bremen Wintersemester