2 Programmiermethodik 2.1 1 Schlagwörter: strukturiertes Programmieren systematisches Programmieren Techniken und Tricks Programmierstil Benutzung von Bibliotheken etc. etc. 2.1 2 2.1 Schrittweise Verfeinerung (stepwise refinement, top-down programming [N. Wirth 1971]) = allgemeines Algorithmus-Muster zum Lösen eines Problems: löse Problem: falls Problem einfach, löse es direkt; sonst zerlege es in Teilprobleme P1,...Pn , löse die Pi für i=1,...,n und füge die Teillösungen zusammen. 2.1 3 2.1 Schrittweise Verfeinerung (stepwise refinement, top-down programming [N. Wirth 1971]) = allgemeines Algorithmus-Muster zum Lösen eines Problems: löse Problem: falls Problem einfach, löse es direkt; sonst zerlege es in Teilprobleme P1,...Pn , löse die Pi für i=1,...,n und füge die Teillösungen zusammen. Beispiele: P A 2.1 D P B E C F A P (Rekursion) 4 2.1.1 Verfeinerung von Anweisungen durch Einsatz zusammengesetzter Anweisungen wie im Beispiel: Programm: . . . . . . . ordne a1 und a2 . . . . . . . ordne a1 und a2: if(a1>a2) vertausche a1 und a2 1. Verfeinerungsschritt: Alternative vertausche a1 und a2: {int x = a1; a1 = a2; a2 = x; } 2. Verfeinerungsschritt: Block Programm: . . . . . . . if(a1>a2) {int x = a1; a1 = a2; a2 = x; }. . . . . . . 2.1 5 3 Alternativen für die Formulierung des endgültigen Programmcodes: Verfeinerungen an Ort und Stelle einfügen (s.o.), desgl., dabei die ursprünglichen Formulierungen als Marken belassen, für jede Verfeinerung entsprechende Prozedur verwenden - Vorteile: - eventuell mehrfach verwendbar, - parametrisierbar, - Entwicklungsreihenfolge = textuelle Reihenfolge, - keine Kollisionsgefahr beim Einführen lokaler Variablen. (Nachteil: zu schwergewichtig bei „kleinen“ Verfeinerungen) 2.1 6 Die Java-Syntax ist dafür eher hinderlich: Einsatz der Zeichen {} und ; () ist obligatorisch auch für parameterlose Methoden Kollisionsgefahr bei lokalen Variablen in Block (statt Methode) keine Variablenparameter u.a. 2.1 7 2.1.2 Verfeinerung von Ausdrücken (gilt für imperative genauso wie für applikative Programmierung) x in Feld a suchen: int i = 0; while(!fertig) i++; // a[i]==x genau dann, wenn x in a vorkommt fertig: (a[i]==x || i==a.length-1)// Klammerung beachten! Mitunter Funktionsprozedur erforderlich: if(Position von x in a != 0) ....... int position(x,a){... (Iteration oder Rekursion) ...} 2.1 8 2.1.3 Beispiel: Suchen durch Einschachteln (binary search) Vor.: In einem Feld int a[] seien die Zahlen zwischen den Indizes unten/oben aufsteigend sortiert (unten<oben). Unter diesen Zahlen befinde sich die Zahl x, d.h. es gebe einen Index position (evtl. mehrere) mit unten<position & a[position]==x & position<oben Gesucht: position Beispiel: 1 unten 2.1 3 4 4 6 7 position 9 x==6 oben 9 Lösungsidee: wiederholtes Halbieren des Intervalls Position suchen: for(;;){ setze zeiger auf Intervallmitte; if(a[zeiger]==x } return zeiger; else betrachte linkes oder rechtes Teilintervall } setze zeiger auf Intervallmitte: int zeiger = (unten+oben)/2; betrachte linkes oder rechtes Teilintervall: if (a[zeiger]< x) unten = zeiger; else /* a[zeiger]> x */ oben = zeiger; } 2.1 10 ... und als Funktionsprozedur verpackt: static int position(int x, int[]a, int unten, int oben){ for(;;) { int zeiger = (unten+oben)/2; if (a[zeiger]==x) return zeiger; else if(a[zeiger]< x) unten = zeiger; else /* a[zeiger]> x */ oben = zeiger; if(oben-unten == 1) return 0; } } ... und erweitert für den Fall des Nichtvorkommens von x 2.1 11 Beachte den erheblich geringeren Aufwand als bei linearer Suche: Beh.: Suchen in n=2k-1 Zahlen erfordert höchstens k Schritte (= Schleifendurchläufe) Bew.: (vollständige Induktion:) I. k=1: n = 21-1 = 1 erfordert 1 Schritt II. Die Behauptung gelte für ein bestimmtes k und n = 2k-1: n=2k-1 Stück höchstens k Schritte n=2k-1 Stück höchstens k Schritte höchstens k+1 Schritte 2.1 2k+1-1 Stück 12 Also: mit n=2k-1 ist k = log2(n+1), d.h. Einschachteln erfordert maximal log2(n+1) Schritte im Gegensatz zu: lineares Suchen erfordert maximal n Schritte Beachte: Obwohl die Schritte beim Einschachteln aufwendiger sind, amortisiert sich dieser Aufwand schnell bei wachsendem n: n: log2 n: 2.1 32 1024 32.768 1.048.576 5 10 15 20 13 2.1.4 Beispiel: Sortieren durch Einfügen (insertion sort) Aufgabe: sortiere a[1] bis a[n] „in situ“ 2.1 sortiere: for(int i=2; i<=n; i++) ordne a[i] vorn ein ordne a[i] vorn ein: a[0] = a[i]; // sentinel schaffe Lücke an der richtigen Stelle k; a[k] = a[0]; schaffe Lücke an der richtigen Stelle k; int k = i; while(a[0]<a[k-1]) a[k] = a[--k]; 14 ... und als Prozedur verpackt, z.B. für Gleitkommazahlen: static void insertionSort(float[]a, int n) { for(int i=2; i<=n; i++) { a[0] = a[i]; // sentinel int k = i; while(a[0]<a[k-1]) a[k] = a[--k]; a[k] = a[0]; } } 2.1 15 2.1.5 Beispiel: Sieb des Eratosthenes zur Ermittlung aller Primzahlen unterhalb von n Idee (Eratosthenes, griech. Mathematiker, 3. Jhdt. v. Chr.): sieve(p:list) = p : sieve [x | x<-list, x `mod` p /= 0 ] 2.1 und damit sieve[2..n] oder auch sieve[2..n/2] (oder auch sieve[2..] ! ) 16 Imperativ: Feld mit Zahlen 2,3,...,n-1 füllen und wiederholt Primzahl-Vielfache „durchs Sieb fallen lassen“ 2.1 Eratosthenes: fülle Sieb; for(int i=1; weitersuchen; ) { bestimme nächste Primzahl; entferne deren Vielfache } stelle Primzahlen zusammen. fülle Sieb: boolean[] durchgefallen = new boolean[n]; // alle false, d.h. noch im Sieb durchgefallen[0] = durchgefallen[1] = true; 17 bestimme nächste Primzahl: while(durchgefallen[++i]) ; // i ist nächste Primzahl entferne deren Vielfache: for(int k=i*i; k<n; k+=i) durchgefallen[k] = true; stelle Primzahlen zusammen: . . . . . weitersuchen: i<n-1 2.1 besser: i<n/2 noch besser: i<√n , d.h. i*i<n ! 18 ... und als Prozedur verpackt: static int[] eratosthenes(int n) { boolean[] durchgefallen = new boolean[n]; durchgefallen[0] = durchgefallen[1] = true; for(int i=1; i*i<n; ) { while(durchgefallen[++i]) ; for(int k=i*i; k<n; k+=i) durchgefallen[k] = true; } int[] result = new int[n]; int k = 0; for(int i=2; i<n; i++) if(!durchgefallen[i]) result[k++] = i; return result; } 2.1 19 2.1.6 Verfeinerung mit Revision ! Nicht immer ist die Verfeinerung ohne nachträgliche Eingriffe möglich ! Beispiel: Wort b in Text a durch anderes Wort c ersetzen (vgl. Aufgabe 7.2) genauer: b kommt tatsächlich in a vor, und c ist nicht länger als b; nur das erste Auftreten von b soll berücksichtigt werden; a ist Inhalt eines Textfelds, das gegebenenfalls hinten mit Leerzeichen aufgefüllt werden soll. Sein•oder•nicht•einein,•das•ist•hier•die•Frage.•• Sein•oder•nicht•sein,•das•ist•hier•die•Frage.•••• 2.1 20 Ersetzen: int i; for(i=0; !gefunden; i++); ersetze b durch c; schließe Lücke; fülle mit Leerzeichen auf. gefunden: for(int k=0; k<b.length; k++) if(a[i+k]!=b[k]) return false; return true; ‘ for(i=0; !gefunden; i++); : seek: for(i=0; ; i++) { for(int k=0; k<b.length; k++) if(a[i+k]!=b[k]) continue seek; break; } 2.1 21 ersetze b durch c: for(int k=0; k<c.length; k++) a[i+k] = c[k]; schließe Lücke: for(int k=0; k<a.length-i-b.length; k++) a[i+c.length+k] = a[i+b.length+k]; fülle mit Leerzeichen auf: for(int k=0; k<b.length-c.length; k++) a[a.length-b.length+c.length+k] = ' '; 2.1 22 ... und zusammengefasst: static void replace(char[]a, char[]b, char[]c) { int i; seek: for(i=0; ; i++) { for(int k=0; k<b.length; k++) if(a[i+k]!=b[k]) continue seek; break; } for(int k=0; k<c.length; k++) a[i+k] = c[k]; for(int k=0; k<a.length-i-b.length; k++) a[i+c.length+k] = a[i+b.length+k]; for(int k=0; k<b.length-c.length; k++) a[a.length-b.length+c.length+k] = ' '; } 2.1 23