• Keine Ergebnisse gefunden

Einfache Algorithmen mit Python – Handout zur Vorlesung Diskrete Strukturen –

N/A
N/A
Protected

Academic year: 2022

Aktie "Einfache Algorithmen mit Python – Handout zur Vorlesung Diskrete Strukturen –"

Copied!
44
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Einfache Algorithmen mit Python

– Handout zur Vorlesung Diskrete Strukturen –

Bauhaus-Universität Weimar

Stefan Lucks 24. September 2015

1. Einleitung

1.1. Warum Python?

Die VeranstaltungDiskrete Strukturenbeschäftigt sich mit Strukturen der Diskreten Mathematik, und mit Algorithmen, die über diese operieren. Um Algorithmen nicht nur abstrakt in Pseudocode zu beschreiben, sondern auf einem Computer laufen lassen zu können, verwenden wir die Programmiersprache Python. Die Hauptgründe für den Einsatz von Python sind:

1. Die Python-Syntax, vor allem für Programmieranfänger, sehr einfach zu verstehen.1 2. Die Python-Syntax ist auch für Programmieranfänger sehr einfach zu schreiben.

Weitere Gründe sind, dass Python eine verbreitete Programmiersprache, gut dokumen- tiert und kostenlos auf allen gängigen Systemen installierbar ist. Außerdem hat, wer

1Wie in anderen Programmiersprachen, braucht man auch in Python manchmal Phrasen, die für Pro- grammieranfänger unverständlich sind, quasi “magisch”. Das fachdidaktische Konzept dieses Handouts – wie das der Veranstaltung Diskrete Strukturen insgesamt – zielt darauf ab, Dingeverständlich zu machen, und auf “magische Aspekte”, die erst für Fortgeschrittene verständlich werden, so weit wie möglich zu verzichten. Einer der Vorteile von Python ist, dass man derarige Phrasen nicht für die ersten Programmierschritte benötigt.

So würde man eine Unterprogrammdefinition als Beispiel für Anfänger in Java mit public sta- tic void einleiten. Für Anfänger könnte man statt “public static” auch “abracadabra” schreiben.

Der Sinn von “public” und “static” erschließt sich erst Fortgeschrittenen. In Python leitet man eine Unterprogrammdefinition mitdef ein, was unschwer als Abkürzung von “define” erkennbar ist.

(2)

Python kennt, jederzeit ein nützliches Werkzeug um mathematische Aussagen nachzu- rechnen, Vermutungen zu überprüfen oder Zufallsprozesse zu simulieren.

Schließlich haben die meisten Programmiersprachen zur Repräsentation ganzer Zahlen einen Typ int (bzw. Integer o. ä.), welcher tatsächlich nur ganze Zahlen bis zu ei- ner bestimmten Größe darstellen kann (oft 232, oder 264) und Fließkommazahlen, die zwar größere Werte repräsentieren können, aber nur näherungsweise, nicht genau. Für die Veranstaltung Diskrete Strukturen ist Python besser geeignet, weil die Darstellung

“mathematischer” ganzer Zahlen ein fester Teil der Sprache ist (nicht etwa Teil einer ergänzenden Bibliothek). Zum Beispiel ist 2200 (exakt, nicht bloß näherungsweise):

>>> 2∗∗200

1 6 0 6 9 3 8 0 4 4 2 5 8 9 9 0 2 7 5 5 4 1 9 6 2 0 9 2 3 4 1 1 6 2 6 0 2 5 2 2 2 0 2 9 9 3 7 8 2 7 9 2 8 3 5 3 0 1 3 7 6

1.2. Dies ist keine Einführung in die Programmierung mit Python!

Es wird nur beschrieben, wie man einfache Algorithmen in Python implementiert und einfache Probleme mit Python löst. Mehr wird für die Veranstaltung Diskrete Struktu- ren nicht gebraucht! Wer mit Python komplexe Probleme lösen will, oder sich überhaupt intensiver mit dieser Programmiersprache auseinandersetzen will, sollte auf ein ordentli- ches Lehrbuch zurückgreifen – ein Handout wie dieses ist allemal zu kurz. Es gibt viele gute Lehrbücher. ich empfehle

Practical Programming: An Introduction to Computer Science Using Python 3

von Paul Gries, Jennifer Campbell und Jason Montojo.2

Insbesondere verzichte ich darauf, jene Aspekte von Python zu vermitteln, die trotz der einfachen Syntax sehr wohl kompliziert sind, zum Beispiel die Unterscheidung zwischen veränderlichen (“mutable”) und unveränderlichen Objekten – bzw. das Speichermodell, das der Python-Semantik zugrunde liegt. Die Unterstützung von objektorientierter Pro- grammierung und Vererbung wird nicht beschrieben. Python-Listen werden zwar er- wähnt, aber nur mit Bezug auf eine Nutzung analog zu Arrays anderer Programmier- sprachen usw. Insgesamt wäre eine Liste der Features von Python die in diesem Handout nicht beschrieben werden länger als das Handout selbst.

2Siehehttps://archive.org/details/PracticalProgramming.

(3)

1.3. Aufgaben (diesen Text sollten Sie nicht nur lesen!)

Benutzen Sie eine Python-Shell3 und schreiben Sie Python-Programme!4

In Abschnitt 1.1 haben Sie bereits gesehen, wie die Interaktion in einer Python-Shell in diesem Handout dargestellt wird. Wir haben interaktiv 2200 berechnet und das exakte Ergebnis ausgegeben. IDLE (und andere Python-Shells) geben “>>> ” (drei “größer”- Zeichen, danach ein Leerzeichen) als “Eingabeprompt” aus, danach dieEingabe des Be- nutzers, in diesem Fall “2**200” für 2200. In der nächsten Zeile, ohne Eingabeprompt, steht dieAusgabe von Python (“160. . .376”).

Listings von Programmdateien in Python werden in diesem Handout durch “Listing . . . ” (oben) und Zeilennummern (links) kenntlich gemacht. Oft wird eine Datei stückweise in mehreren Fragmenten dargestellt. Die vollständigen Dateien können sie auch auf der Webseite zur Veranstaltung finden. Aber grundsätzlich können Sie die Dateien anhand der Zeilennummern auch aus den hier dargestellten Fragmenten vollständig zusammen- setzen. Anhand der Zeilennummern werden Sie feststellen, dass einzelne Zeilen zu fehlen scheinen – dies sind allerdings grundsätzlich nur Leerzeilen. Diese dienen dazu, inhaltlich zusammenhängende Code-Fragmente von anderen Code-Fragmenten abzugrenzen.

1. Rufen Sie Python auf und berechnen Sie 2100, sowie die Summe2100+ 2200. 2. Schreiben Sie eine Python-Datei “hallo.py” mit dem folgenden Programm:

Listing 1: Zeilen 1–3 aus hallo.py 1 def h a l l o ( ) :

2 """ P r i n t s some g r e e t i n g s . """

3 p r i n t ( " H a l l o Universum ! " )

3. Führen Sie Ihr Programm in Ihrer Python-Shell aus:

>>> import h a l l o

>>> h a l l o . h a l l o ( ) H a l l o Universum !

4. Was wird angezeigt, wenn Sie die Hilfefunktion von Python aufrufen?

>>> h e l p ( h a l l o )

3Ich selbst verwende meistens die IDLE Umgebung (“Integrated DeveLopment Environment”).

4Mit IDLE: Im “File”-Menü Unterpunkt “New File”, “Open . . . ” oder ggf. “Recent File” aufrufen, in dem Fenster, das sich neu öffnet schreiben, und dann dort das “File”-Menü aufrufen um das Ergebnis mit

“Save” oder “Save as . . . ” zu sichern.

(4)

2. Python als “Taschenrechner”

Wie erwähnt kann man mit Python nicht nur Programme schreiben, man kann auch interaktiv damit arbeiten – in etwa wie mit einem (extrem leistungsfähigen) Taschen- rechner.

Wenn wir Python aufrufen, werden wir von Python begrüßt, etwa so:

Python 3 . 4 . 3 + ( d e f a u l t , Jun 2 2 0 1 5 , 1 4 : 0 9 : 3 5 ) [GCC 4 . 9 . 2 ] on l i n u x

Type " c o p y r i g h t " , " c r e d i t s " or " l i c e n s e ( ) " f o r more i n f o r m a t i o n .

Wie Sie sehen, arbeite ich zum Zeitpunkt an dem dieses Handout entsteht mit Python 3.4.3. Sie können mit jeder Version 3.x.y arbeiten (kurz: mit Python 3). Die Vorgänger- Version Python 2 ist immer noch weit verbreitet, aber viele der in dieser Einleitung präsentieren Beispiele müssten für Python 2 umgeschrieben werden.

Beispiel:Wieviele Sekunden hat ein Jahr?

>>> j a h r e = 1

>>> t a g e = 365∗j a h r e

>>> s t u n d e n = 24 ∗ t a g e

>>> minuten = 60 ∗ s t u n d e n

>>> sekunden = 60 ∗ minuten

>>> sekunden 31 536 000

Das war einfach!Nun suchen wir die Quadratwurzel von 3343. Genauer, wir suchen die größte ganze Zahl, die kleiner oder gleich der Quadratwurzel von 3343 ist.

>>> 40∗40 1600

>>> 50∗50 2500

>>> 60∗60 3600

Die gesuchte Zahl liegt demzufolge irgendwo zwischen 50 und 60. Im nächsten Schritt könnten wir, z. B., 55 probieren . . .

Geht es auch bequemer? Wozu haben wir einen Computer – und Python?

>>> x = 1

(5)

>>> while x∗x < 3 3 4 3 : x = x + 1

>>> p r i n t ( x ) 58

Was haben wir hier gemacht?

• Wir haben eine Variablex auf 1 gesetzt.

• Wir haben eine Schleife begonnen, die so lange durchlaufen wird, bisx2 >3343ist.

• In jedem Schleifendurchlauf haben wir x um eins erhöht.

• Dann lassen wir die Schleife laufen (leere Eingabe von uns).

• Am Ende geben wirx aus.

Das Ergebnis istfast richtig:

>>> p r i n t ( x∗x ) 3364

>>> p r i n t ( ( x−1)∗( x−1)) 3249

Wir haben die kleinste ganze Zahl, die größer als die gesuchte Quadratwurzel ist, denn wir berechnen so lange x = x + 1 bis die Variable x zu groß ist. Also machen wir das letzte x = x + 1 rückgängig:

>>> p r i n t ( x−1) 57

Das ist das Ergebnis, das wir gesucht haben!

Um mehrere Quadratwurzeln zu berechnen, möchten wir nicht immer wieder x = 1 und whilex∗x < ...: usw. eingeben. Deshalb ist es sinnvoll ein Programm (genauer, eine Funktion) die Quadratwurzeln berechnen kann zu definieren.

>>> def s q r t ( y ) : x = 1

while x∗x < y : x = x + 1 return x−1

(6)

>>> s q r t ( 2 3 4 2 3 4 2 3 ) 4839

Stimmt das Ergebnis eigentlich? Berechnet der Algorithmus, den wir geschrieben haben, die richtigen Werte?

Wir verzichten hier auf den Versuch, die Korrektheit unseres Programms formal zu be- weisen. Aber um mögliche Fehler zu finden, kann man Programmetesten.5

EinTestfall besteht, im einfachsten Fall, aus

• einer Eingabe für das Programm

• undder erwarteten Ausgabe.6

Man sollte die Testfälle so wählen, dass alle Klassen typischer Probleme und dazu alle Grenzfälle (mindestens) einmal getestet werden. Für sqrt sind das

• die Eingabe 0 als Grenzfall, erwartetes Ergebnis 0

• drei Klassen von typischen Eingaben:

– Eingaben, die etwas kleiner als eine Quadratzahl sind (wir wählen 3343*3343-1, erwartetes Ergebnis 3342), – Eingaben, die eine Quadratzahl sind

(wir wählen 3343*3343, erwartetes Ergebnis 3343), und – Eingaben, die etwas größer als eine Quadratzahl sind

(wir wählen 3343*3343+1, erwartetes Ergebnis 3343)

>>> s q r t ( 0 ) 0

>>> s q r t (3343∗3343−1) 3342

>>> s q r t ( 3 3 4 3∗3 3 4 3 ) 3342

>>> s q r t (33 43∗3343 +1) 3343

5MitTestenkann man zwar nicht nachweisen, dass ein Programm richtig ist, aber ein erfolgreicher Test beweist, dass es falsch ist. Schlägt ein systematischer Test fehl, ist das Programm vielleicht korrekt.

6Es ist wichtig, dass man sichvor dem Test gehau überlegt, welche Ergebnisse richtig sind.

(7)

Hurra! Unser Test ist erfolgreich!7 Die Quadratwurzel von 3343 * 3343 ist 3343, nicht 3342. Tatsächlich liefert unser Programmimmer falsche Werte, wennyeine Quadratzahl ist. Denn die Schleife bricht schon ab, wenn x∗y gleich y ist, das richtige Ergebnis wäre x, aber unser Unterprogramm gibt x−1 zurück.

Wir schreiben also nunwhilex∗x <= y: stattwhile x∗x < y:

>>> def s q r t ( y ) : x = 1

while x∗x <= y : x = x + 1 return x−1

Nun rufen wir unsere Testfälle noch einmal auf.

>>> s q r t ( 0 ) 0

>>> s q r t (3343∗3343−1) 3342

>>> s q r t ( 3 3 4 3∗3 3 4 3 ) 3343

>>> s q r t (33 43∗3343 +1) 3343

Diesmal schlägt der Test fehl – das Programm scheint korrekt zu sein.

Einige Elemente von Python:

• Variablen, die Namen haben (sogenannte “Bezeichner”) jahre, tage, . . . x,

• die Zuweisung von Werten an Variablen: jahre = 1, tage = 365∗Jahre, . . . x = 1, x = x + 1

• arithmetische Ausdrücke: 365∗Jahre, . . . , x∗x, x+1,

• logische Ausdrücke: (x∗x < 3343),

• eineSchleife, die mit demSchlüsselwort while beginnt und

• der Aufruf eines vordefinierten Unterprogramms print.

7Das bedeutet natürlich, dass unser Programm nachgewiesenermaßen fehlerhaft ist.

(8)

Bezeichner sind Namen für in einem Programm auftretende Dinge, unter anderem für Variablen und Unterprogramme, bestehen aus Groß- und Kleinbuchstaben, dem Unter- strich, und Ziffern. Eine Variable kann nicht mit einer Ziffer beginnen:

• Mögliche Bezeichner sind “name”, “Name”, “ein_name”, “name1”, “ name_1”, . . .

• Nicht möglich sind “na me”, “1_name”, “na?me”.

EineZuweisung wie z = a + 2 ∗b besteht aus

• einem Variablennamen (hier z),

• gefolgt von einem Gleichheitszeichen (hier =),

• gefolgt von einem Ausdruck (hier a + 2∗ b).

Bei der Zuweisung wird zuerst der Wert des Ausdrucks berechnet, dann wird die Variable auf diesen Wert gesetzt. Der Variablenname muss nicht zuvor bekannt sein (d. h., Py- thon braucht keine “Variablendeklarationen”, wie andere Programmiersprachen). Hat die Variable bereits einen Wert, kann sie im Ausdruck verwendet werden, wie in x=x+1.

Aus den Bezeichnern, den Operatoren für die Grundrechenarten und Klammern kann manarithmetische Ausdrücke konstruieren. Man beachte, dass es zwei verschiedene Divi- sionen gibt: “/” für die Division von Fließkommazahlen (Näherungen von reellen Zahlen) und “//” für die ganzzahlige Division ohne Rest. Den Rest der ganzzahligen Division erhält man mit “%”. Zum Beispiel ist 3343 geteilt durch 57 gleich 58, Rest 37:

>>> 3343 / 57 5 8 . 6 4 9 1 2 2 8 0 7 0 1 7 5 4

>>> 3343 // 57 58

>>> 3343 % 57 37

>>> 57 ∗ 58 + 37 3343

>>> ( 5 7 ∗ 5 8 ) + 37 3343

>>> 57 ∗ ( 5 8 + 3 7 ) 5415

Wie man sieht, werden gemischte Ausdrücke wie in der Mathematik üblich ausgewertet (“Punktrechnung vor Strichrechnung”, . . . )

Logische Ausdrücke erlauben das Vergleichen von Werten. Man beachte, dass das Gleich-

(9)

heitsszeichen nicht seine mathematische Bedeutung hat; es kennzeichnet ja Zuweisungen.

Für den Test auf Gleicheit dient das doppelte Gleichheitszeichen8 “==”:

>>> x = 3

>>> y = 2

>>> p r i n t ( x > y , x >= y , x = y , x <= y , x < y ) F i l e "< s t d i n >" , l i n e 1

S y n t a x E r r o r : non−keyword a r g a f t e r keyword a r g

>>> p r i n t ( x > y , x >= y , x == y , x <= y , x < y ) True True False False False

>>> x = 2

>>> p r i n t ( x > x , x >= x , x == x , x <= x , x < x ) False True True True False

Komplexe logische Ausdrücke kann man mit and,or,not und Klammern realisieren:

>>> x = 3

>>> y = 2

>>> z = x > y or ( x == y and x < y )

>>> p r i n t ( z ) True

>>> p r i n t ( (not z ) or ( x < y ) ) False

Einewhile-Schleife besteht

• aus dem Schlüsselwort while,

• gefolgt von einem logischen Ausdruck, der wahr (True) falsch (False) sein kann,

• gefolgt von einem Doppelpunkt,

• gefolgt von dem Schleifenrumpf, der aus beliebig vielen Zeilen bestehen kann.

Einewhile-Schleife kann beliebig oft ausgeführt werden – auch gar nicht (also null-mal).

Genauer gesagt, die Schleife wird so lange durchlaufen, bis der logische Ausdruck hinter whilefalsch (False) ist.

Dagegen steht bei der for-Schleife sofort fest, wie oft sie durchlaufen wird: Nach forVariable in range(Start, Ziel ):

8Das ist eine häufige Fehlerquelle, die Python leider mit vielen anderen Programmiersprachen teilt.

(10)

wird der Schleifenrumpf (Start-Ziel)-mal durchlaufen; range(Ziel) ist eine Kurzform für range(0, Ziel) Dabei nimmt Variable die Werte Start, Start+1, . . . , Ziel-1 an.9

>>> f o r i in r a n g e ( 2 , 4 ) : p r i n t ( i )

2 3

>>> f o r i in r a n g e ( 4 , 4 ) : p r i n t ( i )

>>> f o r i in r a n g e ( 4 ) : p r i n t ( i )

0 1 2 3

Mit dem Unterprogramm print können wir unsere Ergebnisse ausgeben. Der Bezeichner print istvordefiniert. Vordefinierte Bezeichner kann man in Python neues Objekt zuwei- sen. Zum Beispiel kann man print eine Zahl zuweisen. Das ist aber eine schlechte Idee.

Erstens wird macht man es sich selbst und anderen schwierig, das Programm irgend- wann zu lesen und seine Funktion zu verstehen, zweitens hat jenes Unterprogramm zum Ausgeben von Werten keinen Namen mehr. Wie kann man es dann aufrufen?

>>> p r i n t ( 3 ) 3

>>> p r i n t = 3

>>> p r i n t 3

>>> p r i n t ( 3 )

Traceback ( most r e c e n t c a l l l a s t ) : F i l e "< s t d i n >" , l i n e 1 , in <module>

TypeError : ’ i n t ’ o b j e c t i s not c a l l a b l e

Einige Schlüsselwörter wiewhilesind reserviert, und als Bezeichner verboten:

>>> while = 3

F i l e "< s t d i n >" , l i n e 1 while = 3

^

9Kein Tippfehler! Die Variable durchläuft ein halboffenes Intervall. Der Start ist im Intervall enthalten, das Ziel nicht. Das ist eine andere Fehlerquelle, die Python mit vielen Programmiersprachen teilt.

(11)

S y n t a x E r r o r : i n v a l i d s y n t a x

In diesem Handout werden die reservierten Schlüsselwörterfettdargestellt.

Es gibt nur wenige reservierte Schlüsselwörter. Ihre Liste erhält man mit der Hilfe- Funktion von Python. Man ruft zuerst das vordefinierte Unterprogramm help() auf und gibt dann keywordsein:

h e l p > keywords

Here i s a l i s t o f t h e Python keywords . E n t e r any keyword t o g e t more h e l p .

False def i f r a i s e

None del import return

True e l i f in try

and e l s e i s while

as except lambda with

a s s e r t f i n a l l y nonlocal y i e l d

break f o r not

c l a s s from or

continue

Aufgaben:

1. Das Unterprogramm cqrt (“cube-root”) berchne die dritte Wurzel (Kubikwurzel), abgerundet zur nächsten ganzen Zahl. Geben Sie vier geeignete Testfälle an.

2. Schreiben Sie ein das Unterprogrammm cqrt. Überzeugen Sie sich davon, dass es Ihre Tests besteht.

3. Benutzen Sie das Unterprogramm, um die Kubikwurzel von 3343 zu berechnen.

3. Ein Python-Modul

Natürlich wollen wir unsere Funktion sqrt nicht jedesmal neu eintippen wenn wir ei- ne Quadratwurzel zu berechnen haben. Stattdessen öffnen wir eine Datei sqrt.py und schreiben unsere Funktion dort auf.

(12)

Listing 2: Zeilen 1–11 aus sqrt.py

1 """ module s q r t : F u n c t i o n s t o compute t h e s q u a r e−r o o t . """

2

3 def s q r t ( y ) : 4 """

5 Computes t h e l a r g e s t i n t e g e r x w i t h x∗x <= y . 6 R e q u i r e s y t o b e an i n t e g e r >= 0 .

7 """

8 x = 1

9 while x∗x <= y : # p r e v i o u s v e r s i o n w i t h x∗x < y was b u g g y !

10 x = x + 1

11 return x−1

Die Texte zwischen den dreifachen Anführungszeichen sind Docstrings. Der Docstring in der ersten Zeile dokumentiert, was das Modul als Ganzes leistet. Der Docstring in Zeile 4–6 die Leistung des Unterprogramms sqrt. Speziell sollte man angeben, was eine Funktion für den Aufrufer leistet (“computes . . . ”), und welche Vorbedingung beim Aufruf der Funktion beachtet werden muss (“requires . . . ”). Texte, die mit einer Raute (“#”) beginnen, werden bis zum Zeilenende von Python ignoriert. Es sind Kommentare.10 Ein Modul kann man mit import laden. Dann kann man die im Modul definierten Funktionen aufrufen – oder sich auch den Inhalt des Docstrings anzeigen lassen. Für uns bedeutet dies, dass wir nun jederzeit das Modul sqrt importieren und durch den Aufruf von sqrt . sqrt(n) die (abgerundete) Quadratwurzel einer Zahl nberechnen können.

>>> import s q r t

>>> h e l p ( s q r t . s q r t ) doc

Help on f u n c t i o n s q r t in module s q r t : s q r t ( y )

computes t h e l a r g e s t i n t e g e r x with x∗x <= y r e q u i r e s y t o be an i n t e g e r >= 0

>>> s q r t . s q r t ( 3 3 4 3 ) 57

Die Dopplung sqrt . sqrt ergibt sich daraus, dass Modul und Funktion beide den gleichen Namen haben. Rufen Sie zum Vergleich einmal help(sqrt) auf!

10Kommentare sind ein wichiger Teil der Programmdokumentation, obwohl sie vom Python-Übersetzer ignoriert werden, also semantisch wirkungslos sind. Unkommentierte Programme sind selten verständ- lich, und es ist oft schwierig, Fehler zu beseitigen. Allerdings sind Kommentare wie Medikamente: “zu viel” kann genau so schaden wie “zu wenig”.

(13)

Natürlich wollen wir sqrt . sqrt auch testen – das sollten wir jetzt und nach jeder Änderung tun. Tests sollten umfassend und ohne großen Arbeitsaufwand wiederholbar sein. Deshalb sollte man nach Möglichkeit die Testfälle (Eingaben und erwartete Ausgaben) einmal festlegen und dann automatisch vom Computer durchführen lassen.

Python hat eine elegante Möglichkeit, das Testen zu automatisieren: Das Modul doctest.

Wir ergänzen unser Modul sqrt wie folgt:

Listing 3: Zeilen 13–24 aus sqrt.py 13 """

14 >>> i m p o r t s q r t 15 >>> s q r t . s q r t ( 0 ) 16 0

17 >>> s q r t . s q r t ( 3 3 4 3∗3 3 4 3 ) 18 3343

19 >>> s q r t . s q r t (3343∗3343−1) 20 3342

21 >>> s q r t . s q r t (3343∗3343+1) 22 3343

23 >>>

24 """

Das sieht aus wie eine interaktive Python-Sitzung mit unseren Tests. Tatsächlich steht es aber im Programm. Man achte auf die drei Anführungszeichen vor und nach den Test- fällen! Nun benutzen wir die Funktion doctest. testfile um unser Modul zu testen. Im Prinzip simuliert doctest die interaktive Python-Sitzung, die wir beim manuellen Testen hätten, und vergleicht ob bei den (durch den Eingabeprompt >>> gekennzeichneten) Eingaben genau die erwarteten Ausgaben erzeugt werden. Deshalb ist auch der leere Eingabeprompt in Zeile 23 wichtig. Ohne den Eingabepromt wüsste doctest nicht, in welcher Zeile die Antwort auf >>> sqrt.sqrt (3343∗3343+1) in Zeile 22 endet. Es würde irrtümlich einen Fehler melden.

>>> import d o c t e s t

>>> d o c t e s t . t e s t f i l e ( " s q r t . py " ) T e s t R e s u l t s ( f a i l e d =0 , a t t e m p t e d =5)

Die Antwort ist, dass fünf Tests durchgeführt wurden, und dass kein Fehler aufgetreten ist.11Funktionieren unsere Tests überhaut? Am einfachsten findet man das heraus, indem man doctest mit einem fehlerhaften Programm testet. Deshalb führen wir den Fehler in Zeile 8 wieder ein:

11“Fünf Testfälle? Wir haben doch nur vier Testfälle spezifiziert!” Stimmt, aber doctest betrachtet die Eingabe “>>>importsqrt” auch als Teil eines Testfalls, bei dem das erwartete Ergebnis darin besteht, dass nichts ausgegeben wird.

(14)

8 while x∗x < y : # ERROR! Change t h i s b a c k t o x∗x <= y ! ! !

Nach dieser Manipulation rufen wir noch einmal doctest. testfile auf:

>>> import d o c t e s t

>>> d o c t e s t . t e s t f i l e ( " s q r t . py " )

∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ F i l e " . / s q r t . py " , l i n e 1 6 , in s q r t . py

F a i l e d example :

s q r t . s q r t ( 3 3 4 3∗3 3 4 3 ) Expected :

3343 Got :

3342

∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ ∗ 1 i t e m s had f a i l u r e s :

1 o f 5 in s q r t . py

∗∗∗T e s t F a i l e d∗∗∗ 1 f a i l u r e s .

T e s t R e s u l t s ( f a i l e d =1 , a t t e m p t e d =5)

Hurra! Der Test findet den Fehler! Bevor wir weitermachen, berichtigen wir den Fehler.

Dann rufen wir wieder doctest. testfile , auf, um uns davon zu überzeugen, dass wir den Fehler wieder losgeworden sind und keinen neuen Fehler eingeführt haben (ganz wichtig!).

Module in Python:

• Unterprogramme, die wir wiederverwenden wollen, können inModulen gesammelt werden – typischerweise in einer Datei “hModulnamei.py”.

• Um die Unterprogramme in einem Modul zu nutzen, muss man das Modul zuerst mit importhModulnamei importieren, dann kann ein Unterprogramm unter dem NamenhModulnamei.hNamei aufrufen.

• Nicht nur Python, sondern auch Menschen müssen Programme/Module lesen und verstehen können. Sparsam eingesetzteKommentare sind dabei wichtig. In Python beginnen Kommentare mit eine Raute (“#”) und enden am Zeilenende.

• Die Funktionsweise von Modulen und Unterprogrammen sollte man durchDocstrings dokumentieren. Diese sollten insbesondere beschreiben

(15)

– was ein Unterprogramm anbietet (wozu man es aufrufen soll) und – was es verlangt (welche Vorbedingungen erfüllt sein sollen).

• Gut gepflegte Docstrings sorgen unter anderem dafür, dass man beim Aufruf der Hilfefunktion help die Information bekommt die man benötigt um ein Modul bzw.

Unterprogramm zu nutzen.

• Für das automatische Testen eines Moduls kann man die Testfälle in die Datei

“hModulnamei.py”, in einer Syntax, die einer interaktiven Python-Sitzung enst- spricht, schreiben.

• Für den eigentlichen Test ruft man doctest . testfile auf.

Aufgaben:

1. Schreiben Sie ein Modul cqrt mit Ihrem Unterprogramm cqrt . cqrt zur Berechnung von Kubikwurzeln. Testen Sie Ihr Modul mit doctest.

2. Bauen Sie einen Fehler in das Modul ein.

3. Überpüfen Sie, ob doctest den Fehler findet.

4. Berichtigen Sie den Fehler, und vergewissern Sie sich dann, dass doctest keinen Fehler mehr findet.

4. Zufallsexperimente mit Python

Wenn man mit einem fairen Würfel 60-mal würfelt, wie oft hat man dann eine 6 gewür- felt? Wer spontan “10-mal” antwortet, hat zwar im Durchschnitt recht – aber wenn man 9-mal oder 11-mal eine Sechs würfelt, sollte man auch nicht überrascht sein.

Angenommen, wir würfelnn-mal. Statistisch erwarten wirµ=n/66-en. Seix∈ {0, . . . , n}

die Anzahl an 6-en, die wir bei einem Experiment tatsächlich erwürfeln. Wir interessieren uns für die folgenden Fragen:

1. Wahrscheinlicheit des Erwartungswertes: Wie wahrscheinlich ist x=µ?

2. Absolute Abweichung: Wie wahrscheinlich istµ−δ ≤x≤µ+δ für δ≥1?

(16)

3. Relative Abweichung: Wie wahrscheinlich istµ−cµ≤x≤µ+cµfür c >0?

Natürlich kann man diese Fragen mathematisch beantworten. Aber wir werden die Ant- wort einfach “auswürfeln”. Besser noch: Wir lassen Python die Arbeit tun! Um Zufallser- eignisse zu simulieren, brauchen wir einen Zufallsgenerator. Dazu nutzen wir die Funktion randrange aus dem Paket random. Ganzzahlige Zufallswerte zwischen a und b (einschließ- lich) erhalten wir durch den Aufruf von random.randrange(a, b+1)12:

Listing 4: Zeilen 1–8 aus dice.py 1 import random

2

3 def throw ( ) : 4 """

5 S i m u l a t e s a d i e t h r o w .

6 R e t u r n s a random v a l u e i n { 1 , 2 , 3 , 4 , 5 , 6 } . 7 """

8 return random . r a n d r a n g e ( 1 , 7 ) # u n i f o r m l y from { 1 , 2 , 3 , 4 , 5 , 6 }

>>> import d i c e

>>> d i c e . throw ( ) 4

>>> d i c e . throw ( ) 1

>>> d i c e . throw ( ) 4

>>> d i c e . throw ( ) 6

>>> d i c e . throw ( ) 5

>>> d i c e . throw ( ) 4

Wir haben 6-mal gewürfelt und – wie statistisch zu erwarten – genau eine 6 gewürfelt.

Nun lassen wir unseren Python öfter würfeln. Python übernimmt dabei gleich das Zählen wie oft eine 6 gewürfelt wurde:

Listing 5: Zeilen 10–16 aus dice.py 10 def c o u n t _ s i x ( r ) :

11 """ Throws a d i e r t i m e s ; c o u n t s how o f t e n a s i x i s thrown . """

12 r e s u l t = 0

13 f o r t h r o w s in r a n g e ( r ) :

12Analog zu range ist die Zufallszahl größer oder gleicha, aber echt kleiner alsb+ 1.

(17)

14 i f throw ( ) == 6 :

15 r e s u l t = r e s u l t + 1

16 return r e s u l t

Man beachte die if-Kontrollstuktur: Nur wenn das Ergebnis von throw() gleich 6 ist, wird der Wert der Variablen result um 1 erhöht, sonst wird er nicht verändert.

Was für Ergebnisse liefert uns Python nun?

>>> d i c e . c o u n t _ s i x ( 6 ) 0

>>> d i c e . c o u n t _ s i x ( 6 0 0 ) 101

>>> d i c e . c o u n t _ s i x ( 6 0 0 0 0 ) 9951

>>> d i c e . c o u n t _ s i x ( 6 0 0 0 0 0 0 ) 999806

Wie man sieht, ist die Anzahl an 6-en nahe an dem statistisch zu erwartenden Ergebnis, aber bei keinem unserer Experimente hat sie exakt diesen Wert.

Nun wiederholen wir das Experiment 1000-mal. Das Ergebnis ist eineListe:

Listing 6: Zeilen 18–29 aus dice.py 18 def e x p e r i m e n t ( n ) :

19 """

20 Counts how o f t e n c o u n t _ s i x ( n ) g i v e s a s p e c i f i c v a l u e ; 21 t h i s i s r e p e a t e d 1000 t i m e s ;

22 e x p e r i m e n t ( n ) r e t u r n s a l i s t ’ r e s u l t s ’ o f l e n g t h n+1

23 ’ r e s u l t s [ x ] ’ i s t h e number o f t i m e s c o u n t _ s i x ( n ) r e t u r n e d x . 24 """

25 r e s u l t s = [ 0 ]∗( n+1)

26 f o r r e p e t i t i o n s in r a n g e ( 1 0 0 0 ) : 27 x = c o u n t _ s i x ( n )

28 r e s u l t s [ x ] = r e s u l t s [ x ] + 1 29 return r e s u l t s

Etwas vereinfacht leistet das Unterprogramm experiment das Folgende:

• Die Zuweisung results = [0] ∗ (n+1) in Zeile 25 legt eine Liste ausn+ 1Werten an, auf die man wie auf Variablen unter den Namen results [0] , . . . , results [n]

zugreifen kann. Diese Werte sind alle auf Null gesetzt.

(18)

• Zeile 26–28: Insgesamt wird count_six 1000-mal aufgerufen. Für jedes der 1000 Ergebnisse x wird der Wert results [x] um eins vergrößert.

• In Zeile 29 wird diese Liste results Ergebnis zurückgegeben.

Probieren wir aus, was passiert:

>>> d i c e . e x p e r i m e n t ( 6 0 )

[ 0 , 0 , 2 , 4 , 1 4 , 3 7 , 5 4 , 7 4 , 1 3 1 , 1 2 9 , 1 4 7 , 1 3 5 , 8 8 , 8 4 , 3 6 , 2 8 , 1 9 , 1 2 , 3 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]

Also galt am Ende des Experimentes results [0]==0 results [1]==0 results [2]==2, . . . , results [10]==147, . . . Das bedeutet, bei keinem der 1000 Versuche haben wir nur eine oder keine 6 gewürfelt, bei immerin zwei von 1000 Versuchen hatten wir genau zwei 6-en, . . . , in 14.7 % der Versuche hatten wir genau den Erwartungswert von 10 6-en, . . .13 Insgesamt die Darstellung des Ergebnisses etwas unübersichtlich. Besser kann man sie als Histogramm darstellen, siehe Abbildung 1.

Abbildung 1: Die Ergebnisse des Zufallsexperimentes nach 1000 Wiederholungen:

Wie oft wirft man bei 60 Würfen mit einem fairen Würfel eine 6?

Im nächsten Kapitel werden wir sehen, wie man mit Python sehr einfach ein Paket entwickeln kann um derartige Histogramme zu erzeugen.

Zunächst wollen wir aber noch die zu Beginn dieses Kapitels gestellten Fragen beantwor- ten. Dazu definieren wir eine spezielle Ausgabeprozedur eval_list_of_results im Modul dice. Anders als die Unterprogramme, die wir bisher definiert haben, gibt es keinen Be- fehlreturnhErgebnisi. Vielmehr ruft eval_list_of_results die interne Funktion output auf, die ihrerseits mit print Ergebnisse ausgibt:

13Sie werden bei diesem Experiment so gut wie sicher etwas andere Ergebnisse bekommen als die hier dargestellten – ebenso wie ich selbst bei einer Wiederholung des Experimentes.

(19)

Listing 7: Zeilen 31–56 aus dice.py 31 def e v a l _ l i s t _ o f _ r e s u l t s ( h i s t _ l i s t , exp ) : 32 """

33 Given t h e o u t p u t h i s t _ l i s t from e x p e r i m e n t ( n ) 34 and t h e e x p e c t e d v a l u e exp ,

35 t h i s p r o c e d u r e p r i n t s how many outcomes from c o u n t _ s i x a r e

36 1 . e q u a l t o exp ,

37 2 . i n r a n g e e x p p l u s m i n u s 1 , 1 0 ,

38 3 . i n r a n g e e x p p l u s m i n u s 10%, 1%, 0 . 1 % . 39 """

40

41 def o u t p u t ( message , h i s t _ l i s t , low , h i g h ) :

42 sum = 0

43 f o r i in r a n g e ( low , h i g h + 1 ) :

44 i f i >= 0 and i < l e n ( h i s t _ l i s t ) :

45 sum = sum + h i s t _ l i s t [ i ]

46 p r i n t ( message , " : " , sum / 1 0 , "%") 47

48 o u t p u t ( " 1 . == exp " , h i s t _ l i s t , exp , exp )

49 o u t p u t ( " 2 . exp p l u s−minus 1 " , h i s t _ l i s t , exp−1 , exp +1) 50 o u t p u t ( " exp p l u s−minus 10 " , h i s t _ l i s t , exp−10 , exp +10) 51 o u t p u t ( " 3 . exp p l u s−minus 10% " , h i s t _ l i s t ,

52 round ( exp − exp∗0 . 1 ) , round ( exp + exp∗0 . 1 ) ) 53 o u t p u t ( " exp p l u s−minus 1% " , h i s t _ l i s t ,

54 round ( exp − exp∗0 . 0 1 ) , round ( exp + exp∗0 . 0 1 ) ) 55 o u t p u t ( " exp p l u s−minus 0. 1% " , h i s t _ l i s t ,

56 round ( exp − exp∗0 . 0 0 1 ) , round ( exp + exp∗0 . 0 0 1 ) )

Nun wollen wir sehen, was usere “Versuchsanordnung” so für Ergebnisse bringt:

>>> h2 = d i c e . e x p e r i m e n t ( 6 0 0 )

>>> h4 = d i c e . e x p e r i m e n t ( 6 0 0 0 0 )

>>> d i c e . e v a l _ l i s t _ o f _ r e s u l t s ( h2 , 1 0 0 ) 1 . == exp : 4 . 6 %

2 . exp p l u s−minus 1 : 1 2 . 9 % exp p l u s−minus 10 : 7 2 . 9 % 3 . exp p l u s−minus 10% : 7 2 . 9 %

exp p l u s−minus 1% : 1 2 . 9 % exp p l u s−minus 0.1% : 4 . 6 %

>>> d i c e . e v a l _ l i s t _ o f _ r e s u l t s ( h4 , 1 0 0 0 0 ) 1 . == exp : 0 . 4 %

2 . exp p l u s−minus 1 : 1 . 5 % exp p l u s−minus 10 : 8 . 8 %

(20)

3 . exp p l u s−minus 10% : 1 0 0 . 0 % exp p l u s−minus 1% : 7 1 . 9 % exp p l u s−minus 0.1% : 8 . 8 %

Anhand dieser (und weiterer) Experimente stellen wir die folgenden Hypothesen auf: Je häufiger wir würfeln,

1. um sokleiner ist die Wahrscheinlichkeit,genau das erwartete Ergebnis zu erzielen, 2. um so kleiner ist auch die Wahrscheinlichkeit, nur um einen absoluten Wert vom

erwarteten Ergebnis abzuweichen, und

3. um so größer ist die Wahrscheinlichkeit, nur um einen relativen Wert vom erwar- teten Ergebnis abzuweichen.

Die dritte Hypothese ist tatsächlich eine Konsequenz aus dem “Gesetz der großen Zahlen”.

Die ersten beiden Hypothesen widersprechen einer naiven Auslegung dieses Gesetzes.

Elemente von Python:

• DasPaket random stellt verschiedene Zufallsgeneratoren zur Verfügung.

Mit random.randrange(a,b+ 1) zieht man ganzzahlige Zufallswerte aus{a, . . . , b}.

• Eine Liste some_list enthält n+ 1 Elemente, die mit some_list[0], some_list[1], . . . , some_list[n] referenziert werden können. Diese Elemente verhalten sich analog zu Variablen: results [x] = results [x] + 1.

Um eine Liste der Längen+ 1 anzulegen, kann man allen+ 1Elemente der Liste auf einen konstanten Wert setzen, wie in results = [0] ∗ (n + 1).

• Die syntaktisch mit if dargestellte bedingte Anweisung ist eine wichtige Kontroll- struktur. Sie

– beginnt immer mit “if hBedingungi:” und einem Anweisungsblock,

– es können beliebig viele “elif hBedingungi:” mit Anweisungsblöcken folgen (“elif” steht für “else if”)

– und maximal ein “else:” mit Anweisungsblock:

>>> def t h r e e _ a n d _ f i v e ( x ) :

(21)

i f x % 15 == 0 :

p r i n t ( " d i v i d a b l e by 3 and by 5 " ) e l i f x % 5 == 0 :

p r i n t ( " d i v i d a b l e by 5 " ) e l i f x % 3 == 0 :

p r i n t ( " d i v i d a b l e by 3 " ) e l s e:

p r i n t ( " r e l a t i v e prime t o 1 5 " )

Diese Kontrollstuktur führt den Anweisungsblock aus, der auf die erste erfüllte Bedingung oder aufelsefolgt, falls keine Bedingung erfüllt ist:

>>> t h r e e _ a n d _ f i v e ( 3 3 4 3 ) r e l a t i v e prime t o 15

>>> t h r e e _ a n d _ f i v e ( 3 3 4 5 ) d i v i d a b l e by 3 and by 5

>>> t h r e e _ a n d _ f i v e ( 3 3 5 4 ) d i v i d a b l e by 3

>>> t h r e e _ a n d _ f i v e ( 3 3 3 5 ) d i v i d a b l e by 5

Aufgaben:

1. Wenn Sie mit einer fairen Münze 100-mal werfen, erwarten Sie 50-mal Null (“Kopf”) und 50-mal Eins (“Zahl”). Implementieren Sie dieses Zufallsexperiment in Python, analog zu dice .count_six.

2. Wiederholen sie das Zufallsexperiment 1000-mal und geben Sie das Ergebnis als Liste aus, analog zu dice .experiment.

5. Zeichnen mit Python

Es gibt verschiedene wissenschaftliche Pakete für Python, mit denen man Histogramme wie Abb. 1 erzeugen kann. Es ist aber lehrreicher und macht mehr Spaß, selber ein Paket zu entwickeln um Histogramme zu erzeugen.

Um mit Python einfach und verständlich zu “zeichnen”, verwenden wir das Paket turtle . Eine “Schildkröte” hat einen Stift, den sie hoch und herunternehmen kann. Die Schildkröte nimmt einfache Befehle entgegen (“gehe 100 Einheiten vorwärts, drehe dich um 90*1.5

(22)

Grad nach links, . . . ”). Ist der Stift unten, zeichnet die Schildkröte den Weg, den sie zurücklegt. Ist der Stift oben, ist der Weg unsichtbar.

Zur Demonstration zeichnen wir das “Haus vom Nikolaus”, siehe Abbildung 2.

Abbildung 2: Das Haus vom Nikolaus mit Turtle-Graphik gezeichnet. Bild 1 ist nach dem Zeichnen von drei Linien entstanden, Bild 2 nach sechs Linien, von denen die letzte leider zu lang ist, Bild 3 nachdem die falsche sechste Linie wieder entfernt wurde, und Bild 4 zeigt das fertige Haus (acht Linien). Die Pfeilspitze bezeichnet Position und Richtung der Turtle.

>>> import t u r t l e

>>> t u r t l e . p e n s i z e ( 3 )

>>> import math

>>> w2 = math . s q r t ( 2 )

>>> t u r t l e . f o r w a r d ( 1 0 0 )

>>> t u r t l e . l e f t ( 1 . 5∗9 0 )

>>> t u r t l e . f o r w a r d ( 1 0 0∗w2 )

>>> t u r t l e . r i g h t ( 1 . 5∗9 0 )

>>> t u r t l e . f o r w a r d ( 1 0 0 ) ## B i l d 1

>>> t u r t l e . r i g h t ( 9 0∗1 . 5 )

>>> t u r t l e . f o r w a r d ( 1 0 0∗w2 )

>>> t u r t l e . r i g h t ( 9 0∗1 . 5 )

>>> t u r t l e . f o r w a r d ( 1 0 0 )

>>> t u r t l e . r i g h t ( 9 0∗0 . 5 )

>>> t u r t l e . f o r w a r d ( 1 0 0∗w2 ) ## B i l d 2

>>> t u r t l e . undo ( ) ## B i l d 3

>>> t u r t l e . f o r w a r d ( 5 0∗w2 )

>>> t u r t l e . r i g h t ( 9 0 )

>>> t u r t l e . f o r w a r d ( 5 0∗w2 )

>>> t u r t l e . r i g h t ( 9 0∗0 . 5 )

>>> t u r t l e . f o r w a r d ( 1 0 0 ) ## B i l d 4

Bild 1 Bild 2

Bild 3 Bild 4

Noch ein zweites Beispiel: Wir definieren ein Unterprogram spiral , um eine Spirale zu zeichnen. Die Spirale besteht aus steps geraden Strichen, der erste ist start_len lang, jeder weitere ist um delta_len länger, und wird um den Winkel delta_angle nach links gedreht:

>>> def s p i r a l ( s t a r t _ l e n , d e l t a _ l e n , d e l t a _ a n g l e , s t e p s ) : l = s t a r t _ l e n

f o r i in r a n g e ( s t e p s ) : t u r t l e . f o r w a r d ( l ) l = l + d e l t a _ l e n

(23)

t u r t l e . l e f t ( d e l t a _ a n g l e )

Nun fangen wir an, Spiralen zu zeichnen. Die Ergebnisse sind in Abbildung 3 zu sehen.

Abbildung 3: Die drei Spiralen. In der Mitte die erste (Stichstärke 1, Farbe Schwarz), links die zweite (Stichstärke 3, Farbe Schwarz) und rechts die dritte (Strich- stärke 3, Farbe rot, Winkel 60 Grad).

>>> s p i r a l ( 1 0 , 2 0 , 9 0 , 2 0 )

Die Striche sind dünn. Also zeichnen wir links davon mit dickeren Strichen:

>>> t u r t l e . penup ( )

>>> t u r t l e . g o t o (−500 , 0 )

>>> t u r t l e . pendown ( )

>>> t u r t l e . p e n s i z e ( 3 )

>>> s p i r a l ( 1 0 , 2 0 , 9 0 , 2 0 )

Das Ergebnis ganz links ist die gleiche Spirale, nur eben mit der dreifachen Stichstärke.

Rechtwinklig und Schwarz ist etwas langweilig – wie wäre es, den Winkel zu verändern, und die Farbe? Das Ergebnis kommt ganz nach rechts.

>>> t u r t l e . penup ( )

>>> t u r t l e . g o t o ( 5 0 0 , 0 )

>>> t u r t l e . c o l o r ( " dark r e d " )

>>> t u r t l e . pendown ( )

>>> s p i r a l ( 1 0 , 1 0 , 6 0 , 2 0 )

Die Möglichkeiten, mit turtle zu spielen, sind fast unbeschränkt. Abbildung 4 zeigt das Ergebnis eines weiteren Experimentes mit spiral . Wir nutzen auch ein selbstdefiniertes Unterprogramm goto, das die lästigen und sich wiederholenden Sequenz

turtle .penup()−turtle.goto(...)−turtle .pendown() ersetzt.

(24)

Um unsere Spiralen zu beschriften, nutzen wir das Unterprogramm turtle . write:

>>> def g o t o ( x , y ) : t u r t l e . penup ( ) t u r t l e . g o t o ( x , y ) t u r t l e . pendown ( )

>>> t u r t l e . p e n s i z e ( 2 )

>>> f o r i in r a n g e ( 1 , 5 ) : i f i % 3 == 0 :

t u r t l e . c o l o r ( " r e d " ) e l i f i % 3 == 1 :

t u r t l e . c o l o r ( " g r e e n " ) e l s e:

t u r t l e . c o l o r ( " b l u e " ) g o t o ( 3 0∗i∗( i −1 ) , 0 )

s p i r a l (2+ i / 2 , 1+ i / 4 , 3 6 0 / ( i +2) , 10+5∗i ) g o t o ( 3 0∗i∗( i−1) , 1 0 0 )

t u r t l e . w r i t e ( " S p i r a l e " + s t r ( i ) , a l i g n =" c e n t e r " )

>>> g o t o ( 4 5 0 , 0 )

>>> t u r t l e . c o l o r ( " b l a c k " )

Abbildung 4: Vier weitere Spiralen – diesmal mit Beschriftung. Man beachte das schwarze Dreieck ganz rechts, das die aktuelle Position und Richtung anzeigt.

Lauf, Schildkröte lauf – einige Unterprogramme aus turtle:

• Stift und Zeichenverhalten: pensize, penup, pendown, color

(25)

• Richtung: left , right

• Turtle bewegen: forward, goto

• Befehl rückgängig machen: undo

• Text ausgeben: write

Abbildung 5: Bilder, die mit der Turtle-Grafik erzeugt wurden.

Regelmäßiges Sechs- und Achteck, siehe

Aufgabe 1.

Das Ergebnis von acht(!) Aufrufen des Polygon-Unterprogramms,

siehe Aufgabe 2.

Aufgaben:

1. Schreiben Sie ein Unterprogramm polygon(n,`,f), mit dem Sie für regelmäßigen- Ecke mit der Seitenlänge`und in der Farbef erzeugen können. Mit den folgenden Aufrufen können Sie z. B. das linke Bild von Abbildung 5 erzeugen:

>>> t u r t l e . p e n s i z e ( 3 )

>>> Polygon ( 6 , 1 0 0 , " r e d " )

>>> Polygon ( 8 , 1 0 0 , " b l u e " )

2. Der folgende Code ruft das Polygon-Unterprogramm achtmal auf. Wir würden ei- gentlich vier grüne und vier rote Polygone erwarten – tatsächlich sehen wir insge- samt fünf Polygone und einen geraden “Strich”. Erssetzen sie die beiden mit ???

markierten Stellen durch geeignete ganzzahlige Ausdrücke um das rechte Bild aus Abbilduung 5 zu erzeugen.

>>> t u r t l e . r e s e t ( )

>>> t u r t l e . p e n s i z e ( 3 )

>>> f o r i in r a n g e ( 4 ) :

Polygon ( ? ? ? , 1 0 0 , " g r e e n " )

(26)

Polygon ( ? ? ? , 1 0 0 , " r e d " )

6. Die Schildkröte zeichnet Histogramme

Nun haben wir alles was wir benötigen um Histogramme zu zeichnen. Histogramme bestehen im Wesentlichen aus einer Folge von Rechtecken. Einx∗y-Rechteck kann man zeichnen, indem manxEinheiten geht, dann links abbiegt,yEinheiten, links,xEinheiten, links, y Einheiten. Das Rechteck ist fertig und wir stehen wieder am Ausgangspunkt.

Damit die Turtle die gleiche Richtung wie zu Beginn hat, drehen wir uns noch einmal nach links.

Wenn wir mehrere Rechtecke nebeneinander zeichnen wollen, gehen wir die erste Strecke noch einmal (das schadet nichts) und ein kleines bisschen weiter, nämlich x Einheiten plus die Strichstärke. Sonst würde die linke Seite des nächsten Rechtecks die rechte Sei- te unseres gerade gezeichneten Rechtecks übermalen – was zumindest dann, wenn die Rechtecke unterschiedliche Farben haben, ärgerlich wäre.

>>> import t u r t l e

>>> def r e c t a n g l e ( len_x , len_y , p e n s i z e , c o l o r ) : t u r t l e . p e n s i z e ( p e n s i z e )

t u r t l e . p e n c o l o r ( c o l o r ) t u r t l e . f o r w a r d ( len_x ) t u r t l e . l e f t ( 9 0 )

t u r t l e . f o r w a r d ( len_y ) t u r t l e . l e f t ( 9 0 )

t u r t l e . f o r w a r d ( len_x ) t u r t l e . l e f t ( 9 0 )

t u r t l e . f o r w a r d ( len_y ) t u r t l e . l e f t ( 9 0 )

t u r t l e . f o r w a r d ( len_x+p e n s i z e )

Abbildung 6 zeigt, wie man das Unterprogramm nutzt.

Allerdings fehlt noch die Beschriftung. Ein Rechteck mit einer Beschriftung unten und oben bezeichnen wir als “pillar” (“Säule”). Wir schreiben das Modul histogram mit einem Unterprogramm histogram. pillar . Geeignete Positionen, für die Texte below und above haben wir durch Versuch und Irrtum gefunden. Das Ergebnis findet sich in Abbildung 7.

Listing 8: Zeilen 1–40 aus histogram.py

1 """ module h i s t o g r a m : Subprograms f o r h i s t o g r a m d r a w i n g . """

(27)

Abbildung 6: Einsatz von rectangle, um 02,12, . . .72 als Histogramm darzustellen.

>>> f o r i in r a n g e ( 8 ) : i f i % 2 == 0 :

c o l o r = " r e d "

e l s e:

c o l o r = " g r e e n "

r e c t a n g l e ( 1 5 , i∗i∗4 , 4 , c o l o r )

2

3 import t u r t l e 4

5 def p i l l a r ( below , above , x , y , p e n s i z e , c o l o r ) : 6 """

7 Draws r e c t a n g l e w i t h g i v e n p e n s i z e and c o l o r ; a b o v e and 8 b e l o w a r e t e x t s p o s i t i o n e d a c c o r d i n g l y . C u r r e n t p o s i t i o n i s 9 SW o f r e c t a n g l e , c u r r e n t + ( x , y ) i s NE, f i n a l p o s i t i o n i s 10 c u r r e n t + ( x + p e n s i z e , 0 ) , f i n a l d i r e c t i o n i s e a s t .

11

12 R e q u i r e s i n i t i a l d i r e c t i o n t o b e e a s t . 13 """

14

15 def w r i t e ( message , d i s t a n c e ) : 16 t u r t l e . r i g h t ( 9 0 )

17 t u r t l e . penup ( )

18 t u r t l e . f o r w a r d ( d i s t a n c e )

19 t u r t l e . w r i t e ( message , a l i g n =" c e n t e r " ) 20 t u r t l e . r i g h t ( 1 8 0 )

21 t u r t l e . f o r w a r d ( d i s t a n c e ) 22 t u r t l e . r i g h t ( 9 0 )

23 t u r t l e . pendown ( ) 24

25 t u r t l e . p e n s i z e ( p e n s i z e ) 26 t u r t l e . p e n c o l o r ( c o l o r ) 27 t u r t l e . f o r w a r d ( x / 2 )

28 w r i t e ( below , 1 6 ) # 16 c h o s e n a f t e r some e x p e r i m e n t s 29 t u r t l e . f o r w a r d ( x / 2 )

30 t u r t l e . l e f t ( 9 0 ) 31 t u r t l e . f o r w a r d ( y ) 32 t u r t l e . l e f t ( 9 0 )

33 t u r t l e . f o r w a r d ( x / 2 )

(28)

34 w r i t e ( above , 2 ) # 2 c h o s e n a f t e r some e x p e r i m e n t s 35 t u r t l e . f o r w a r d ( x / 2 )

36 t u r t l e . l e f t ( 9 0 ) 37 t u r t l e . f o r w a r d ( y ) 38 t u r t l e . l e f t ( 9 0 )

39 t u r t l e . f o r w a r d ( x + p e n s i z e ) # move t o t h e p o s i t i o n

40 # f o r t h e n e x t p i l l a r

Abbildung 7: Einsatz von histogram. pillar .

>>> import h i s t o g r a m , t u r t l e

>>> f o r i in r a n g e ( 8 ) : i f i % 2 == 0 :

c = " r e d "

e l s e:

c = " g r e e n "

h i s t o g r a m . p i l l a r ( i , i∗i , 1 5 , i∗i∗4 , 4 , c )

Zuguterletzt, hier ist die interaktive Sitzung, mit dem ich Abbildung 1 erzeugt habe:

>>> import h i s t o g r a m

>>> L i = [ 0 , 0 , 2 , 4 , 1 4 , 3 7 , 5 4 , 7 4 , 1 3 1 , 1 2 9 , 1 4 7 , 1 3 5 , 8 8 , 8 4 , 3 6 , 2 8 , 1 9 , 1 2 , 3 , 0 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ]

>>> f o r i in r a n g e ( 0 , 8 ) :

h i s t o g r a m . p i l l a r ( i , L i [ i ] , 2 0 , L i [ i ] , 3 , " g r e y " )

>>> f o r i in r a n g e ( 8 , 1 0 ) :

h i s t o g r a m . p i l l a r ( i , L i [ i ] , 2 0 , L i [ i ] , 3 , " b l u e " )

>>> f o r i in r a n g e ( 1 0 , 1 1 ) :

h i s t o g r a m . p i l l a r ( i , L i [ i ] , 2 0 , L i [ i ] , 3 , " r e d " )

>>> f o r i in r a n g e ( 1 1 , 1 3 ) :

h i s t o g r a m . p i l l a r ( i , L i [ i ] , 2 0 , L i [ i ] , 3 , " g r e e n " )

>>> f o r i in r a n g e ( 1 3 , 2 6 ) :

h i s t o g r a m . p i l l a r ( i , L i [ i ] , 2 0 , L i [ i ] , 3 , " g r e y " )

Aufgabe: Stellen Sie das Ergebnis Ihres Zufallsexperiments aus Aufgabe 2 aus Ab- schnitt 4 als buntes Histogramm dar!

(29)

7. Schneeflocken in Regenbogenfarben

Mit der turtle -Grafik kann man nicht nur nützliche Diagramme sondern auch schöne bunte Bilder, wie die Schneeflocke in Abbildung 8, zeichnen.

Abbildung 8: Eine bunte Koch-Schneeflocke.

Um von A nach B zu gehen, kann man entweder den direkten Weg oder einen Umweg nehmen. Zum Beispiel sucht man einem Punkt C, und geht zuerst von A nach C und dann von C nach B. Von A nach C kann man wiederum entweder den direkten Weg gehen, oder erneut einen Umweg. Ebenso für C nach B. D. h., das Programm “von hier nach dort” ruft sich rekursiv selbst auf. Um irgendwann tatsächlich am Punkt B anzukommen, muss die Rekursion irgendwann abbrechen.

Diese Idee setzen wir wie folgt um: Wenn die Blickrichtung von A zu B geht, und die Entfernung zwischen A und B `Schritte beträgt, definieren wir den Punkt C durch die folgende Anweisung:

• drehe Dich 45 Grad nach rechts

• gehe`√

2 Schritte vorwärts

Mit der folgenden Anweisung kommt man dann von C nach B

• drehe Dich um 90 Grad nach links

• gehe`√

2 Schritte vorwärts

Die ursprüngliche Blickrichtung erhält man, indem man zum Schluss die folgende An- weisung befolgt:

• drehe Dich 45 Grad nach rechts

Das folgende Programm aus dem Paket fractal setzt diese Anweisung in Python um. A

(30)

ist der Standort der Turtle, B ist der Punkt in Turtle-Richtung, dessen Entfernung length beträgt. Der Parameter step definiert die Rekursionstiefe. Null bedeutet einen Weg ohne Umwege, step>0bedeutet, zwei Wege mit step−1 von A nach C und C nach B.

Listing 9: Zeilen 40–53 aus fractal.py 40 # Levy−C−c u r v e

41

42 sqr t_2 = math . s q r t ( 2 ) # we ’ l l need t h i s c o n s t a n t 43

44 def l e v y ( s t e p , l e n g t h ) :

45 """ Draws a Levy−C−c u r v e . """

46 i f s t e p > 0 :

47 t u r t l e . r i g h t ( 4 5 )

48 l e v y ( s t e p−1 , l e n g t h / sq rt_ 2 ) 49 t u r t l e . l e f t ( 9 0 )

50 l e v y ( s t e p−1 , l e n g t h / sq rt_ 2 ) 51 t u r t l e . r i g h t ( 4 5 )

52 e l s e:

53 t u r t l e . f o r w a r d ( l e n g t h )

Nun können wir unsere Wege gehen – jeweils 50 Einheiten nach rechts, auf immer längeren Umwegen, wie in Abbildung 9 dargestellt.

>>> import t u r t l e , f r a c t a l

>>> t u r t l e . p e n s i z e ( 2 )

>>> t u r t l e . c o l o r ( " b l a c k " )

>>> f r a c t a l . l e v y ( 0 , 5 0 )

>>> f r a c t a l . t u r t l e _ i n v i s i b l e _ m o v e ( 5 0 )

>>> t u r t l e . c o l o r ( " r e d " )

>>> t u r t l e . c o l o r ( " b l u e " )

>>> f r a c t a l . l e v y ( 1 , 5 0 )

>>> t u r t l e . c o l o r ( " g r e e n " )

>>> f r a c t a l . t u r t l e _ i n v i s i b l e _ m o v e ( 5 0 )

>>> f r a c t a l . l e v y ( 2 , 5 0 )

>>> t u r t l e . c o l o r ( " r e d " )

>>> f r a c t a l . t u r t l e _ i n v i s i b l e _ m o v e ( 5 0 )

>>> f r a c t a l . l e v y ( 3 , 5 0 )

>>> f r a c t a l . t u r t l e _ i n v i s i b l e _ m o v e ( 5 0 )

>>> t u r t l e . c o l o r ( " b l a c k " )

>>> t u r t l e . p e n s i z e ( 1 )

>>> f r a c t a l . l e v y ( 4 , 5 0 )

>>> f r a c t a l . t u r t l e _ i n v i s i b l e _ m o v e ( 5 0 )

(31)

Abbildung 9: Lévy-C-Kurven vom Grad 0 (schwarz), 1 (blau), 2 (grün), 3 (rot) und 4 (schwarz, dünne Pinselstärke).

Das Ergebnis ist die sogenannte C-Kurve von Lévy. Für hinreichend große Werte von step erhält man eine Figur, die an ein verschnörkeltesC erinnert, siehe Abbildung 10.

Dabei wird unsere Turtle-Graphik allerdings etwas langsam. Es gibt Turtle-Befehle, um die Animation der Turtle zu beschleunigen. Für das Paket fractal definieren wir das Un- terprogramm turtle_fast und einige weitere Befehle. Unter anderem das Unterprogramm fractal .turtle_invisible_move, das wir beim Erstellen von Abbildung 9 bereits benutzt haben, um die Turtle unsichtbar vorwärts zu bewegen.

Listing 10: Zeilen 1–29 aus fractal.py 1 import t u r t l e , math

2

3 # c o n v e n i e n c e f u n c t i o n s ( don ’ t need t o c a l l t u r t l e . x x x d i r e c t l y ) 4

5 def t u r t l e _ f a s t ( ) :

6 """ S e t s t u r t l e movement t o h i g h s p e e d . """

7 t u r t l e . s p e e d ( 0 ) 8

9 def t u r t l e _ n o r m a l _ s p e e d ( ) :

10 """ S e t s t u r t l e movement t o normal s p e e d . """

11 t u r t l e . s p e e d ( 3 ) 12

13 def t u r t l e _ p e n s i z e ( s i z e ) :

14 """ S e t s p e n s i z e ( d e f a u l t 1 ) t o a new v a l u e . """

15 t u r t l e . p e n s i z e ( s i z e ) 16

17 def t u r t l e _ h i d e ( ) :

18 """ Makes t h e t u r t l e i n v i s i b l e . """

19 t u r t l e . h i d e t u r t l e ( ) 20

21 def t u r t l e _ s h o w ( ) :

22 """ Makes t h e t u r t l e v i s i b l e . """

23 t u r t l e . s h o w t u r t l e ( ) 24

25 def t u r t l e _ i n v i s i b l e _ m o v e ( l e n g t h ) :

26 """ Moves t u r t l e i n t o g i v e n d i r e c t i o n , w i t h o u t d r a w i n g . """

27 t u r t l e . penup ( )

(32)

28 t u r t l e . f o r w a r d ( l e n g t h ) 29 t u r t l e . pendown ( )

Bleibt noch die Frage, wie wir unsere Kurven bunt gestalten, mit fließenden Farbüber- gängen wie in den Abbildungen 8 und 10. Dafür definieren wir zunächst einige Farben als rot-grün-blau-Tripel:

Listing 11: Zeilen 32–37 aus fractal.py 32 # c o l o r−t r i p l e s t o b e u s e d f o r c o l o r e d c u r v e s 33

34 r e d = ( 0 . 9 9 9 , 0 . 0 0 1 , 0 . 0 0 1 ) 35 b l u e = ( 0 . 0 0 1 , 0 . 0 0 1 , 0 . 9 9 9 ) 36 g r e e n = ( 0 . 0 0 1 , 0 . 9 9 9 , 0 . 0 0 1 ) 37 y e l l o w = ( 0 . 9 9 9 , 0 . 9 9 9 , 0 . 0 0 1 )

Damit haben wir die Zutaten um eine Lévy’sche C-Kurve auch bunt zu zeichnen.

Listing 12: Zeilen 55–68 aus fractal.py 55 def l e v y _ c o l o r e d ( s t e p , l e n g t h , cols_0 , c o l s _ 1 ) :

56 """ Draws a Levy−C−c u r v e , c o l o r s c h a n g i n g c o l s _ 0 −> c o l s _ 1 . """

57 ( r1 , y1 , b1 ) = c o l s _ 0 58 ( r2 , y2 , b2 ) = c o l s _ 1

59 c o l s _ 5 0 = ( ( r 1+r 2 ) / 2 , ( y1+y2 ) / 2 , ( b1+b2 ) / 2 ) 60 i f s t e p > 0 :

61 t u r t l e . r i g h t ( 4 5 )

62 l e v y _ c o l o r e d ( s t e p−1 , l e n g t h / sqrt_2 , cols_0 , c o l s _ 5 0 ) 63 t u r t l e . l e f t ( 9 0 )

64 l e v y _ c o l o r e d ( s t e p−1 , l e n g t h / sqrt_2 , c o l s _ 5 0 , c o l s _ 1 ) 65 t u r t l e . r i g h t ( 4 5 )

66 e l s e:

67 t u r t l e . c o l o r ( c o l s _ 5 0 ) 68 t u r t l e . f o r w a r d ( l e n g t h )

Der folgende Aufruf liefert einen Farbverlauf von Rot nach Blau, siehe Abbildung 10:

>>> f r a c t a l . t u r t l e _ f a s t ( )

>>> f r a c t a l . t u r t l e _ p e n s i z e ( 2 )

>>> f r a c t a l . l e v y _ c o l o r e d ( 1 2 , 3 0 0 , f r a c t a l . red , f r a c t a l . b l u e )

Was hat das Ganze mit Schneeflocken zu tun? Die C-Kurve ist ein Fraktal14. Für die

14Siehehttps://de.wikipedia.org/wiki/Fraktal.

(33)

Abbildung 10: Ergebnis von fractal .levy_colored(12, 300, fractal .red, fractal .blue)

Schneeflocken brauchen wir ein anderes Fraktal, eine “Koch-Kurve”. Die Anweisungen, mit denen wir eine Koch-Kurve erzeugen, sind denen der C-Kurve sehr ähnlich:

• (Punkt A) gehe `/3Schritte vorwärts

• drehe Dich 60 Grad nach rechts

• gehe`/3 Schritte vorwärts (Punkt C)

• drehe Dich 120 Grad nach links

• gehe`/3 Schritte vorwärts

• drehe Dich 60 Grad nach rechts

• gehe`/3 Schritte vorwärts

Es ist nicht schwierig, das entsprechende Unterprogramm zu anzugeben. Das Ergebnis ist allerdings noch keine “Schneeflocke”, sondern nur eine verschnörkelte Kurve, die zwei Punkte miteinander verbindet. Siehe Abbildung 11.

Listing 13: Zeilen 71–86 aus fractal.py 71 # Koch−c u r v e and s n o w f l a k e

72 def koch ( s t e p , l e n g t h ) : 73 """

74 Draws a Koch−c u r v e o f g i v e n ’ l e n g t h ’ . 75 R e c u r s i o n d e p t h : ’ s t e p ’ .

76 """

77 i f s t e p < 1 :

78 t u r t l e . f o r w a r d ( l e n g t h )

(34)

79 e l s e:

80 koch ( s t e p−1 , l e n g t h / 3 ) 81 t u r t l e . r i g h t ( 6 0 )

82 koch ( s t e p−1 , l e n g t h / 3 ) 83 t u r t l e . l e f t ( 1 2 0 )

84 koch ( s t e p−1 , l e n g t h / 3 ) 85 t u r t l e . r i g h t ( 6 0 )

86 koch ( s t e p−1 , l e n g t h / 3 )

Abbildung 11: Ergebnis von fractal .koch(5, 200).

Eine Schneeflocke erhält man indem man mehrere Koch-Kurven, wie im folgenden Unter- programm, zusammenfügt. Das Ergebnis (mit 3 Koch-Kurven) ist endlich unsere Schnee- flocke, siehe Abbildung 12.

Listing 14: Zeilen 88–96 aus fractal.py 88 def k o c h _ s n o w f l a k e ( s t e p , l e n g t h , l i n e s ) :

89 """

90 C a l l s k o c h ’ l i n e s ’ t i m e s , t u r n s t o t h e l e f t by 91 3 6 0 / ’ l i n e s ’ d e g r e e s a f t e r e a c h c a l l .

92 """

93 a n g l e = 360/ l i n e s 94 f o r i in r a n g e ( l i n e s ) : 95 koch ( s t e p , l e n g t h ) 96 t u r t l e . l e f t ( a n g l e )

Abbildung 12: Ergebnis von fractal .koch_snowflake(5, 200, 3).

Noch ist die Schneeflocke nicht bunt. Die Unterprogramme, mit denen Abbildung 8 tat- sächlich erzeugt wurde, sind die folgenden:

(35)

Listing 15: Zeilen 101–137 aus fractal.py 101 def k o c h _ c o l o r ( s t e p , l e n g t h , cols_0 , c o l s _ 1 ) : 102 """

103 Draws Koch−c u r v e o f g i v e n ’ l e n g t h ’ w i t h r e c u r s i o n ’ s t e p ’ 104 s l o w l y c h a n g e s c o l o r from c o l s _ 1 t o c o l s _ 1

105 c o l s _ i s h a l l h o l d a t r i p l e o f v a l u e s b e t w e e n 0 and 1 . 106 """

107

108 ( r1 , y1 , b1 ) = c o l s _ 0 109 ( r2 , y2 , b2 ) = c o l s _ 1

110 c o l s _ 5 0 = ( ( r 1+r 2 ) / 2 , ( y1+y2 ) / 2 , ( b1+b2 ) / 2 ) 111 c o l s _ 2 5 = ( ( 2∗r 1+r 2 ) / 4 , ( 2∗y1+y2 ) / 4 , ( 2∗b1+b2 ) / 4 ) 112 c o l s _ 7 5 = ( ( r 1 +2∗r 2 ) / 4 , ( y1+2∗y2 ) / 4 , ( b1+2∗b2 ) / 4 ) 113 i f s t e p <= 1 :

114 t u r t l e . c o l o r ( c o l s _ 5 0 [ 0 ] , c o l s _ 5 0 [ 1 ] , c o l s _ 5 0 [ 2 ] ) 115 t u r t l e . f o r w a r d ( l e n g t h )

116 e l s e:

117 k o c h _ c o l o r ( s t e p−1 , l e n g t h / 3 , cols_0 , c o l s _ 2 5 ) 118 t u r t l e . r i g h t ( 6 0 )

119 k o c h _ c o l o r ( s t e p−1 , l e n g t h / 3 , c o l s _ 2 5 , c o l s _ 5 0 ) 120 t u r t l e . l e f t ( 1 2 0 )

121 k o c h _ c o l o r ( s t e p−1 , l e n g t h / 3 , c o l s _ 5 0 , c o l s _ 7 5 ) 122 t u r t l e . r i g h t ( 6 0 )

123 k o c h _ c o l o r ( s t e p−1 , l e n g t h / 3 , c o l s _ 7 5 , c o l s _ 1 ) 124

125 def k o c h _ s n o w f l a k e _ c o l o r ( s t e p , l e n g t h , l i n e s , c o l s ) : 126 """

127 C a l l s k o c h _ c o l o r l i n e s t i m e s , t u r n s t o t h e l e f t by 128 360/ l i n e s d e g r e e s a f t e r e a c h c a l l .

129 c o l s : l i s t o f c o l o r−t r i p l e s

130 c o l o r s c h a n g e from c o l s [ 0 ] t o . . . t o c o l s [ l i n e s −1]

131 and b a c k t o c o l s [ 0 ]

132 R e q u r i e s l e n g t h ( c o l s ) >= l i n e s . 133 """

134 a n g l e = 360/ l i n e s 135 f o r i in r a n g e ( l i n e s ) :

136 k o c h _ c o l o r ( s t e p , l e n g t h , c o l s [ i ] , c o l s [ ( i +1) % l i n e s ] ) 137 t u r t l e . l e f t ( a n g l e )

Aufgaben:

1. Experimentieren Sie mit der C-Kurve. Welche Figuren erhält man, wenn man die

(36)

C-Kurve, analog zur Koch’schen Schneeflocke, zu einem geschlossenen Linienzug zusammensetzt?

2. Experimentieren Sie mit der Koch-Kurve. Was erhält man wenn man nicht drei, sondern vier, fünf, . . . , Koch Kurven zu einer größeren “Schneeflocke” zusammen- setzt?

3. Implementieren sie die berühmte “Drachenkurve”15in Python. Experimentieren Sie damit.

8. Warum nicht immer Python?

Wenn Sie bis zu dieser Stelle gelesen und die Aufgaben bearbeitet haben, können Sie leidlich mit Python programmieren. Vielleicht halten Sie Python für eine so tolle Pro- grammiersprache, dass Sie alle Programmierprobleme damit lösen wollen.16Das ist keine gute Idee – erst recht nicht für die wissenschaftlich ausgebildete Informatikerin oder den wissenschaftlich ausgebildeten Informatiker, die oder der Sie einmal sein werden.

Python hat die wesentlichen Eigenschaften einerSkriptsprache. D.h., es ist ideal für die Entwicklung

• überschaubar kurzer Programmen (“Skripte”),

• die nicht über lange Zeit von verschiedenen Leuten gewartet werden müssen und

• an die keine hohe Ansprüche an Sicherheit oder Fehlertoleranz gestellt werden.

Zum Beispiel können Sie Variablen einfach benutzen.

In kompilierten (Nicht-Skript-)Sprachen wie Ada, C, C++, Java, Pascal, . . . müssen Sie typischerweise Variablendeklarieren, bevor Sie diese benutzen können. Sie geben denTyp der Variablenen. Dieser kann sich während der Lebenszeit der Variablen nicht ändern. In Python hat jedes Objekt einen Typ (z.B. String, ganze Zahl, Liste, . . . ), aber einer Varia- ble, die ein Objekt von einem Typ enthält, kann jederzeit ein Objekt von einem anderen Typ zugewiesen werden. Entsprechend müssen in Nicht-Skript-Sprachen auch Funktionen deklariert werden – eine Funktion, die laut Deklaration einen String als Ergebnis zurück- geben soll, kann keine Zahl zurückgeben, und umgekehrt. Verletzt ein Programm diese Vorgabe, liefert der Compiler eine Fehlermeldung statt eines ausführbaren Programms.

15Siehehttps://de.wikipedia.org/wiki/Fraktal.

16Es gibt aus gutem Grund ziemlich viele Python-Enthusiasten. Aber die meisten verstehen genug von Software-Engineering, um Python nicht überall einsetzen zu wollen.

(37)

Bei kurzen Programmen, und wenn Sie keine großen Ansprüche an die Sicherheit und Zuverlässigkeit Ihrer Programme stellen (kurz, wenn Sie ein “Skript” schreiben), sind Deklarationszwang und Typenbindung lästig. Wenn aber

• Ihre Programme länger und unübersichtlicher sind,

• über einen längeren Zeitraum genutzt, gepflegt bzw. weiterentwickelt werden sollen, ggf. von wechselnden Personen,

• oder der Schaden bei einem Fehlverhalten Ihres Programms beträchtlich wäre, sollten Sie keine Skriptsprache wie Python verwenden. Der Compiler ist, dank Dekla- rationszwang und Typenbindung (und weiterer Dinge, die wir hier nicht diskutieren) ein extrem nützliches Werkzeug um Fehler zu entdecken und zu beheben, ohne dass die Fehler jemals Bestandteil eines ausführbaren Programmes gewesen sind.

Unten sehen Sie “dummy.py”. Die Funktion dummy.text_to_num soll Buchstabenfolgen ähnlich einer Handy-Tastatur in Ziffernfolgen übersetzen.

Listing 16: Zeilen 1–28 aus dummy.py

1 """ t h i s i s a dummy−module w i t h a b r o k e n text_to_num f u n c t i o n """

2

3 def text_to_num ( t e x t ) : 4 def char_to_num ( c ) :

5 i f c >= " a " and c <= " c " : 6 return "2"

7 e l i f c <= " f " : 8 return "3"

9 e l i f c <= " i " : 10 return "4"

11 e l i f c <= " l " : 12 return "5"

13 e l i f c <= " o " : 14 return "6"

15 e l i f c <= " s " : 16 return "7"

17 e l i f c <= "v " :

18 return 8

19 e l i f c <= " z " : 20 return "9"

21 e l i f c == " " : 22 return "0"

23 e l s e:

(38)

24 return "?"

25 num = char_to_num ( t e x t [ 0 ] ) 26 f o r i in r a n g e ( 1 , l e n ( t e x t ) ) :

27 num = num + char_to_num ( t e x t [ i ] )

28 return num

Zeile 18 ist fehlerhaft. Deshalb liefert char_to_num manchmal eine Zahl statt, wie be- absichtigt, einen String zurück. Die Funktion text_to_num liefert ihrerseits

• oft das richtige Ergebnis, (“ada” und “programmieren”),

• manchmal ein falsches Ergebnis, (“tut”)

• und manchmal eine Fehlermeldung zurück (“gut”).17

>>> import dummy

>>> dummy . text_to_num ( " ada " )

’ 2 3 2 ’

>>> dummy . text_to_num ( " programmieren " )

’ 7 7 6 4 7 2 6 6 4 3 7 3 6 ’

>>> dummy . text_to_num ( " t u t " ) 24

>>> dummy . text_to_num ( " g ut " )

Traceback ( most r e c e n t c a l l l a s t ) :

F i l e "< p y s h e l l#150 >" , l i n e 1 , i n <module>

dummy . text_to_num ( " gut " )

F i l e " . . . / python_bsp /dummy . py " , l i n e 2 7 , in text_to_num num = num + char_to_num ( t e x t [ i ] )

TypeError : Can ’ t c o n v e r t ’ i n t ’ o b j e c t t o s t r i m p l i c i t l y

Bei einer kompilierten Sprache hätte der Compiler den Fehler sofort entdeckt und gemel- det. Das vorliegende Beispiel ist ein Trivialbeispiel – aber stellen Sie sich vor, Sie würden in einem Programm mit tausenden von Zeilen nach einem solchen Fehler suchen.

Ein weniger wichtiger Punkt, den man aber auch nicht unterschätzen sollte, ist, dass kompilierte Programme meistens deutlich schneller laufen als (interpretierte) Skripte.

17Die Fehlermeldung liegt daran, dass der Operator “+” entweder zwei Strings hintereinander hängen kann (hier beabsichtigt)oderzwei Zahlen addieren (falsch, siehe dummy.text_to_num("tut")),aber nicht, keinesfalls Zahlen und Strings miteinander verknüpfen kann.

(39)

A. Anhang: Quadratwurzeln Reloaded

Das Unterprogramm sqrt . sqrt funktioniert zwar, ist aber etwas langsam, denn die Schlei- fe wird etwa √

y-mal durchlaufen. Wenn y ≈ 2n eine Zahl der Länge n ist, dann ist

√y≈2n/2. D.h., die Laufzteit von sqrt . sqrt istexponentiell in der Eingabelänge n. Das geht viel effizienter:

1. `= 0≤√

y < y+ 1 =h.

2. Seien`eine untere Schranke undh eine obere Schranke für die Quadratwurzel von y. Genauer: Seien `≤√

y < h. Wiederhole:

a) Berechne m= (`+h)/2.

b) Vergleiche m∗m mit y.

i. Wenn m∗m=y, also m=√

y: Quadratwurzel gefunden!

ii. Wenn m∗m≤y, also m≤√

y < h: Neues Intervall m≤√ y < h.

iii. Wenn m∗m > y, also `≤√

y < m: Neues Intervall`≤√ y < m.

bis Quadratwurzel gefunden oder `=h−1(Intervall hat Länge 1).

In jedem jedem Berechnungsschritt halbieren wir das Intervall {` . . . , h−1}, in dem

√y liegt, etwa. Deshalb finden wir das gesuchte Ergebnis in etwalog2(y) Schritten. Für y≈2nist log2(y)≈n, d.h. die Laufzeit ist dann nur nochlinear in der Eingabelänge n, statt exponentiell. Deshalb schreiben wir das Unterprogramm fast_sqrt:

Listing 17: Zeilen 26–42 aus sqrt.py 26 def f a s t _ s q r t ( y ) :

27 """Same a s s q r t , b u t much f a s t e r . """

28

29 def f s q r t ( y , low , h i g h ) :

30 """ r e s u l t must b e i n r a n g e ( low , h i g h +1) """

31 mid = ( low + h i g h ) // 2

32 square_of_mid = mid ∗ mid

33 i f low +1 == h i g h :

34 return low

35 e l i f square_of_mid == y :

36 return mid # u s e s o b e r v a t i o n 2 . a 37 e l i f square_of_mid < y :

38 return f s q r t ( y , mid , h i g h ) # o b s e r v a t i o n 2 . b

Referenzen

ÄHNLICHE DOKUMENTE

Finden Sie mit Hilfe des Satzes in

a) n St¨ uhle stehen in einer Reihe. Wie viele M¨ oglichkeiten gibt es, die St¨ uhle so mit M¨ annern und Frauen zu besetzen, daß keine zwei Frauen nebeneinander sitzen?. b) n St¨

Geben Sie jeweils eine kurze Begr¨ undung f¨ ur

Abgabe: Die L¨ osungen m¨ ussen am Mittwoch den 26.04.2011 in der Vorlesung sp¨ atestens bis 08:15 Uhr

Abgabe: Die L¨ osungen m¨ ussen am Mittwoch den 04.05.2011 in der Vorlesung sp¨ atestens bis 08:15 Uhr

a) Berechnen Sie die Wahrscheinlichkeit daf¨ ur, in einem solchen Wurf einen Kniffel (d.h. alle W¨ urfel zeigen die gleiche Zahl) zu erzielen.. b) Berechnen Sie die

Erscheint die vom Spieler gew¨ ahlte Zahl einmal, zweimal oder dreimal, so bekommt er seinen Einsatz doppelt, dreifach oder vierfach zur¨ uck.. Wenn die vom Spieler gew¨ ahlte

Ein Versuch mit Ergebnisraum {0, 1} (0=Niete, 1=Treffer) und Trefferwahrschein- lichkeit p wird solange unabh¨ angig wiederholt, bis ein Erfolgs-Run der L¨ ange r beobachtet wird. Sei