Kapitel 4 Iterationen Anything that happens, happens. Anything that, in happening, causes something else to happen, causes something else to happen. Anything that, in happening, causes itself to happen again, happens again. It doesn’t necessarily do it in chronological order, though. (Douglas N. Adams, Mostly Harmless) 4.1 Einführung Motivierendes Beispiel „Aufgabe“ Esse alle Pralinen in einer Pralinenschachtel! Annahme: Ich weiß, wie ich eine Praline essen kann. Bildquelle: BeautifulCataya, CC 2.0 121 M. Werner: Algorithmen & Programmierung Variante 1 Esse die erste Praline von links in der ersten Reihe. Esse die zweite Praline von links in der ersten Reihe. .. . Esse die Praline ganz rechts in der ersten Reihe. Esse die erste Praline von links in der zweiten Reihe. .. . Esse die erste Praline von links in der letzten Reihe. Esse die zweite Praline von links in der letzten Reihe. .. . Esse die Praline ganz rechts in der letzten Reihe. • Problem: Algorithmus1 ist evtl. sehr lang Variante 2 • Weise den Pralinen die Nummern 1 bis n zu • Zähle von 1 bis n und esse die Praline mit der jeweiligen Nummer • Anmerkung: Es findet eine Abbildung von der Menge der natürlichen Zahlen N auf die Menge der Pralinen statt Variante 3 • Zähle die Pralinen á n • Zähle von 1 bis n und esse bei jeder Zahl jeweils eine Praline • Anmerkung: Der Vorgang des Essens wird für eine vorher bestimme Zahl von Wiederholungen durchgeführt Variante 4 • Solange noch eine Praline da ist: Esse eine (beliebige) Praline • Anmerkung: Der Vorgang des Essens wird für eine (zunächst) unbestimme Zahl von Wiederholungen durchgeführt 1 Genau 122 genommen ist es in der dargestellten Form gar kein Algorithmus; vgl. Kapitel 1. 4. Iterationen I 4.2 Schleifen in C und Python Unterschiedliche Sichtweisen • Wiederholen... – ...für eine bestimmte Anzahl von Wiederholungen – ...für Elemente einer Menge – ...solange eine Bedingung (nicht) erfüllt ist • Alle Varianten sind aufeinander abbildbar • Solche Wiederholungen heißen in Programmiersprachen Schleifen • Schleifen gibt es in vielen (nicht allen!) Programmiersprachen • Allgemein gibt es bei Schleifen immer etwas Invariantes/Konstantes und i.d.R. etwas sich Änderndes 4.2 Schleifen in C und Python • In C gibt es drei Arten von Schleifen: – while-Schleife – do-while-Schleife – for-Schleife • Alle Schleifen nutzen Bedingungen • Jede davon kann2 die anderen simulieren • Welche genutzt wird, ist also z.T. Geschmackssache while-Schleife • Die while-Schleife führt ein Statement solange aus, wie eine Bedingung wahr ist. • Sie arbeitet wie folgt: 1. Es wird überprüft, ob der Ausdruck in der Klammer nach dem Schlüsselwort while eingehalten wird 2. Falls ja, wird eine Anweisung nach diesem Ausdruck ausgeführt (á Schleifenkörper) und anschließen wieder zu 1. gesprungen – Wenn die Schleife mehrere Anweisungen umfassen soll, müssen diese in einem Block geklammert werden 2 ...ggf unter Nutzung anderer Sprachkonstrukte... 123 M. Werner: Algorithmen & Programmierung 3. Falls nein, wird mit der ersten Anweisung nach dem Schleifenkörper fortgesetzt Anweisung (Schleifenkörper) while(i<10) i=i+1; Bedingung Beispiele: / * sqr .c -- Heron ’s method * / # include < stdlib .h > # include < math .h > # include < stdio .h > int main ( int argc , char ** args ) { double x =1.0; double y= atof ( args [1]); while ( fabs (y -x * x ) >0.00001){ x =0.5 * ( x+y/x ); } printf (" sqrt (% f) = % f .\ n " ,y , x ); return 0; } # include < stdio .h > int main () { int i =3; while (i >0) { printf (" Cthulhu !\ n " ); i=i -1; } return 0; } Schleifen und Felder • Schleifen können zur Bearbeitung oder Belegung der Elemente eines Array zur Laufzeit genutzt werden 124 4. Iterationen I 4.2 Schleifen in C und Python • Beispiel: # include < stdio .h > enum { arraysize =12}; / * constant for array size * / int main () { int xa [ arraysize ] ,i =0; while (i < arraysize ){ xa [i ]= i * i; i=i +1; } / * 0 ,1 ,... ,11 */ / * square of index * / i =0; while (i < arraysize ){ / * 0 ,1 ,... ,11 */ printf (" Element %d of xa : % d \n" ,i , xa [i ]); i=i +1; } return 0; } do-while-Schleife • Die do-while-Schleife ähnelt der while-Schleife • Unterschied: Die Bedingung wird nach einem Schleifenduchlauf geprüft • Damit der Compiler weiß, dass eine Schleife beginnt, wird zu Schleifenbeginn das Schlüsselwort do gesetzt do i=i+1; while(i<10); Schleifenkörper Bedingung • Wieder müssen bei mehr als einer Anweisung im Schleifenkörper diese in geschweifte Klammern gesetzt werden • do-while-Schleife werden immer dann eingesetzt, wenn der Schleifenkörper mindestens einmal ausgeführt werden soll 125 M. Werner: Algorithmen & Programmierung for-Schleife • Typisch: – Vor einer Schleife wird ein Wert initialisiert – Im Schleifenkörper wird ein (meist derselbe) Wert geändert • Die for-Schleife lässt beides explizit in der Schleifendeklaration zu • Wieder: Mehr als ein Statement im Schleifenkörper muss geklammert werden • Achtung: Der Schleifenkörper kann leer sein á einzelnes Semikolon – Schleifenkörper im Schleifenkopf integriert – Schlechter Stil, außer bei sehr einfachen Schleifen Initialisierung for(int i=0;i<10; i=i+1) printf("i=%d \n",i); Bedingung nach Schleifenkörper Schleifenkörper Beispiele für for-Schleife / * for .c -- loop with for * / # include < stdio .h > int main ( int argc , char * argv []){ for ( int i =0; i < argc ; i =i +1) printf ("%d. argument : % s\ n" ,i +1 , argv [i ]); return 0; } > ./for 23 foo Bar 42 1. argument: ./for 2. argument: 23 3. argument: foo 4. argument: Bar 5. argument: 42 • Es können einzelne Ausdrücke weggelassen werden... 126 4. Iterationen I 4.2 Schleifen in C und Python / * for2 . c -- loop with for , #2 * / # include < stdio .h > int main ( int argc , char * argv []){ int i =0; for (; i < argc ; i= i +1) printf ( "% d. argument : %s \n " , i +1 , argv [i ]); return 0; } • ... oder auch alle Ausdrücke á Endlosschleife / * for - ever . c -- infinite loop * / # include < stdio .h > int main (){ for (;;) printf (" The answer is 42.\ n" ); return 0; // never reached } • Anmerkung: Vor C99 konnte in einer for-Schleife eine Laufvariable nur initialisert, aber nicht deklariert werden • Die Deklaration musste also vorher erfolgen • Das obige Beispiel würde in ANSI-C nicht übersetzbar sein und müsste stattdessen so aussehen: C / * for .c -- loop with for , ANSI C * / # include < stdio .h > Version int main ( int argc , char * argv []){ int i ; for (i =0; i < argc ; i= i +1) printf ( "% d. argument : %s \n " ,i +1 , argv [i ]); return 0; } 127 M. Werner: Algorithmen & Programmierung Quelle: xkcd - A webcomic of romance, sarcasm, math, and language http://xkcd.com/411/ break und continue • Schleifen werden solange durchlaufen, wie die Schleifenbedingung gegeben ist • Allerdings gibt es zwei Möglichkeiten, innerhalb des Schleifenkörpers den Kontrollfluss zu ändern: break: Beendet die Schleife unabhängig von der Schleifenbedingung continue: Startet sofort die nächste Auswertung der Schleifenbedingung und ggf. den nächsten Schleifendurchlauf Achtung! Die Benutzung von break und continue ist eigentlich nicht notwendig und ein Indiz, dass die Programmlogik nicht hinreichend durchdacht wurde. Daher sollten break und continue nur zur Behandlung von Notfällen (Ausnahmen) genutzt werden. / * reciprocal .c -- calculate reciprocal value of array elements * / # include < stdio .h > / * a negative value indicates end of list * / double f []={1.0 , .5 , 3.1415 , .33333 , 0.0 , 2.7182 , 42.23 , -1}; int main () { int i; for (i =0; ; i=i +1){ 128 4. Iterationen I 4.2 Schleifen in C und Python if ( f[i ] <0.0) break ; if ( f[i ] <=0.0001) continue ; f[i ]=1/ f[ i ]; } for (i =0; f[ i ] >=0.0; i= i +1) printf (" %d value : %f \n " ,i ,f [i ]); return 0; } Geschachtelte Schleifen • Schleifen können auch ineinander verschachtelt sein • Dabei können sich die Schleifen auch beeinflussen / * for3 . c -- nested loops * / # include < stdio .h > int main ( int argc , char * argv []){ int goal ; if ( sscanf ( argv [1] , "% d" ,& goal )!=1) return -1; for ( int i =1; i <= goal ; i= i +1) { for ( int j =1; j <= i ; j =j +1) printf ( "% d " ,i ); printf (" \n " ); } return 0; } Schleifen in Python • In Python gibt es zwei Arten von Schleifen: – while-Schleifen – for-Schleifen • while-Schleifen ähneln ihren Pendants in C: 129 M. Werner: Algorithmen & Programmierung i =10 while (i >0): print ("i=" ,i) i=i -1 • Wie immer in Python können Blöcke durch Einrückungen markiert werden Schleifensteuerung • Auch Python kennt die Schlüsselwörter break und continue • Die Semantik ist gleich zu C • Zusätzlich gibt es bei Python-Schleifen einen optionalen else-Zweig á Wird nach der Schleife ausgeführt, wenn diese nicht abgebrochen wurde def prim (x ): y= int (x /2) while y >=2: if x % y == 0: print (x , " has factor " , y) break y=y -1 else : print (x , " is prim ") • Mit Hilfe von break kann leicht die do-while-Schleife aus C nachempfunden werden i =0 while True : print ("i=" ,i) i=i +1 if not (i <10): break print (" Ende ") for-Schleife in Python • Die Syntax for-Schleife sieht zunächst ähnlich wie die while-Schleife aus 130 4. Iterationen I 4.2 Schleifen in C und Python for i in C : do _ something ( i) • Die Semantik ist jedoch anders: – C muss eine Sequenz oder Menge sein (d.h. String, Liste, Tupel, Menge, Dictionary)3 – Es werden i nacheinander die Elemente von C zugewiesen # for . py -- loops over sequence objects for x in [ ’ John ’, ’ Paul ’ ,’ Georg ’ ,’ Pete ’]: print ( x) • Dies funktioniert auch für gemischte Typen: # for2 . py -- mixed types T =( ’ Alpha ’ ,2 ,(1 ,2)) for x in T : print (2 * x) python for2.py AlphaAlpha 4 (1, 2, 1, 2) • Um die Semantik der C-for-Schleife nachzuempfinden, kann die range-Funktion genutzt werden range(start=0, end, step=1 ) Von Bis (nicht einschliesslich) Schrittweite »> range(5), range(2,5), range(2,10,2) ([0, 1, 2, 3, 4], [2, 3, 4], [2, 4, 6, 8]) »> 3 Genauer: Es muss ein iterierbarer Typ sein. 131 M. Werner: Algorithmen & Programmierung # for3 . py -- loops with range for i in range (1 ,8): for j in range (i ): print (i , end = ’ ’) print () Iteratoren in C • Umgekehrt kann man auch den Python-Ansatz in C „nachempfinden“ / * iter .c -- iterator with C * / # include < stdlib .h > # include < stdio .h > static int ndx =0; char * first () { return names [ ndx =0]; } char * next (){ if ( names [ ndx ]== NULL ) ndx =0; else ndx = ndx +1; return names [ ndx ]; } char * names []={ " John " ," Paul " ," Georg " , " Ringo " , NULL }; int main (){ for ( char * str = first (); str != NULL ; str = next ()) printf ("%s " , str ); return 0; } • Solche Iteratoren sind z.B. in C++ verbreitet á Dort aber eleganter (auch in C geht es schöner 132 ) 4. Iterationen I 4.3 Iteration und Rekursion 4.3 Iteration und Rekursion • Bisher wurden in Schleifen immer (implizite) Zuweisungen benutzt á Zustandsmodell • Auch im funktionalen Modell sind Schleifen möglich • Dort werden sie durch Rekursion realisiert • Beispiel: / * recursion . c -- loops by recursion * / # include < stdio .h > void say _ it ( int i ){ if ( i ==0) return ; else { printf (" Cthulhu !\ n " ); say _ it (i -1); } } int main (){ say _ it (3); return 0; } Schleifenfunktion • Dabei kann der Schleifenkörper von der „Schleifenmechanik“ getrennt werden, so dass eine Schleifenfunktion möglich ist • Wir nutzen hier einen Zeiger auf Funktionen / * loop - func .c * / # include < stdio .h > // Pointer to a void function with integer argument typedef void ( * VFP )( int ); void loop ( VFP func , int start , int end , unsigned int step ){ if ( start <= end ) { func ( start ); loop ( func , start + step , end , step ); 133 M. Werner: Algorithmen & Programmierung } } void lbody ( int param ){ printf ("%d ^2=% d\n" , param , param * param ); } int main (){ loop ( lbody ,0 ,10 ,1); return 0; } • Dieses Beispiel hat einige Einschränkungen: – Der Typ der Schleifenkörper-Funktion ist festgelegt á Der Schleifenkörper kann nur auf den Schleifenindex (und globale Variablen) zugreifen – Es muss step> 0 gelten á Die Schleife kann nur aufwärts zählen • Man kann diese Einschränkungen überwinden, aber dies würde den Code „aufblasen“ • Allgemeiner (und in der Anwendung eleganter) funktioniert dies in Python, das mit λ-Funktionen (namenlosen Funktionen) ein Feature funktionaler Sprachen bietet # loop - func . py -- loops by recursion from sys import stdout loop = lambda v ,e ,i ,f , args : \ e(v) and (f ( * (( v ,)+ args )) , \ loop (( lambda y: y+i )( v),e ,i ,f , args )) or 0 def printpow (i ,x ): print (i ," ** " ,x ,"=" ,i ** x) def printi (i ,b ,a ): stdout . write (b+ str (i )+ a) loop (1 , lambda x: x <=4 ,1 , printpow ,(2 ,)) loop (10 , lambda x: x >=0 , -1 , printi , ("[" ,"] " )) >python loop-func.py 1 ** 2 = 1 2 ** 2 = 4 3 ** 2 = 9 4 ** 2 = 16 [10] [9] [8] [7] [6] [5] [4] [3] [2] [1] [0]> 134 4. Iterationen I 4.3 Iteration und Rekursion Anmerkung: Wenn Sie dieses Beispiel nicht verstehen, können Sie es ignorieren – λ-Funktionen werden in Kurs „Funktionale Programmierung“ behandelt Äquivalenz • Allgemein gilt: Jede Schleife in einem Programm kann in eine rekursive Funktion umgewandelt werden. und Jede (trivial-)rekursive Funktion kann mit Hilfe von Schleifen berechnet werden.4 • Nicht trivialerekursiv ist z.B. die y + 1 f (x, y) = f (x − 1, 1) f (x − 1, f (x, y − 1)) Ackermann-Funktion: ⇐x=0 ⇐ (x 6= 0) ∧ (y = 0) sonst • Schleifen und Rekursion sind also für die meisten praktischen Anwendungsfälle gleichmächtig • In der Regel ist eine Schleife übersichtlicher • Es gibt aber auch Fälle (vgl. Kapitel 9), in denen Rekursion die handlichere Lösung bildet • Einige Programmiersprachen kennen keine Schleifen, sondern nur Rekursionen Aufgaben Aufgabe 4.1 Was bedeutet in C die folgende Deklaration: int* (*foo)(int,int*) 4 Genauer: Primitiv-rekursive Funktionen lassen sich stets mit a priori beschränkten Schleifen berechnen; bei sogenannten µ-rekursive Funktionen muss diese Einschränkung aufgegeben werden. 135 M. Werner: Algorithmen & Programmierung Aufgabe 4.2 In einer Schale und in einem Eimer seien eine Anzahl von weißen und orangen Tischtennisbällen, wobei soviel Bälle im Eimer sind, dass der folgende Algorithmus stets durchgeführt werden kann: 1. Nehme zwei beliebige Bälle aus der Schale. • Wenn sie die gleiche Farbe haben, werfe sie in den Eimer und nehme einen orangen Ball aus dem Eimer und lege ihn in die Schale. • Wenn sie unterschiedliche Farbe haben, lege den weißen in die Schale zurück und werfe den orangen Ball in den Eimer. 2. Wiederhole, bis nur noch ein Ball in der Schale ist. Wird tatsächlich stets ein Ball übrig bleiben? Wenn ja, kann etwas über seine Farbe in Abhängigkeit zur ursprünglichen Anzahl der Bälle gesagt werden? Wenn ja, was? 136