Lösungen

Lösungen

Aufgabe: Funktionen definieren

Die Maximumsfunktion mit drei Parametern können wir auf die mit zwei zurückführen.

def max(x,y):
  if x > y:
    return x
  else:
    return y

def max3(x,y,z):
  return max(x,max(y,z))

Beispielaufrufe:

>>> max3(1,2,3)
3
>>> max3(3,1,2)
3
>>> max3(3,4,3)
4

Bei der Implementierung der logischen Funktionen führen wir die Disjunktion mit Hilfe der Gesetze von de Morgan auf die beiden anderen Definitionen zurück.

def nicht(x):
  if x:
    return False
  else:
    return True

def und(x,y):
  if x:
    return y
  else:
    return False

def oder(x,y):
  return nicht(und(nicht(x),nicht(y)))

Die Summe der ersten n Zahlen berechnen wir mit einer Zählschleife.

def sum_up_to(n):
  sum = 0
  for i in range(n+1):
    sum = sum + i
  return sum

Beispielaufruf:

>>> sum_up_to(100)
5050

Analog dazu berechnen wir die Fakultät als Produkt der ersten n Zahlen und initialisieren dazu die Variable prod mit dem neutralen Element 1 der Multiplikation.

def factorial(n):
  prod = 1
  for i in range(1,n+1):
    prod = prod * i
  return prod

Beispielaufrufe:

>>> factorial(3)
6
>>> factorial(10)
3628800

Zur Wurzelberechnung mit dem Heron-Verfahren verbessern wir eine initial gewählte Näherung solange, bis sie nah genug an der Wurzel ist. Um die Lesbarkeit zu erhöhen, führen wir zwei Hilfsfunktionen ein. Eine zum Testen, ob die Nährerung gut genug ist und eine um eine Näherung zu verbessern.

def is_close_enough(x,sqrt,eps):
  diff = sqrt**2 - x
  return -eps < diff and diff < eps

def improve(x,sqrt):
  return (sqrt + x/sqrt)/2

Mit Hilfe dieser Funktionen können wir die Wurzeliteration nun mit einer einfachen bedingten Schleife programmieren.

def heronIter(x,eps):
  sqrt = 1.0
  while not is_close_enough(x,sqrt,eps):
    sqrt = improve(x,sqrt)
  return sqrt

Beispielaufrufe:

>>> heronIter(9,1)
3.023529411764706
>>> heronIter(9,0.1)
3.00009155413138
>>> heronIter(9,1e-10)
3.0

Aufgabe: Summe von Zahlenbereichen

Mit Zählschleife:

def sum_from_to(n,m):
  sum = 0
  for i in range(n,m+1):
    sum = sum + i
  return sum

Mit bedingter Schleife:

def sum_from_to(n,m):
  sum = 0
  i = n
  while i <= m:
    sum = sum + i
    i = i + 1
  return sum

Die Variante mit Zählschleife ist weniger fehleranfällig, weil die Manipulation der Zahlvariablen i automatisch geschieht und keine Gefahr besteht, dass die Schleife versehentlich nicht terminiert.

Bonusaufgabe: Binärdarstellung positiver Zahlen

def binary(n):
  bin = ""
  while n > 0:
    bin = str(n % 2) + bin
    n = n // 2
  return bin

Aufgabe: Eingaben verarbeiten

Die Eingabezahlen lesen wir mit der Funktion get_int(), die eine Eingabeaufforderung als Parameter erhält und als Ergebnis eine eingelesene Zahl liefert. Dabei wird so lange nach Eingaben gefragt, bis eine gültige Zahl eingegeben wird.

def get_int(aufforderung):
  valid = False
  while not valid:
    eingabe = input(aufforderung)
    if eingabe.isnumeric():
      zahl = int(eingabe)
      valid = True
    else:
      print("Ungültige Eingabe!")
  return(zahl)

Das folgende Programm liest zwei ganze Zahlen ein und vergleicht sie der Größe nach.

min = get_int("Gib eine ganze Zahl ein: ")
max = get_int("Gib noch eine ganze Zahl ein: ")
if min > max:
  num = min
  min = max
  max = num
if min == max:
  print("Die eingegebenen Zahlen sind gleich.")
else:
  print(str(max) + " ist größer als " + str(min) + ".")

Hier ist eine Beispiel-Interaktion mit diesem Programm.

Gib eine ganze Zahl ein: zwölf
Ungültige Eingabe!
Gib eine ganze Zahl ein: 12
Gib noch eine ganze Zahl ein: 8
12 ist größer als 8.

Bonusaufgabe: Stein, Schere, Papier

Das Programm zum Spielen von “Stein, Schere, Papier” liest eine Benutzereingabe und wählt dann entsprech um zu gewinnen.

s = input("Stein, Schere oder Papier? ")

while s == "Stein" or s == "Schere" or s == "Papier":
  if s == "Stein":
    print("Ich hatte Papier genommen. Gewonnen!")
  if s == "Schere":
    print("Ich hatte Stein genommen. Gewonnen!")
  if s == "Papier":
    print("Ich hatte Schere genommen. Gewonnen!")
  s = input("Stein, Schere oder Papier? ")

Das Programm terminiert, sobald etwas anderes als Stein, Schere oder Papier eingegeben wird.

Aufgabe: Gängige Fehler

Die Funktion

def is_small_prime(n):
  if n == 2 or 3 or 5 or 7:
    return True
  else:
    return False

liefert bei jedem Aufruf den Wert True zurück. Beabsichtigt ist hingegen, dass nur für die Eingaben 2, 3, 5 und 7 der Wert True und sonst der Wert False geliefert wird.

Der Fehler liegt in der Formulierung der Bedingung, die implizit wie folgt geklammert ist.

(n == 2) or 3 or 5 or 7

Der Wert dieses Ausdrucks ist True, falls n gleich 2 ist und sonst gleich 3. Beide Ergebnisse führen zur Ausführung des then-Zweiges der bedingten Verzweigung.

Beabsichtigt ist hier jedoch nicht die Oderverknüpfung des Vergleiches n == 2 mit den Zahlen drei, fünf und sieben sondern die Oderverknüpfung von Vergleichen der Variable n mit den vier kleinsten Primzahlen. Diese schreiben wir in python wie folgt.

n == 2 or n == 3 or n == 5 or n == 7

Nach einer entsprechen Korrektur liefert die Funktion is_small_prime() das beabsichtigte Ergebnis.

Die Funktion

def describe_text(s):
  if len(s) >= 10:
    print("10 Zeichen oder mehr")
    if len(s) > 20:
      print("Auch mehr als 20")
    else:
      print("Weniger als 10 Zeichen")

liefert für ein Argument mit einer Länge zwischen 10 und 20 Zeichen (z.B. Hallo Welt!) eine widersprüchliche Ausgabe.

10 Zeichen oder mehr
Weniger als 10 Zeichen

Dies ist vermutlich nicht beabsichtigt. Die Einrückung suggeriert, dass der else:-Zweig zur äußeren bedingten Anweisung gehören sollte und nicht zur inneren. Wir erreichen dies, indem wir die Einrückung von else: und den zugehörigen Block um eine Position ausrücken.

def describe_text(s):
  if len(s) >= 10:
    print("10 Zeichen oder mehr")
    if len(s) > 20:
      print("Auch mehr als 20")
  else:
    print("Weniger als 10 Zeichen")

Nach dieser Korrektur wird beim obigen Beispiel nur noch die erste Ausgabe erzeugt.

Aufgabe: Zahlenbereiche als Zeichenkette

Die Funktion nums_from_to() liefert eine Zeichenkette durch Leerzeichen getrennter Zahlen.

def nums_from_to(lower,upper):
  nums = ""
  for i in range(lower,upper):
    nums = nums + str(i) + " "
  if lower <= upper:
    nums = nums + str(upper)
  return nums

Die bedingte Anweisung sorgt dafür, dass eine leere Zeichenkette geliefert wird, wenn upper kleiner als lower ist.

Die folge Prozedur verwendet die definierte Funktion um Zahlen in einem abgefragten Bereich auszugeben.

def print_nums():
  fro = int(input("Erste Zahl: "))
  to = int(input("Letzte Zahl: "))
  print(nums_from_to(fro,to))

Wenn die obere Grenze kleiner ist als die untere, erzeugt die Prozedur als Ausgabe des Zahlenbereiches nur eine Leerzeile, weil die Funktion nums_from_to() in dem Fall die leere Zeichenkette zurückliefert und print() einen Zeilenumbruch erzeugt.

Hausaufgabe: Prozeduren definieren

Die Prozedur zum Zeichnen eines Dreiecks gegebener Größe verwet die Hilfsfunktion repeat(), die eine gegebene Zeichenkette wiederholt aneinander hängt.

def repeat(times,string):
  result = ""
  for i in range(0,times):
    result = result + string
  return result

Mit Hilfe dieser Funktion können wir Dreiecke zeichnen, indem wir zuerst den obersten Stern zeichnen, dann in einer Schleife jeweils zwei Sterne mit wachser Anzahl Leerzeichen schreiben und schließlich eine Zeile nur aus Sternen zeichnen. Ein Sonderfall sind Dreiecke der Größe eins, da sie nur aus dem ersten Stern bestehen.

def put_dreieck(n):
  print("*")
  if n > 1:
    for i in range(0, n-2):
      print("*" + repeat(i," ") + "*")
    print(repeat(n,"*"))

Bonusaufgabe: Prozedur definieren

Auch die Prozedur put_hailstone() verwet die Prozedur repeat(). Sie berechnet die Zahlen gemäß der angegebenen Vorschrift in einer bedingten Schleife bis eins erreicht wird und gibt den aktuellen Wert vor und nach jedem Durchlauf als Sterne aus.

def put_hailstone(n):
  while n > 1:
    print(repeat(n,"*"))
    if n % 2 == 0:
      n = n // 2
    else:
      n = 3*n + 1
  print(repeat(n,"*"))

Bonusaufgabe: Geschachtelte bedingte Anweisungen

def put23(n)
  for i in range(n):
    print (str(i) + " " + zwei3(i))

def zwei3(n)
  if n % 2 == 0 and n % 3 == 0:
    return "geht durch 2 und 3"
  else:
    if n % 2 == 0:
      return "geht durch 2"
    else:
      if n % 3 == 0:
        return "geht durch 3"
      else:
        return "geht weder durch 2 noch 3"

Aufgabe: Funktionen und Prozeduren beschreiben

Das gezeigte Programm definiert zwei Funktionen (is_divisible() und is_prime()) und eine Prozedur (print_prime_twins()). Letztere wird am Ende des Programms mit dem Argument 100 aufgerufen, wodurch alle Primzahlzwillinge aus Primzahlen kleiner als 100 ausgegeben werden.

Die Funktion is_divisible() hat zwei Parameter n und k und gibt einen Wahrheitswert zurück, der beschreibt, ob n durch k teilbar ist.

Die Funktion is_prime() hat einen Parameter n. Der Rückgabewert ist ein Wahrheitswert, der beschreibt, ob n eine Primzahl ist. Der Rumpf von is_prime() enthält eine bedingte Schleife, in deren Rumpf mit Hilfe eines Aufrufs der Funktion is_divisible() getestet wird, ob der Parameter n durch den Wert der Variablen k teilbar ist. Falls ja, wird durch eine optionale Anweisung die Ausführung des Funktionsrumpfes beendet und False als Ergebnis zurückgeliefert. Die Zählvariable k wird durch die Zuweisung k = 2 initialisiert und so lange erhöht, bis ihr Quadrat den Parameter n erreicht oder übersteigt. Auf diese Weise wird die Ausführung beim kleinsten gefundenen Teiler, der größer als eins ist, beendet. Falls die Ausführung des Funktionsrumpfes nicht innerhalb der Schleife abgebrochen wird, wird zurückgegeben, ob der Parameter n größer als eins ist. Es wird also genau dann True zurückgeliefert, wenn diese Zahl eine Primzahl ist.

Die Prozedur print_prime_twins() hat einen Parameter to und gibt Paare n,n+2 von Zahlen aus, die beide Primzahlen sind. Solche Paare heißen Primzahlzwillinge. Dazu werden im Rumpf der Prozedur in einer Zählschleife alle Paare n,n+2 bis zu n = to daraufhin getestet, ob beide Zahlen Primzahlen sind. Ist das der Fall, wird das Zahlenpaar mit Hilfe einer optionalen Anweisung ausgegeben.

Die Definition von Hilfsfunktionen is_divisible() und is_prime() erhöht die Lesbarkeit des Programms durch sprechende Namen. Im Fall von is_prime() ermöglicht die Definition des Primzahltests als Funktion außerdem, diesen im Rumpf der Zählschleife von print_prime_twins() mehrfach mit unterschiedlichen Argumenten aufzurufen, wodurch Code-Duplikation vermieden wird.

Die bedingte Schleife im Rumpf von is_prime() ist eigentlich eine Zählschleife, die bis zur Quadratwurzel des Parameters n läuft. Da die Abbruchbedingung mit Hilfe einer for-Schleife nicht so kurz beschrieben werden kann wie hier, kann man die Verwendung einer bedingten Schleife als gerechtfertigt betrachten, obwohl sie es erforderlich macht, die Zählvariable explizit zu initialisieren und zu erhöhen. Wegen der return-Anweisung im Schleifenrumpf kann dieser verlassen werden, bevor die Abbruchbedingung erreicht ist. Solch vorzeitige Beendigung der Ausführung einer Schleife (hier sogar des gesamten Funktionsrumpfes) erschwert das Verständnis des Programms, weil nicht einfach ersichtlich ist, unter welchen Umständen welche Programmteile erreichbar sind. Die Verwendung einer return-Anweisung im Schleifenrumpf ließe sich vermeiden, indem man das Ergebnis des Aufrufs von is_divisible() in einer Variablen speichert und deren Wert in der Schleifenbedingung abfragt, um diese zu beenden, wenn ein Teiler gefunden wurde. Auf diese Weise würde zwar die Abbruchbedingung komplexer, aber nicht die Ausführung der Schleife. Dadurch würde erreicht, dass man allein anhand der Abbruchbedingung erkennen kann, wann die Ausführung der Schleife beendet wird.