Manche Aussagen sind wirklich wahr...
... und andere schlicht falsch:
Dafür gibt es in Python und anderen Programmiersprachen einen eigenen Datentyp, die Boole'sche (nach G. Boole) oder logische Variablen genannt werden. Dieser Datentyp kennt nur zwei Werte: True
und False
.
Bool'sche Variable werden z.B. aus Vergleichsoperationen erzeugt:
# Vergleich von Zahlenwerten
7 > 2
7 < 2
# Vergleich von Zeichenketten
"Golm" == "Potsdam"
Zur Erinnnung: Der Test auf Gleichheit erfordert das doppelte Gleichheitszeichen ==
.
# Vergleich von Zahlen auf Ungleichheit
5 != 1
Die Ergebnisse derartiger Vergleiche kann man wiederum in logischen Variablen speichern, um später darauf zurückzugreifen:
temperatur = -5.
esfriert = (temperatur <= 0.)
print("Es friert:", esfriert)
print(type(esfriert))
Mit logischen Variablen lässt sich eine ganze Menge an 'logischer Arithmetik' betreiben:
istnass = True
# logisches UND: nur wahr, wenn beide Seiten wahr sind
istglatt = istnass and esfriert
istglatt
Das Resultat ist nur True, wenn beide Seiten auch True sind, sonst False.
zuschnell = False
#logisches ODER, auch wahr, wenn beide Seiten wahr sind
unfall = istglatt or zuschnell
unfall
Welche Wahrheitswerte haben folgende Ausdrücke? Versuche erst, sie im Kopf auszuwerten, dann die Ausdrücke in Python zu formulieren und das Ergebnis zu überprüfen.
Es sei A = WAHR, B = FALSCH.
1. A und (nicht(B))
2. nicht (nicht(A))
3. nicht(A) oder nicht(B)
- WAHR
- WAHR
- WAHR
A = True
B = False
#1.
A and not B
#2.
not(not A)
#3.
(not A) or (not B)
if
¶Logische Ausdrücke bzw. Variablen sind unverzichtbar für Fallunterscheidungen, wie in wenn-dann-Beziehungen:
In Python sieht das folgendermaßen aus:
regenwetter = True
if regenwetter:
print("Regenschirm einpacken!")
Nach dem if
steht also ein Ausdruck, der einer logischen Variable entspricht, also den Wert True
oder False
annimmt. Falls der Ausdruck True
ist, wird wird die folgende, eingerückte Zeile ausgeführt. Das Ganze kann auch deutlich komplizierter aufgebaut sein:
regenwetter = True
warmer_sommerregen = False
unterwegs_zur_regenschirmtauschboerse = False
if ((regenwetter and not warmer_sommerregen) or unterwegs_zur_regenschirmtauschboerse):
print("Regenschirm einpacken!")
Die Konstruktion kann außerdem um eine Aktion ergänzt werden, die im nicht-zutreffenden Fall ausgeführt wird:
regenwetter = False
if regenwetter:
print("Regenschirm einpacken!")
else:
print("Schirm bleibt zu Hause!")
Hier wird also immer nur die erste ODER die zweite Anweisung ausgeführt.
Derartige Konstruktionen kann man mit Aktivitätsdiagrammen darstellen, die etwa so aussehen können:
Diese können insbesondere bei komplexeren Fällen hilfreich sein.
Jetzt wollen wir bei gutem Wetter nicht nur den Regenschirm zu Hause lassen, sondern auch noch die Sonnencreme einpacken. Wie muss der Code oben erweitert werden?
regenwetter = True
if regenwetter:
print("Regenschirm einpacken!")
else:
print("Schirm bleibt zu Hause!")
print("Sonnencreme einpacken!")
Eine sorgfältige Formatierung des Quelltextes durch entsprechende Einrückungen ist in Python zwingend notwendig. Dies erleichtert auch die Lesbarkeit des Programmcodes erheblich. In den meisten Editoren gibt es einen Shortcut um ganze Code-Blöcke einzurücken, oder wieder zurück zu rücken. Hier ist es Sektion markieren + Tab
, bzw. Sektion markieren + Shift
+Tab
Wenn es nicht gerade "alternativlos" ist, gibt es im richtigen Leben manchmal ja auch mehr als zwei Alternativen. Wir könnten unsere Tagesaustattungb z.B. je nach Wetterlage zwischen Pudelmütze, Regenschirm und Sonnencreme wählen:
wetter = "sonnig" #hier "frostig", "regnerisch" oder "sonnig" wählen
if (wetter == "frostig"):
print("Pudelmütze eingepackt!")
elif (wetter == "regnerisch"): #elif heißt else if
print("Regenschirm eingepackt!")
elif (wetter == "sonnig"):
print("Sonnencreme eingepackt!")
else:
print("Nichts eingepackt.")
Die haben wir also drei Fälle abgedeckt und sogar an eine vierte Variante gedacht, falls keiner der drei Fälle zutrifft, also für alle anderen Werte von wetter
.
Manchmal möchte man eine Bedingung prüfen, die vom Vergleich mehrerer Werte abhängt.
So könnten wir uns z.B. fragen, ob es während einer einer Woche Frost gegeben hat, also die Zeitreihe der Minimaltemperaturdaten tmin = [2, 1, 0, -1, 3, 2, 4]
mindestens einmal unter 0 °C gefallen ist.
Wie könnte man diese Frage mit den bisher bekannten Mitteln beantworten?
tmin = [2, 1, 0, -1, 3, 2, 4]
frost = tmin[0]<0 or tmin[1]<0 or tmin[2]<0 or tmin[3]<0 or tmin[4]<0 or tmin[5]<0 or tmin[6]<0
frost
Diese Lösung ist allerdings sehr unelegant. Wir müssen viel tippen, und die Lösung nur, solange wir genau sieben Werte im Vektor tmin
haben. Ein achter Wert würde bei der Prüfung nicht berücksichtigt.
any
und all
¶Hier werden die logischen Aggregierungsfunktionen any
und all
nützlich. Diese prüfen, ob in einem Array/Vektor von logischen Variablen mindestens ein Eintrag (any
) bzw. alle Einträge (all
) True
sind: Um alle Einträge einer Liste auf einmal zu berücksichtigen, müssen die Listen vorher in Numpy-Arrays umgewandelt werden. Dies spart letztendlich sehr viel Arbeit.
all([True, False])
any([True, False])
import numpy
# Liste mit Minimumtemperaturen
tmin = [2, 1, 0, 1, 3, 2, 4, -1]
# numpy array
tmin = numpy.array(tmin)
type(tmin)
frosttage = tmin < 0
frost = any(frosttage)
nurfrost = all(frosttage)
print("Wenigstens EIN Frosttag:", frost)
print("NUR Frosttage:", nurfrost)
Mit dieser Umsetzung funktioniert der Code nun auch mit beliebig langen Datenreihen.
Wir haben mit der Variablen frosttage
einen Numpy-Array von Wahrheitswerten erzeugt, der für jeden Tag die Information enthält, ob es Frost gab:
tmin = [2, 1, 0, 1, 3, 2, 4, -1]
tmin = numpy.array(tmin)
frosttage = tmin < 0
frosttage
Man kann nun auch mehrere derartiger Vektoren benutzen, um mehrteilige Bedingungen zu prüfen. Dafür müssen wir die Listen in Numpy-Arrays umwandeln und folgende Operatoren benutzen &
(statt and
) und |
(statt or
):
A = numpy.array([True, False, True, False])
B = numpy.array([True, True, False, False])
# Elementweises UND
A & B
# Elementweises ODER
A | B
# Elementweises Negieren
~A
Achtung: &
, |
und ~
funktionieren nur bei numpy-Arrays
. Sonst heißt es and
, or
und not
!
not True
~numpy.array([True])
Nun haben wir neben den Temperaturdaten auch eine Niederschlagszeitreihe. Wir nehmen an, dass an Frosttagen mit Niederschlag Schnee gefallen sein könnte. Gab es Schneefall im betrachteten Zeitraum?
tmin = numpy.array( [2, 1, 0, 1, 3, 2, 4, -1])
niederschlag = numpy.array([0, 0, 3, 2, 0, 0, 0, 3])
tage_mit_frost = tmin < 0
tage_mit_niederschlag = niederschlag > 0
tage_mit_schnee = tage_mit_frost & tage_mit_niederschlag
any(tage_mit_schnee)
Wieviele Schneetage gab es? An welchen Tagen genau fiel der Schnee? Wie hoch ist die Summe des als Schnee gefallenen Niederschlags?
Da True
und False
in Python wie 1 und 0 behandelt werden, ist die Funktion sum()
hier hilfreich.
Um zu bestimmen, welche Elemente eines Vektors von Wahrheitswerten True
sind, verwende die Funktion numpy.where()
.
# Wieviele Schneetage gab es?
sum(tage_mit_schnee)
# An welchen Tagen hat es geschneit?
numpy.where(tage_mit_schnee)
# Niederschlagssumme der Schneetage
sum(niederschlag[tage_mit_schnee])
for
¶Die naive Lösung der Frost-Aufgabe ist ein Beispiel für die Wiederholung von (nahezu) identischen Kommandos. Neben zusätzlichem Schreibaufwand birgt derartiges Vorgehen immer die Gefahr zusätzlicher Fehler. Außerdem sind Anpassungen im Code mit viel Aufwand verbunden.
Dies spiegelt sich in der Programmiermaxime "Don't repeat yourself!" (DRY) wider. Vermeide Wiederholungen von Code! Use 'Style & Taste', not 'Copy & Paste'! Gegenteilige Lösungen werden auch als WET ("write everything twice" / "write every time" / "we enjoy typing" / "waste everyone's time") bezeichnet. Mögen diese anfangs schneller geschrieben und in Einzelfällen auch besser lesbar sein, überwiegen i.d.R. doch die Vorteile von DRY.
Gegeben sei ein Vektor von Tagen, an denen Schnee fällt tage_mit_schnee
. Es soll nun für jeden Tag ausgegeben werden, ob wir aufstehen müssen, um Schnee zu fegen... oder ausschlafen können. Ein WET-Lösung könnte so aussehen:
tage_mit_schnee = [False, False, True, False, True, True, True]
if tage_mit_schnee[0]:
print("Tag 1: Schneefegen!")
else:
print("Tag 1: Ausschlafen!")
if tage_mit_schnee[1]:
print("Tag 2: Schneefegen!")
else:
print("Tag 2: Ausschlafen!")
if tage_mit_schnee[2]:
print("Tag 3: Schneefegen!")
else:
print("Tag 3: Ausschlafen!")
if tage_mit_schnee[3]:
print("Tag 4: Schneefegen!")
else:
print("Tag 4: Ausschlafen!")
if tage_mit_schnee[4]:
print("Tag 5: Schneefegen!")
else:
print("Tag 5: Ausschlafen!")
if tage_mit_schnee[5]:
print("Tag 6: Schneefegen!")
else:
print("Tag 6: Ausschlafen!")
if tage_mit_schnee[6]:
print("Tag 7: Schneefegen!")
else:
print("Tag 7: Ausschlafen!")
Ziemlich viel redundanter Code - ein klassischer Fall für eine for
-Schleife:
tage_mit_schnee = [False, False, True, False, True, True, True]
# range erzeugt hier eine Sequenz von 0 bis AUSSCHLIEßLICH 7
for zaehler in range(0,7):
# aus dieser Sequenz wählt "for" nacheinander aus und weist den Wert "zahler" zu
if (tage_mit_schnee[zaehler]):
print("Tag " + str(zaehler + 1) + ": Schneefegen!")
else:
print("Tag " + str(zaehler + 1) + ": Ausschlafen!")
Fein! Sollten wir uns nun entscheiden, statt Schnee zu fegen, liegenzubleiben, müssen wir das lediglich an einer einzigen Stelle im Code ändern.
Die Zeitreihe der Schneetage sei nun von unbekannter, d.h. variabler Länge. Passe die Schleife so an, dass sie für beliebig lange Zeitreihen funktioniert!
# Mit diesem Modul lassen sich Zufallszahlen generieren
import random
# Hier wird k-mal aus den Optionen True oder False gewählt
tage_mit_schnee = random.choices([True,False], k=random.randint(1, 30))
In der for
-Schleife folgt nach dem Schlüsselwort in
eine Sequenz (ähnlich einem Vektor), dessen Elemente "iteriert" (abgearbeitet) werden, hier also range(0,7)
, also von 0 bis 6. Sollte unser Vektor tage_mit_schnee
nun eine andere Länge aufweisen, kann man diese ja mit len()
bestimmen (length)...
tage_mit_schnee = random.choices([True,False], k=random.randint(1, 30))
for zaehler in range(0,len(tage_mit_schnee)):
if tage_mit_schnee[zaehler]:
print("Tag " + str(zaehler + 1) + ": Schneefegen!")
else:
print("Tag " + str(zaehler + 1) + ": Ausschlafen!")
Wir bekommen einen neuen Mitbewohner in unserem Haus und sind nur noch an ungeraden Tagen verantwortlich. Passe die Schleife durch
a) Veränderung des Schleifenkopfs oder
b) durch Veränderung des Schleifenkörpers an!
Auch hier hilft es, den zu iterierenden Vektor im letzten Teil der for
-Konstruktion zu verändern. Der range()
-Befehl bietet da vielfältige Möglichkeiten...
tage_mit_schnee = random.choices([True,False], k=random.randint(1, 30))
for zaehler in range(0,len(tage_mit_schnee),2):
if tage_mit_schnee[zaehler]:
print("Tag " + str(zaehler + 1) + ": Schneefegen!")
else:
print("Tag " + str(zaehler + 1) + ": Ausschlafen!")
Ob eine Zahl gerade oder ungerade ist, kann man mit dem Modulo-Operator %
ermitteln. Dieser gibt den jeweiligen Rest bei ganzzahliger Division zurück. So ist 8 % 3
gleich 2, denn wenn man 8 durch 3 teilt, bleibt der Rest 2
for zaehler in range(0,len(tage_mit_schnee)):
if (zaehler % 2 == 0):
if tage_mit_schnee[zaehler]:
print("Tag " + str(zaehler + 1) + ": Schneefegen!")
else:
print("Tag " + str(zaehler + 1) + ": Ausschlafen!")
Die letzte Aufgabe soll nun noch darum erweitert werden, dass in der Ausgabe auch der Wochentag auftaucht. Tag 1 soll dabei immer der Montag sein.
Ähnlich wie bei b) hilft hier auch der Modulo-Operator %
. Mit seiner Hilfe lässt sich eine geeignete Liste ansteuern, welche die Namen der Wochentage enthält.
tage_mit_schnee = random.choices([True,False], k=random.randint(1, 30))
wochentage = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"]
for zaehler in range(0,len(tage_mit_schnee),2):
index_wochentag = zaehler % 7
if tage_mit_schnee[zaehler]:
print("Tag " + str(zaehler + 1) + ", " + wochentage[index_wochentag] + ": Schneefegen!")
else:
print("Tag " + str(zaehler + 1) + ", " + wochentage[index_wochentag] + ": Ausschlafen!")
while
¶In den vorherigen Beispielen war die notwendige Anzahl von Iterationen durch die Schleifen vorbestimmt, z.B. durch die Länge des betreffenden Vektors. Denkbar sind jedoch auch Situationen, wo dies nicht der Fall ist.
An einer Windkraftanlage befindet sich ein Windmesser, der die Windgeschwindigkeit v_wind
misst (wir simulieren diese Messung hier der Einfachheit halber mit dem Zufallsgenerator). Die Anlage soll laufen, solange die Windgeschwindigkeit unter 20 m/s liegt und sich bei einer höheren Geschwindigkeit abschalten.
v = 5 # Anfangswert der Windgeschwindigkeit
v_max = 20 # Abschaltgeschwindigkeit
while v < v_max:
print("Wind = %.1f m/s --> Anlage läuft." % v)
# Windmessung: hier zufällige Änderung der Windgeschwindigkeit
v = max(0, v + random.uniform(-v/2, (1.2*v_max-v)/2))
print("Wind = %.1f m/s --> ANLAGE GESTOPPT." % v)
while
macht also so lange weiter, bis das Kriterium (hier v < v_max
) nicht mehr erfüllt ist. Die Prüfung erfolgt immer am Anfang, also bevor der Block ausgeführt wird.
Achtung: Fehlerhafte while
-Schleifen können schnell zur "Endlosschleife" werden, wenn das Kriterium einfach immer erfüllt wird. Entsprechende Bugs (Fehler) können Programmabläufe erheblich stören.
Die Anlage soll nun nach maximal 10 Zyklen gestoppt werden, um gewartet werden zu können. Ist die Maximalgeschwindigkeit vorher erreicht, wird wie bisher auch angehalten. Der Wert des Zählerstands der Zyklen soll zusätzlich zur Windgeschwindigkeit ausgegeben werden.
v = 5 # Anfangswert der Windgeschwindigkeit
v_max = 20 # Abschaltgeschwindigkeit
zaehler = 0 # zählt die Schleifendurchläufe
zaehler_max = 10 # Maximale Anzahl an Zyklen
while (v < 20) and (zaehler < zaehler_max):
print("Zyklus %d: Wind = %.1f m/s --> Anlage läuft." % (zaehler, v))
v = max(0, v + random.uniform(-v/2, (1.5*v_max-v)/2))
zaehler = zaehler + 1
print("Zyklus %d: Wind = %.1f m/s --> ANLAGE GESTOPPT." % (zaehler, v))
continue
und break
¶Innerhalb von for
und while
-Schleifen lässt sich mit den Befehlen continue
und break
der Programmablauf noch flexibler gestalten: continue
springt dabei wieder an den Schleifenanfang, während break
die Schleife verlässt. Beide Befehle beziehen sich immer nur auf die innerste Schleife. Bei geschachtelten Schleifen werden also die äußeren Schleifen weiter ausgeführt.
# Beispiel: 3 überspringen und bei 5 abbrechen:
for i in range(8):
if i==3:
print("Nö.")
continue
if i==5:
print("Schluss jetzt!")
break
print(i)
Die Windkraftanlage können wir auch mit for
und unter Nutzung von break
steuern:
v_max = 20 # Abschaltgeschwindigkeit
zaehler = 0 # zählt die Schleifendurchläufe
zaehler_max = 10 # Maximale Anzahl an Zyklen
for i in range(zaehler_max):
v = max(0, v + random.uniform(-v/2, (1.4*v_max-v)/2))
if v < 20:
print("Zyklus %d: Wind = %.1f m/s --> Anlage läuft." % (i+1, v))
else:
print("Zyklus %d: Wind = %.1f m/s --> ANLAGE GESTOPPT." % (i+1, v))
break
Bei Geschwindigkeiten von 12 bis 20 soll die Anlage nun außerdem gedrosselt werden. Dies soll durch die Ausgabe ... --> Anlage gedrosselt
vermerkt werden.
v_max = 20 # Abschaltgeschwindigkeit
zaehler = 0 # zählt die Schleifendurchläufe
zaehler_max = 10 # Maximale Anzahl an Zyklen
for i in range(zaehler_max):
v = max(0, v + random.uniform(-v/2, (1.4*v_max-v)/2))
if v >= 20:
print("Zyklus %d: Wind = %.1f m/s --> ANLAGE GESTOPPT." % (i+1, v))
break
elif v > 12 and v < 20:
print("Zyklus %d: Wind = %.1f m/s --> ANLAGE GEDROSSELT." % (i+1, v))
else:
print("Zyklus %d: Wind = %.1f m/s --> Anlage läuft." % (i+1, v))
Fritzchen soll vier mal das Alphabet aufsagen. Er kennt es aber nur bis zum "G", und das "D" vergisst er auch jedes Mal. Das Alphabet holen wir uns wie folgt rein:
import string
Letters = list(string.ascii_lowercase) #voreingstelltes Alphabet
print(list(string.ascii_lowercase))
for zaehler in range(0,4):
print("%d. Mal:" % (zaehler+1))
for buchstabe in Letters:
if buchstabe == "d": #das "d" wird vergessen
continue #die Schleife geht zum nächsten Zähler
print(buchstabe)
if buchstabe == "g":
break # hier wird die Schleife beendet
Finde alle Primzahlen von 1 bis 100! Nutze dazu die Modulo-Funktion. Liefere am Ende einen Vektor von Wahrheitswerten ist_primzahl[1:100]
, in dem die Einträge für Primzahlen TRUE
sind.
Hinweis
Der Floor-Operator //
könnte hier hilfreich sein. Dieser gibt nur den ganzzahligen Teiler aus und ignoriert den Rest.