Kontrollstrukturen und Schleifen Inhalt

Werbung
Vorlesung
4
Kontrollstrukturen und
Schleifen
Inhalt
1 Verzweigungen
1.1 Bedingte Verzweigungen in Python: if-elif-else
1.2 Verzweigungen und Blöcke in anderen Programmiersprachen
2 Visualisierung von Kontrollstrukturen
2.1 Kontrollstrukturen
2.2 Graphische Repräsentationen
2.2.1 Programmablaufpläne
2.2.2 Nassi-Shneiderman-Diagramme
2.2.3 Jackson-Diagramme
2.2.4 Unified Modeling Language
3 Iterative Grundstrukturen
3.1 Schleifen in Python: for und while
3.2 break – continue - else
3.3 Realisierungsformen der Iteration
3.4 Schleifen in anderen Programmiersprachen
3.5 Iteration als Lösungsmethode
3.6 Beispiel für iterative Approximation: die Regula Falsi
4 Reading
5 Hinweise zu Programmierkursen
1
2
2
4
4
5
5
6
7
8
9
9
13
14
17
18
19
21
21
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
V 4 Kontrollstrukturen
Vorlesung
4
6
Lernziele:
In diesem Kapitel werden wir die Verzweigung, und Iteration als grundlegende
mathematische und informatische Lösungsmethoden kennen lernen sowie deren
Realisierungen in Programmiersprachen. Hierzu gehören insbesondere die verschiedenen Ausprägungen von Schleifen zur Iteration.
Alle diese Konzepte finden in Python eine konkrete Ausprägung. Es soll gelernt
werden, diese grundsätzlich einzuordnen und anzuwenden.
1 Verzweigungen
In vielen Fällen reicht eine einfache lineare Abfolge von Anweisungen nicht aus, sondern wir
benötigen Fallunterscheidungen. Je nach Ergebnis der Berechnungen müssen unterschiedliche
Dinge getan werden, es muss zwischen den möglichen Aktionen verzweigt werden. Bedingte
Verzweigungen bilden zusammen mit den Schleifen die Kontrollstrukturen moderner Programmiersprachen. In allen imperativen und objektorientierten Sprachen sind sie in unterschiedlichen Ausprägungen vorhanden. Leistungsfähige Schleifenkonstrukte zusammen mit Unterprogrammmethoden sind essenzielle Konstituenten der strukturierten Programmierung
(dritte Programmiersprachen-Generation) aber auch der Objektorientierten Programmierung.
Diese Vorlesung beschreibt Kontrollfluss-Anweisungen eines Python Programms: Bedingungen
und Schleifen. Ausnahmeereignisse (exeptions), die auftreten können, führen zwar ebenfalls zu
einer Verzeigung und gehörten damit prinzipiell auch in dieses Vorlesungskapitel, werden allerdings später behandelt.
1
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
1.1 Bedingte Verzweigungen in Python: if-elif-else
In Python steuern die Anweisungen if, else und elif die bedingte Ausführung des Programmcodes und implementieren so Verzweigungen. In einem Programm sieht das dann so aus:
if expression:
suite
elif expression:
suite
elif expression:
suite
...
else:
suite
Für die Formulierung von Bedingungen gilt somit folgende Syntax:
if_stmt
::=
suite
::= stmt_list NEWLINE
| NEWLINE INDENT statement+ DEDENT
::= stmt_list NEWLINE | compound_stmt
::= simple_stmt (";" simple_stmt)* [";"]
statement
stmt_list
"if" expression ":" suite
( "elif" expression ":" suite )*
["else" ":" suite]
„Expression“ ist hierbei ein Ausdruck, der sich zu einem logischen (booleschen) Wert auswerten lässt, und das ist fast jeder. Der Bezeichner „suite“ kennzeichnet eine beliebige Anweisungsfolge auf gleicher Einrückungsebene. In anderen Programmiersprachen nennt man eine „suite“
auch einen Block. Hiermit ist dann auch der Gültigkeitsbereich einer Variablen nur innerhalb
des Blocks festgelegt. Falls keine Aktion ausgeführt werden muss, kann man sowohl die elseals auch die elif-Klausel einer Bedingung weglassen. Man verwendet dann die passAnweisung, wenn eine bestimmte Klausel keine Anweisungen braucht.
Ein konkretes kleines Beispiel:
a = 3
b = 2
if a < b:
pass
elif a == b:
a = a + 1
else: a = a - 1
print a
Und das Ergebnis beim Durchlauf:
>>> ================================ RESTART ================================
>>>
2
1.2 Verzweigungen und Blöcke in anderen Programmiersprachen
Neben den syntaktischen Unterschieden gibt es subtile semantische Unterschiede, die man für
jede Sprache sehr differenziert betrachten muss. Das würde jedoch weit über unser Lernziel
herausreichen. Hier geht es zuerst darum, die prinzipiellen Varianten zu erkennen und zusammenfassendes Verständnis (Sprach-Komprehension) für die Konzepte zu erlangen!
2
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
Die auffälligsten Unterschiede zwischen den verschiedenen Programmiersprachen findet man
bei der Kennzeichnung von „Blöcken“ oder „Suiten“ in Programmiersprachen. Beide Konstrukte kennzeichnen Folgen von Anweisungen, die zu einer Einheit zusammengefasst sind.
Blöcke markieren in Sprachen mit statischer Typprüfung (also solchen Sprachen in denen eine
Variablendefinition und –deklaration erfolgen muss) den Gültigkeitsbereich der entsprechenden Definition oder Deklaration: Die Variable ist nur in dem Block bekannt, in dem sie definiert
respektive deklariert wird. In unserer Auflistung kennzeichnet die Klammerung „[ ... ]“ optionale
Konstrukte.
Sprache
Blöcke / Suiten
Python
C, C++
Java
Fortran90/95 (2003)
Einrückung (indent)
Pascal
Anmerkungen
{ … }
{ … }
program … end program
if … then … [else if … ]end if
select case … end select
do … if … exit … end do
do while … end do
begin … end
Um die Lesbarkeit des
Quellcodes zu verbessern
werden oft in Programmierrichtlinien zusätzlich zur
Klammerung Einrückungen
empfohlen. Diese haben
aber für den Compiler keine
Aussage!
Für die Verzweigung, die in jeder Programmiersprache vertreten ist, finden wir auch verschiedene Varianten:
Sprache
Verzweigungen
Anmerkungen
Python
if expression:
suite
[elif expression:
suite
elif expression:
suite
...]
[else:
suite]
als einseitige, zweiseitige und mehrseitige Auswahl genutzt
if(expression)
…;
[else if(expression)
…;
else if(expression)
…;
…
else
Einrückungen sind „nur“ Programmierkonvention
C, C++
Einrückungen sind zwingend!
...;];
exp1 ? exp2 : exp3;
Bedingter Ausdruck:
Wenn exp1 ungleich 0 und somit
true, dann ist der Ausdruck exp2
das Resultat der bedingten Anweisung, andernfalls ist exp3 das
Resultat.
3
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
switch(expression)
{
case const-expr: …;
case const-expr: …;
…
default: …;
};
Java
mehrseitige Auswahl:
const-expr sind Konstanten
if (<log.Ausdruck> )
…
else {
…
}
switch (myIntWert) {
case 0 :
…;
break;
case 1 :
…;
default : …;
}
mehrseitige Auswahl
if (<log.Ausdruck>) then
…
Fortran
[else
if
(<log.Ausdruck>)
90/95 (2003)
then
[else
end if
…]
…]
select case (<case-Ausdruck>)
case (<case-Fall 1>) …
case (<case-Fall 2>) …
…
case default …
end select
Pascal
if <Bedingung>
then …
[else …];
einseitige und zweiseitige Auswahl
case <Variable>
1:
… ;
2:
… ;
…
else …
end;
mehrseitige Auswahl
2 Visualisierung von Kontrollstrukturen
Nachdem wir Verzweigungen bei Python kennen gelernt haben und sie mit denen anderer Programmiersprachen verglichen haben, können wir das Ganze verallgemeinern. Eine Verzweigung, also wertabhängige Fallunterscheidung, ist sowohl in der Mathematik, als auch in der Informatik elementar für fast jede Art von Verfahren (Algorithmen).
2.1 Kontrollstrukturen
Aufgrund einer Bedingung wird der Programmfluss (die Abfolge der Ausführung der Befehle)
verzweigt. Wir unterscheiden die einfache (bedingte) Verzweigung, die den Programmfluss
in zwei Pfade auftrennt und eine mehrfache Verzweigung.
4
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
Realisiert ist dies in den meisten Programmiersprachen sehr ähnlich, ungefähr folgendermaßen:
if <Bedingung>
then
Aktionsfolge
else
<AlternativeAktionsfolge>
endif
und die mehrfache Verzweigung mit
case <aVariable>
aValue1: <AktionsfolgeA>
aValue2: <AktionsfolgeB>
...
otherwise: <Aktionsfolge>
endcase
In (fast) allen modernen Programmiersprachen werden Verzweigungsstrukturen durch
if … then … else realisiert. Unterschiede finden sich allenfalls in Details der Syntax, z.B.
der Art der Klammerung der Anweisungsblöcke. Größere Unterschiede in Syntax und Semantik
finden wir allerdings bei Mehrfachverzweigungen: Hier sind üblich: CASE … SWITCH … oder
if … then … elif … elif …else Strukturen gebräuchlich.
2.2 Graphische Repräsentationen
Zur Darstellung von Verzweigungen und allgemeinen Programmstrukturen wurden schon sehr
früh graphische Repräsentationen (visuelle Sprachen) eingesetzt, die später auch genormt wurden:
•
Programmablaufpläne (und Datenflusspläne) nach DIN 66001
•
Nassi-Shneiderman Diagramme nach DIN 66261
•
Jackson-Diagramme
•
UML Diagramme (hier allein 14 verschiedene Diagrammarten)
Betrachten wir dies näher.
2.2.1
Programmablaufpläne
Ein Programmablaufplan ist ein Ablaufdiagramm für ein Computerprogramm, das auch als
Flussdiagramm (engl. flowchart) oder Programmstrukturplan bezeichnet wird. Das Konzept der Programmablaufpläne stammt aus den 60er-Jahren. Es besteht aus graphischen Einheiten (Rechtecke für Befehlsfolgen, Rauten für Entscheidungen, etc.) und Pfeilen dazwischen, die den Befehlsfluss symbolisieren.
Hauptkritikpunkt an Programmablaufplänen ist, dass
•
sie schon bei mittelgroßen Algorithmen schnell unübersichtlich. werden und
•
sie zur Verwendung von expliziten Sprunganweisungen (GOTO's) verführen und damit die Produktion von "Spaghetti-Code" fördern und
5
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
•
gut strukturierter Programmcode (ggf. als Pseudocode oder in einer Skriptsprache wie
Python) relativ einfach direkt zu schreiben und zu verändern ist und dabei auch übersichtlich strukturiert werden kann.
•
Wenn man zwei Repräsentationen des Programms benutzt (Quellcode) und einen
Programmablaufplan, so ist es aufwendig, diese konsitent zu halten: Korrigiert man einen Fehler oder macht eine Ergänzung im Programm selbst, müsste dies natürlich im
Ablaufplan „nachgezogen“ werden.
Tatsächlich werden Programmablaufpläne heute in der Softwareentwicklung kaum noch eingesetzt. Sie erleben allerdings in etwas modifizierter Form eine Renaissance in MultimediaEntwicklungsprozessen und sind eben gut geeignet, elementare Strukturen der Programmierung
zu verdeutlichen.
2.2.2
Nassi-Shneiderman-Diagramme
Ein Nassi-Shneiderman-Diagramm (auch Struktogramme genannt) ist ein SoftwareEntwurfshilfsmittel (auch Methode) für die strukturierte Programmierung. Es ist genormt nach
DIN 66261. Benannt wurde es nach seinen Vätern Ike Nassi1 und Ben Shneiderman2.
Die Methode zerlegt ein Gesamtproblem, das man mit dem gewünschten Algorithmus lösen
will, in immer kleinere Teilprobleme bis schließlich nur noch elementare Grundstrukturen wie
Sequenzen und Kontrollstrukturen zur Lösung des Problems übrig bleiben. Die beiden Autoren
konnten mit ihren Diagrammen nachweisen, dass in keinem Algorithmus ein unbedingter
Sprung (GOTO) benötigt wird.
In dieser Vorlesung werden wir Programmablaufpläne und Nassi-Shneiderman-Diagramme nur
dazu benutzen, elementare Grundstrukturen zu visualisieren, weil diese hoffentlich so einprägsamer sind.3
Verzweigungen werden folgendermaßen visualisiert:
1
Homepage von Ike Nassi (http://www.nassi.com/ike.htm)
2
Homepage von Ben Shneiderman (http://www.cs.umd.edu/~ben/)
Wer trotzdem einmal versuchen will, verschiedene Diagrammarten auszuprobieren, der sei auf das Web verwiesen. Hier gibt es zahlreiche hilfreiche Freeware oder Public Domain Programme:
Hus Struktogrammer (http://www.lunabits.net/nette-freeware-tools/index.html#hus)
StruktEd ()
Nassi-Shneiderman Diagram-Editor (http://www.strukted.de/)
Struktogrammeditor (http://www.learn2prog.de/)
Software für Struktogramme und Ablaufdiagramme (http://www.tinohempel.de/info/info/infocd/index2.htm#Klasse%2012/II)
Struktogramme erzeugen (Macros und Symbolleiste für MS Word)
( http://www.dsw2.de/Randprodukte.html)
3
6
V O R L E S U N G 4 :
Verzweigungsart
K O N T R O L L S T R U K T U R E N
Programmablaufplan
Nassi-Shneiderman
einfach-einseitig
if <Bedingung>
then
<Block 1>
endif
wahr
wahr
Bedingung
Bedingung
falsch
leer
Block 1
Block 1
einfach-zweiseitig
if <Bedingung>
then
<Block 1>
else
<Block 2>
endif
wahr
Bedingung
falsch
wahr
Bedingung
falsch
Block 2
Block 1
Block 1
Block 2
mehrfach
case <Variable>
Wert 1: <B1>
Wert 2: <B2>
...
otherwise: <Bn>
endcase
Wert 1
B1
Variable
=
Wert 2
sonst
Bn
B2
Wert1
1
Wert2
B1
B2
Variable =
sonst
Bn
Sie können jetzt selbst entscheiden, wie hilfreich diese Art der Darstellung sein kann. Die meisten Programmierer bevorzugen heute so genannten „Pseudocode“ oder formulieren zum Prototyping direkt in Skriptsprachen wie z.B. Python.
2.2.3
Jackson-Diagramme
Jackson-Diagramme sind eine Alternative zu Nassi-Shneiderman. Sie sind Bestandteil einer
Methode, die der Theoretiker Michael A. Jackson in den Jahren 1975 bis 1979 entwickelt hat.
Das Jackson-Diagramm stellt den Programmentwurf in hierarchischer Form dar, ähnlich einem
Organigramm. Das Gesamtprogramm wird in schrittweiser Verfeinerung bis zu den Einzelfunktionen heruntergebrochen. Die Komponenten des Diagramms sind einfache Rechtecke, die
durch Pfeile verbunden sind. Es gibt nur drei Arten von graphischen Symbolen (alles Rechtecke):
•
Ein Rechteck ohne weitere Markierung kennzeichnet einen einfachen Funktionsblock,
der einmal durchgeführt wird.
7
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
•
Ein Rechteck mit einem Stern (*) in der rechten oberen Ecke kennzeichnet eine Funktion, die wiederholt durchlaufen wird.
•
Ein Rechteck mit einem Kreis (o) in der rechten oberen Ecke kennzeichnet eine von
mehreren Alternativen.
Zur Darstellung des Programmablaufs ist das Jackson-Diagramm funktionell äquivalent zum
Nassi-Shneiderman-Diagramm. Das Nassi-Shneiderman-Diagramm wächst mit der Verfeinerung nach innen, das Jackson-Diagramm wächst nach unten und außen. Es ist lediglich eine
Geschmacksfrage, welche der beiden Methoden man bevorzugt.
Beide Methoden hatten ihre Blütezeit in den 1970er und 1980er-Jahren, als die strukturierte
prozedurale Programmierung die Technik der Wahl war. Mit dem Aufkommen objektorientierter Programmiersprachen in den 1990er Jahren verloren beide Methoden an Bedeutung, weil
sie die erweiterten Möglichkeiten, die diese Sprachen bieten, nicht adäquat abbilden können.
2.2.4
Unified Modeling Language
UML: In der heutigen Zeit ist als graphische Beschreibungssprache die Unified Modeling
Language ("vereinheitlichte Modellierungssprache", häufig mit UML abgekürzt) aktuell. Sie ist
eine von der Object Management Group (OMG, siehe http://www.omg.org/ ) entwickelte und
standardisierte Beschreibungssprache, um Strukturen und Abläufe in objektorientierten Softwaresystemen darzustellen und unterstützt deshalb vorwiegend die objektorientierte Programmierung.. OMG™ ist eine internationale, frei zugängliches gemeinnütziges (not-for-profit) Computer Industrie-Konsortium. Im Sinne einer Sprache definiert die UML dabei Bezeichner für die
meisten Begriffe, die im Rahmen der Objektorientierung entstanden sind, und legt mögliche
Beziehungen zwischen diesen Begriffen fest. UML definiert des Weiteren grafische Notationsweisen für diese Begriffe und für Modelle von Software oder anderen Abläufen, die man in diesen Begriffen formulieren kann. Damit wurde die UML zur de-Facto-Norm für "technische
Zeichnungen" von Programmen und Abläufen. Sie standardisiert jedoch nicht nur die technische Zeichnung selbst, sondern auch das Datenformat, in dem diese Zeichnung in EDVSystemen abgelegt wird (sog. XMI, eine XML-Variante). Dadurch ermöglicht es die UML im
Prinzip, Modellierungsdaten zwischen Modellierungswerkzeugen unterschiedlicher Softwarehersteller auszutauschen.
Die Väter der UML sind Grady Booch, Ivar Jacobson und James Rumbaugh (auch "die drei
Amigos" genannt). UML wurde am 1997 von der OMG (Object Management Group) als Standard akzeptiert, nachfolgend von ISO Standard akzeptiert (ISO/IEC 19501, in der Version
1.4.2) und wird seit dem von der OMG weiterentwickelt. Die aktuelle Version ist UML 2.0
(noch nicht vollständig abgestimmt). UML 2.0 hat 4 Teile:
Superstructure,
Infrastructure,
Object Constraint Language (OCL) und
Diagram Interchange.
Zur Darstellung von Strukturen unterstützt die Unified Modeling Language 2.0 insgesamt 13 +1
Diagrammtypen:
•
Strukturdiagramme
o
o
o
Klassendiagramm (engl. class diagram)
Objektdiagramm (engl. object diagram)
Komponentendiagramm (engl. component diagram)
8
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
•
Kompositionsstrukturdiagramm (auch: Montagediagramm, engl. composite structure diagram)
o Verteilungsdiagramm (engl. deployment diagram)
o Paketdiagramm (engl. package diagram)
•
Verhaltensdiagramme
o
o
o
o
o
o
o
•
Anwendungsfalldiagramm und Anwendungsfalldiagramm (UML2) (auch: Nutzfalldiagramm, engl. use case diagram)
Zustandsdiagramm (engl. statechart)
Aktivitätsdiagramm (engl. activity diagram)
Sequenzdiagramm (UML2) (engl. sequence diagram)
Interaktionsübersichtsdiagramm (engl. interaction overview diagram)
Kommunikationsdiagramm (engl. communication diagram)
Zeitverlaufsdiagramm (engl. timing diagram)
Kollaborationsdiagramme (nicht zu verwechseln mit den gleichnamigen Diagrammen in
UML 1.x, die in UML 2.0 Kommunikationsdiagramme heißen). Sie stellen einen abstrakten
strukturellen Zusammenhang dar (z.B. zur Dokumentation von Entwurfsmustern) und ähneln den "Collaborations" der Catalysis-Methode.
Es würde hier viel zu weit führen, diese verschiedenen Diagramme zu behandeln, zumal sie in
vielerlei Hinsicht mit den objektorientierten Entwurfsmethoden direkt verknüpft sind. In PRG2 werden Sie einige Diagrammarten kennen lernen.
Alle Diagrammarten sind „Kinder Ihrer Zeit“; spiegeln also direkt die verschiedenen Programmiersprachen-Generationen und Programmierparadigmen wider:
Programmablaufpläne
Generation 1 und 2 (Formular Translation)
mit GOTO, siehe unten.
Nassi-Shneiderman Diagramme
Jackson Diagramme
Strukturiertes Programmieren – Generation 3
Vermeidung von GOTO
UML Diagramme
Objektorientierte Programmierung
3 Iterative Grundstrukturen
Die fallbasierte Verzweigung reicht nicht aus, alle Anwendungen zu implementieren. Man muss
auch sehr viele sehr ähnliche Befehle im linearen Befehlsfluss durchführen, die man leichter als
eine wiederholte Sequenz beschreibt als sie wiederholt hinzuschreiben.
3.1 Schleifen in Python: for und while
Wiederholte Aktionen, also Schleifen, werden in Python mit Hilfe der while- und forAnweisungen implementiert. Für while ist dies in der Form
while expression:
suite
[else:
suite]
Die while-Anweisung wird solange ausgeführt, bis die dazugehörige Bedingung zu „false“
ausgewertet wird. Die Syntax dazu lautet:
9
V O R L E S U N G 4 :
while_stmt
K O N T R O L L S T R U K T U R E N
::=
"while" expression ":" suite
["else" ":" suite]
Die for-Anweisung ist von der Form:
for target in sequenz:
suite
[else:
suite]
Die for-Anweisung ist eine Schleife über Sequenzen einer expression_list. Sie weist die
Elemente in expression_list nacheinander der target_list zu und führt dann jedes Mal
die erste suite aus. Optional kann zum normalen Abschluss der Schleife die else : suite
ausgeführt werden.
for_stmt
::=
expression_list ::=
"for" target_list "in" expression_list ":" suite
["else" ":" suite]
expression ( "," expression )* [","]
target darf alles sein darf, was links von einem Zuweisungsoperator stehen darf, also einfache
Variablen, Tupel (x,y,z), etc.
target_list
target
::=
::=
target ("," target)* [","]
identifier
| "(" target_list ")"
| "[" target_list "]"
| attributeref
| subscription
| slicing
Die Mächtigkeit der Python for-Schleife rührt daher, dass die Sequenz S durch einen beliebigen
Sequenzdatentyp (insbesondere dann macht die for-Schleife Sinn) repräsentiert wird, also entweder ein String, eine Liste, ein Tupel oder eine Menge (set). Man kann als expression also
eine beliebige Variable eines solchen Typs angeben, dann wird über alle Elemente iteriert. Es
können aber auch Teilbereiche angegeben werden, durch Indizierung oder wichtiger durch
Teilbereichsbildung (Slicing), die zusammenhängende Bereiche einer Sequenz extrahiert: Die
Bereichsgrenzen sind mit 0 und der Sequenzlänge vorbelegt:
•
S[1:3] geht von Index 1 bis ausschließlich 3 (also 2)
•
S[1:]
•
S[:-1] nimmt alles bis auf das letzte Element (negative Indizes zählen vom Ende)
geht vom Index 1 bis zum Ende
Auch erweiterte Teilbereichsbildung S[i:j:k] ist zulässig: Das dritte Element k ist die Schrittweite:
•
S[::2] ergibt jedes zweite Element der Sequenz S von 0 bis zum Ende
•
S[::-1] ergibt die Umkehrung der Sequenz
•
S[4:1:-1] holt Elemente von rechts nach links ab Position 4 (inklusive) bis Position 1
(exklusive) ab.
Insbesondere sind als Sequenz natürlich auch Literale zulässig:
10
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
"Ein Text"
"Ein" "zusammengesetzter" "Text"
u"Ein Unicode Text"
[1,2,3,4] eine Liste
["spam", 42, 1.35] eine andere Liste
(1,2,3,4) ein Tupel mit vier Elementen
Einige wenige Beispiele mögen das verdeutlichen:
>>> for x in "Ein Text":
print x
E
i
n
T
e
x
t
>>> for x in "Ein Text"[::-1]:
print x
t
x
e
T
n
i
E
Im Zusammenhang mit Listen sind gerade für for-Schleifen sogenannte
•
Generator-Ausdrücke (und List-Comprehension-Ausdrücke)
und die eingebaute Funktion
•
range ([start,]stop [,step]
interessant.
range liefert eine Liste von aufeinander folgenden Integern zwischen start und stop. Mit nur einem Parameter (stop) ergeben sich die Zahlen 0...stop-1. step ist optional und ist die Schrittweite:
range (0,20,2) ist eine Liste von geraden Zahlen von 0 bis 18.
Beispiel für einen Parameter:
>>> for x in range (4):
print x
0
1
2
3
Beispiel für drei Parameter:
11
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
>>> for x in range (2,10,2):
print x
2
4
6
8
Ein Generator-Ausdruck erzeugt die Elemente in Iterationen einzeln.
(x ** 2 for x in range (4))
Ein List-Comprehension-Ausdruck (list_display) ist eine Listenabstraktion und sammelt
alle Werte eines Ausdrucks für jede Iteration über for Schleifen für die die (optionale) Bedingung wahr ist. Im Gegensatz zum Generator werden die Elemente nicht nur einzeln in der Iteration generiert, sondern als „physikalische“ Liste in ihrer Gesamtheit.
[x ** 2 for x in range (4)]
Beide Formen sind sehr ähnlich, bis auf die äußeren Klammern und liefern in diesem Fall die
gleichen Ergebnisse. Die vollständige Syntax erlaubt noch einiges mehr. Wir besprechen dies in
einer der folgenden Vorlesungen.
Als Beispiel für solche Ausdrücke betrachten wir die Folge der Quadratzahlen:
>>> for x in (x ** 2 for x in range (4)):
print x
0
1
4
9
>>> for x in [x ** 2 for x in range (4)]:
print x
0
1
4
9
Ab Python Version 2.2 versucht der Interpreter mit iter(expression_list)ein IteratorObjekt zu bekommen und dann deren Methode next() wiederholt aufzurufen, bis eine Exeption (StopIteration) ausgelöst wird. Wenn kein Iterator-Objekt existiert (die Methode __iter__
nicht definiert ist), wird stattdessen expression_list mit steigenden Positionen indiziert, bis
ein IndexError ausgelöst wird.
Die for-Anweisung iteriert solange über alle Elemente einer Sequenz, bis keine Elemente mehr
verfügbar sind.
Man darf auch über Sequenzen von Tupeln (auch ggf. verschieden langen) iterieren. Beachten
Sie, dass in target alles stehen darf, was auf der linken Seite des Zuweisungsoperators stehen
darf. Wenn alle Elemente der Sequenz aus Tupeln gleicher Länge bestehen, kann folgende Variante der for-Anweisung benutzt werden:
12
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
for x, y, z in s:
In diesem Fall muss s eine Sequenz von dreistelligen Tupeln sein. In jeder Iteration wird den
Variablen x, y und z der Inhalt des entsprechenden Tupels zugewiesen.
3.2 break – continue - else
Um eine Schleife (while oder for) abzubrechen, verwendet man die break-Anweisung. Die
Funktion im folgenden Beispiel gezeigt werden. Im Beispiel werden solange Eingabezeilen vom
Benutzer gelesen, bis eine leere Zeile eingegeben wird:
>>> while 1:
cmd = raw_input('Befehl eingeben > ')
if not cmd:
break
else:
print cmd
Befehl eingeben > bla bla bla
bla bla bla
Befehl eingeben >
Anmerkung zum obigen Beispiel: raw_input ([prompt]) ist eine weitere eingebaute Funktion,
die Folgendes leistet:
1. Sie druckt den String prompt aus, wenn angegeben,
2. liest dann eine Zeile von sys.stdin und gibt sie als String zurück..
Mit diesem kleinen Codestück können wir also Eingaben vom Benutzer erfragen.
Um in die nächste Schleifeniteration zu springen (wobei der Rest des Schleifenrumpfes übersprungen wird) verwendet man die continue-Anweisung. Diese Anweisung wird weniger häufig verwendet, ist aber nützlich, wenn die Umkehr des Testes und die Einrückung um eine weitere Stufe das Programm zu sehr verschachteln oder verkomplizieren würden. Als Beispiel gibt
die folgende Schleife nur die nicht-negativen Elemente einer Liste aus:
>>> s = [-1,7,0,-2]
>>> for a in s:
if a < 0:
continue # Überspringe negative Elemente
print a
7
0
Die break- und continue-Anweisungen gelten nur in der innersten Schleife, die gerade ausgeführt wird. Falls es notwendig ist, aus einer tief verschachtelten Schleife „auszubrechen“, kann
man eine Ausnahme verwenden. Python stellt keine goto-Anweisung zur Verfügung.
Die else-Anweisung kann auch an Schleifen angefügt werden:
while i < 4:
print i
i = i + 1
13
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
else:
print 'Erledigt'
s = [-1,7,0,-2]
for a in s:
if a == 1:
break
else:
print 'Nicht gefunden!'
>>>
Erledigt
Nicht gefunden!
Die else-Klausel einer Schleife wird nur dann ausgeführt, wenn die Schleife bis zum Ende
ausgeführt wird. Das passiert entweder sofort (wenn die Schleife überhaupt nicht ausgeführt
wird) oder nach der letzten Iteration. Falls die Schleife jedoch frühzeitig mit der breakAnweisung beendet wird, wird auch die else-Klausel übersprungen.
3.3 Realisierungsformen der Iteration
In Programmiersprachen werden iterative Lösungen aller Art allgemein durch Schleifen realisiert: Eine Schleife ist eine Kontrollstruktur in imperativen Programmiersprachen. Sie wiederholt einen Teil des Codes – den so genannten Schleifenrumpf oder Schleifenkörper – so lange, bis eine Abbruchbedingung eintritt. Schleifen, die ihre Abbruchbedingung niemals erreichen oder Schleifen, die keine Abbruchbedingungen haben, nennen wir Endlosschleifen.
Eine Endlosschleife kann nur von „außen“ unterbrochen werden, etwa durch ein Zurücksetzen
(engl. reset), durch eine Unterbrechung (engl. interrupt), in Programmiersprachen durch Ausnahmen (engl. exeptions), Abschalten des Gerätes oder ähnliches. Oft, aber nicht immer, ist eine
Endlosschleife ein Programmierfehler, weil das Programm nicht normal beendet werden kann.
Ist die Aufgabe des Programms jedoch z.B. eine Überwachung Prozessen und Reaktion auf
einen externen (gemeldet durch einen Interrupt) oder internen Fehlerzustand (gemeldet durch
eine Exeption), oder die dauernde Regelung von Echtzeitprozessen in der Industrie, so kann dieses Verhalten gewollt sein. Grundsätzlich ist aber bei Endlosschleifen VORSICHT geboten!
Prinzipiell unterschieden wir
•
die kopfgesteuerte oder vorprüfende Schleife, bei der erst die Abbruchbedingung
geprüft wird, bevor der Schleifenrumpf durchlaufen wird. Meist durch das Schlüsselwort WHILE (= solange-bis) angezeigt.
•
die fußgesteuerte oder nachprüfende Schleife, bei der nach dem Durchlauf des
Schleifenrumpfes die Abbruchbedingung überprüft wird, z.B. durch ein Konstrukt
REPEAT-UNTIL (= wiederholen-bis).
•
die Zählschleife, eine Sonderform der kopfgesteuerten Schleife, meist als FOR
(= für ) -Schleife implementiert.
Die Abbruchbedingung wird meist durch Vergleichsoperatoren und/oder logische Ausdrücke
gesteuert. Bei der letzten Gruppe, der Zählschleifen, unterscheiden wir drei verschiedene Arten:
14
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
(1) Die „Algol-artige“ For-Schleife: Diese Art der For-Schleife dient in erster Linie dazu, eine
Anweisung wiederholt auszuführen. Sie hat folgende Eigenschaften:
Die Anzahl der Wiederholungen stehen schon beim Eintritt in die Schleife fest.
Es gibt eine so genannte Schleifenvariable, die am Anfang auf den Startwert gesetzt wird
und dann jeweils um die Schrittweite verändert wird, bis der Zielwert erreicht ist. Die
Schleifenvariable, der Startwert, die Schrittweite und der Endwert müssen numerisch
sein.
Bei jedem Schritt wird die Anweisung ausgeführt.
(2)
In „C-artigen“ Programmiersprachen hat eine For-Schleife die Form:
for (Initialisierung; Test; Fortsetzung) Anweisung
Hier kann man während der Abarbeitung die Werte der Variablen in dem Test verändern,
so dass die Anzahl der Wiederholungen dynamisch sein können.
(3) Einige Programmiersprachen (zum Beispiel Perl, Python, PHP) bieten ein Konstrukt an,
um einer Variablen nacheinander alle Elemente einer Liste zuzuweisen.
Eine Variante der for-Schleife ist die Foreach-Schleife und hat die Form:
foreach Variable (Werte) { Anweisungen }
Bis auf die rein funktionalen und logischen Programmiersprachen (diese brauchen keine Schleifen) realisieren alle modernen Programmiersprachen eine Auswahl der hier dargestellten Grundstrukturen. Sie unterscheiden sich in den benutzten Schlüsselwörtern, der Art der Klammerung
der Programmblöcke, z.B. ( ), { }, begin ... end, etc. und des Typs der Laufvariablen sowie deren „Inkrementierung“. Mit Python haben wir hier eine sehr leistungsfähige Variante kennen
gelernt.
Schleifenkonstrukte können ähnlich wie Verzweigungskonstrukte gut visualisiert werden.
Schleifenart
Programmablaufplan
Nassi-Shneiderman
vorprüfend
while (B) do
Block
B
falsch
B
Block
wahr
Block
15
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
nachprüfend
repeat Block
Block
Block
until (B)
B
falsch
wahr
B
Zählschleife
C=1
C=1
for(C=1;B;C++)do
Block
falsch
B
B
wahr
C++
Block
Block
C++
Ein kleine historische Notiz zum Befehl GOTO: Noch in den 60er-Jahren waren Sprunganweisungen der Form GOTO <Sprungziel> in Programmen üblich, was bei größeren Programmen nahezu zur Unwartbarkeit führte, da sie schnell kaum noch überschaubar wurden. Das GOTO <Sprungziel>
ist eine direkte Abbildung des Maschinenbefehls JUMP <Sprungziel>. Lediglich musste nun das
Sprungziel keine Programmadresse mehr sein, sondern konnte symbolisch als Zahl oder Name angegeben werden.
Ein Beispiel für so genannten Spaghetti-Code mit GOTO-Anweisungen ist folgendes kleine Programm zur Rettung einer Jungfrau:
20
40
60
70
GOTO 40
UmgeheDasProblem
GOTO 70
if (Durcheinander > TödlicheDosis) then GOTO 60
GOTO 20
RetteJungfrau
...
Ist jetzt klar , was man tun soll? Besser dafür ist allerdings die strukturierte Fassung:
IF ((Durcheinander > TödlicheDosis)
THEN RetteJungfrau
ELSE UmgeheDasProblem
Beurteilen Sie selbst, was leichter verständlich und damit wahrscheinlich weniger fehlerhaft, auf jeden
Fall aber besser wartbar ist.
Schon im Mai 1966 publizierten Böhm und Jacopini in der Zeitschrift Communications of the ACM einen
Artikel, in dem sie zeigten, dass jedes Programm, das GOTO-Anweisungen enthält, in ein GOTO -
16
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
freies Programm umgeschrieben werden kann, das nur mit Verzweigung (IF <Bedingung> THEN ...
ELSE ... ) und einer Schleife (WHILE <Bedingung> DO xxx) arbeitet, gegebenenfalls unter Zuhilfenahme von etwas Kodeduplikation und der Einführung von booleschen Variablen (true/false). In der
gleichen Zeitschrift veröffentlichte im März 1968 Edsger W. Dijkstra seinen legendären Aufsatz
„Go To Statement Considered Harmful“ (Dieser Aufsatz ist das READING dieser Woche!)
Anfang der 70er Jahre wurde damit begonnen, diesen Ansatz zur Selbstbeschränkung auf wenige, typische Elemente umzusetzen. Wegweisend war die Arbeit von Niklaus Wirth mit seiner Programmiersprache Pascal. Die so genannte strukturierte Programmierung etablierte sich und war bis Anfang der
90er Jahre der State-of-the-Art.
Jede Kontrollstruktur besteht wenigstens aus einem Anfangs- und meistens einem Endschlüsselwort. Sprachen wie Pascal, C, Modula-2 (und auch neuere Varianten der Sprache BASIC)
verfügen über Kontrollstrukuren für Wiederholungen. Die Anweisungsblöcke werden durch
Klammern ( ), {} oder Schlüsselwörter begin ... end zusammengefasst.
Das Programm erhält dadurch eine einfach zu überschauende Struktur. Meist wird diese durch
Einrückungen noch verdeutlicht oder man auf die Klammerung und arbeitet nur mit Einrückungen, wie in Python. Die Schlüsselwörter, die Klammerung und die Formatierung erleichtern das Verständnis des Programmcodes.
3.4 Schleifen in anderen Programmiersprachen
In allen Programmiersprachen existiert die Möglichkeit, Befehle iterativ auszuführen.
Sprache
Schleifen
Python
for ziel in sequenz:
…
[else:
…]
Anmerkungen
while ziel in sequenz:
…
[else:
…]
C, C++
continue
beendet die Schleife
break
geht zum Anfang der Schleife
und setzt fort
pass
Platzhalter
for (expression; expression;
expression)
...;
while (expression)
...;
do
...
while (expression);
continue;
break;
Auch C hat das noch!
17
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
goto label;
Java
for (Startwert;Endbedingung;
Anweisung) {
...
};
Zählschleife
while (Bedingung) {
...
};
Vorprüfung
do {
...
} while (Bedingung);
Nachprüfung
do [schleifenvariable anfangsFortran
90/95 (2003) wert, endwert, schrittweite]
...
[if (<log.Ausdruck> )cycle]
[if (<log.Ausdruck> )exit]
...
end do
geht zum Anfang der Schleife
und setzt fort
Schleife verlassen
do while (<log.Ausdruck>)
...
end do
Pascal
GOTO <label>
for Variable := Startwert
[to|downto] Endwert do … ;
Gibt es tatsächlich immer noch!
while <log.Ausdruck> do
begin
…
end;
repeat
…
until <log.Ausdruck>;
goto <label>.
3.5 Iteration als Lösungsmethode
Die Iteration (von lateinisch iterare, "wiederholen"; engl. iteration) ist ein grundlegender Lösungsansatz sowohl in der Mathematik als auch der Informatik mit zwei verschiedenen Ausprägungen:
1. Iteration (in Mathematik und Informatik) ist eine Methode, sich der Lösung eines Rechenproblems schrittweise, aber zielgerichtet anzunähern. Sie besteht in der wiederholten Anwendung des selben Rechenverfahrens. Meistens iteriert man mit Rückkopplung: Die Ergebnisse
eines Iterationsschrittes werden als Ausgangswerte des jeweils nächsten Schrittes genommen bis das Ergebnis (beziehungsweise Veränderung einer Bestandsgröße) zufrieden stellt. Manch-
18
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
mal setzt man auch den nächsten Schritt aus den Ergebnissen der vorherigen zwei Schritten
(oder von noch mehr Schritten) an, zum Beispiel bei der Regula Falsi, siehe unten.
2. In der Informatik wird auch von Iteration gesprochen, wenn ein wiederholter Zugriff auf
Einzelelemente eines zusammengesetzten (aggregierten) Datentyps, etwa Sammlungen (engl.
collections) erfolgt. Beispiel: In einem String “otto“ sollen alle Kleinbuchstaben durch Großbuchstaben ersetzt werden zu “OTTO“. Dazu wird wiederholt auf den String mit veränderten Indexwert zugegriffen, er wird „iteriert“. Eine besondere Bedeutung hat dieses Vorgehen bei
Sammlungen und Strukturen wie Felder (engl. arrays), Listen, Schlüssel-Wert-Paaren (engl. Maps,
Hashes, Dictionaries) oder Mengen (engl. Sets), die wir allerdings detailliert erst demnächst kennen
lernen werden.
Zu 1. Wenn man ein Rechenproblem iterativ lösen will, so muss man sicher sein (beweisen!),
dass die Iterationsfolge konvergiert und dass der Grenzwert mit der gesuchten Lösung übereinstimmt. Die Geschwindigkeit der Konvergenz ist ein Maß dafür, wie brauchbar die Iterationsmethode ist. Grundsätzlich wird ein iteratives Lösungsverfahren dann eingesetzt, wenn das
Ergebnis nicht geschlossen berechenbar ist (zum Beispiel Gleichungen mit transzendenten
Funktionen: sin x + cos x = x, Bestimmung der Nullstellen ab dem Polynomgrad 5, etc.).
Häufig ist eine gute Näherung schon befriedigend. Speziell bei Anwendungsproblemen können
z.B. die Eingabedaten prinzipiell ungenau sein (Messfehler) oder die Parameter der analytischen
Lösung nicht direkt messbar. Dann ist die exakte Lösung des gegebenen Problems nicht notwendigerweise besser als ihre Approximation oder sogar überhaupt nicht verfügbar. Das Iterationsverfahren ist also dann attraktiv, wenn es eine gute Näherung schneller liefert, als die Berechnung der „exakten“ Lösung brauchen würde oder die exakte Lösung nicht existiert.
Denken Sie in diesem Zusammenhang z.B. auch an die prinzipielle Ungenauigkeit von Gleitkommazahlen: Genauer muss und kann man eben nicht rechnen, und so kann die Lösung durchaus mit einer geeigneten iterativen Approximation erfolgen.
3.6 Beispiel für iterative Approximation: die Regula Falsi
Betrachten wir zur Illustration der Notwendigkeit von Approximationen als klassisches Beispiel
die Regula Falsi, genannt die „Regel des falschen Ansatzes“. Hierbei handelt es sich um das
Problem, die Nullstelle (Wurzel) eines Polynoms zu bestimmen. Dies ist iterativ oft rascher gefunden als mit exakten, geschlossenen Formeln.
Dazu wählen wir folgendes Vorgehen:
1. Schritt: Durch Schätzen oder Probieren ermittelt man ein Intervall (a0, b0) wo eine Nullstelle
von y = f(x) ungefähr liegen könnte, z.B. dadurch, dass f(a0)>0, f(b0)<0
2. Schritt: Einen besseren Wert c0 berechnen wir mit der Regula Falsi, die als Näherung ein gerades Kurvenstück annimmt:
c k = a k −1 + ∆ = a k −1 + f (a k −1 ) ⋅
b k −1 − a k −1
f (b k −1 ) − f (a k −1 )
Nun setzte man
a k = c k , b k = b k −1
wenn f (a k −1 ) und f(c k ) gleiches Vorzeichen haben oder
a k = c k −1 , b k = b k
wenn f (b k −1 ) und f(c k ) gleiches Vorzeichen haben.
19
V O R L E S U N G 4 :
K O N T R O L L S T R U K T U R E N
Diese Schritte wiederholen wir mit ansteigenden Index k, bis uns das Ergebnis „genügend genau“ ist und haben mit ck eine Näherung für eine Nullstelle gefunden.
Zur Herleitung dieser Formel beachte den Strahlensatz. Es gilt:
∆
f (ak −1)
=
und damit
bk −1 − ak −1 f (bk −1) − f (ak −1)
ck = ak −1 + ∆ = ak −1 + f (ak −1 ) ⋅
bk −1 − ak −1
f (bk −1 ) − f (ak −1)
∆
f ( bk −1 ) − f (ak −1 )
bk −1 − ak −1
Start a0, b0
1. Iteration: Berechne c1, f(c1)>ε
Berechne a1, b1
2. Iteration: Berechne c2, f(c2)>ε
Berechne a2, b2
3. Iteration: Berechne c3, f(c3)>ε
20
V O R L E S U N G 4 :
Berechne a3, b3
K O N T R O L L S T R U K T U R E N
3. Iteration: Berechne c4, f(c4)<ε: Ende.
Abbildung 1 Veranschaulichung der Regula Falsi
Es liegt auf der Hand, dass es mit einem derartigen Vorgehen diverse grundsätzliche Probleme
gibt, wie die schon angesprochene Konvergenz, die Vollständigkeit der Lösungsmenge, Fortpflanzung von Rundungsfehlern, etc. Diese Problembereiche werden insbesondere in der Numerischen Mathematik behandelt (siehe dort). In der Praxis sind aber iterative Lösungsmethoden trotzdem grundsätzlich sehr, sehr wichtig!
4 Reading
Edsger W. Dijkstras legendärer Aufsatz „Go To Statement Considered Harmful“ aus dem
Jahre 1968, der den Grundstein für die Strukturierte Programmierung legte.
5 Hinweise zu Programmierkursen
Im Internet finden Sie unzählige Angebote. Wenn Sie sich mit anderen Programmiersprachen vertiefend auseinandersetzen wollen (das gehört allerdings nicht zum Inhalt von
PRG-1) empfehle ich folgende Kurse, was nicht heißen soll, dass diese die Besten sind:
Fortran 90/95 siehe Heidrun Kolinsky: Programmieren in Fortran 90/95
http://www.rz.uni-bayreuth.de/lehre//fortran90/index.html
C/C++ siehe Knut Reinert: Programmierkurs C/C++ http://www.inf.fu- berlin.de/inst/agbio/file.php?p=ROOT/Teaching/Lectures/WS0506/05,programmierung.lecture.htm
Java siehe Dietrich Boles: Programmierkurs Java http://www.programmierkurs-java.de/
21
Zugehörige Unterlagen
Herunterladen