Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Programmablauf

Wir wollen hier die grundlegenden Konzepte zu Programmabläufen vorstellen, die in nahezu jeder Programmiersprache vorkommen.
Um einen logischen Programmablauf zu realisieren, benötigen wir Fallunterscheidungen und Schleifen.
Ferner lernen wir, eigene Funktionen zu implementieren, um den Programmablauf besser zu strukturieren und häufig verwendete Codezeilen auszulagern.

Logische Operatoren

Logische Operatoren werden in if-Abfragen und Schleifen verwendet um den Programmablauf zu steuern:

OperatorBedeutung
a==ba und b sind gleich
a < ba ist kleiner als banalog >
a <= ba ist kleiner oder gleich banalog >=
a != ba ist ungleich b

Vergleichsoperationen geben immer ein Objekt vom Typ bool zurück, also entweder True oder False:

b = 1<2 ##de
print("Das Ergebnis von 1<2 ist vom Typ", type(b), "und besitzt den Wert", b)
Das Ergebnis von 1<2 ist vom Typ <class 'bool'> und besitzt den Wert True

Vergleichsoperatoren können allerdings nur dann verwendet werden, wenn diese Operationen auch für die zu vergleichenden Datentypen definiert ist. Folgende Vergleiche funktionieren:

# int-int ##de
print("1 ist kleiner 3:", 1<3)

# string-string (vergleiche alphabetische Reihenfolge)
print("'abc' ist kleiner 'bcd':", 'abc'<'bcd')

# int-float
print("2 ist größer oder gleich 2.0:", 2 >= 2.0)
1 ist kleiner 3: True
'abc' ist kleiner 'bcd': True
2 ist größer oder gleich 2.0: True
# int-int  #en
print("1 is less than 3:", 1<3)

# string-string (compare alphabetical order)
print("'abc' is less than 'bcd':", 'abc'<'bcd')

# int-float
print("2 is greater than or equal to 2.0:", 2 >= 2.0)
1 is less than 3: True
'abc' is less than 'bcd': True
2 is greater than or equal to 2.0: True

##de Bei komplexen Zahlen existiert keine Vergleichsoperation wie < oder >:

(1+3j)<(2-4j)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[4], line 1
----> 1 (1+3j)<(2-4j)

TypeError: '<' not supported between instances of 'complex' and 'complex'

Boolsche Ausdrücke können auch miteinander verknüpft werden. Die Äquivalente der mathematischen Ausdrücke aba\wedge b, aba\vee b und ¬a\neg a sind:

OperatorBedeutung
a and bergibt True, wenn a und b True sind
a or bergibt True wenn a oder b True sind
not aergibt True wenn a False ist

Beispiel: Wir wollen testen, ob eine Zahl xx im Intervall [1/4,3/4)[1/4,3/4) liegt:

import random

# Generiere Zufallszahl zwischen 0 und 1
x = random.random()

# Teste, ob x im Intervall [1/4 , 3/4) liegt:
in_interval = x >= 1/4 and x < 3/4

# Konsolenausgabe
print("x =", x)
print("x liegt in [1/4,3/4):", in_interval)
x = 0.33521690000138815
x liegt in [1/4,3/4): True

Fallunterscheidungen (if-Abfragen)

Fallunterscheidungen können in Python wie folgt realisiert werden:

if <condition_1>:
    # Wenn condition_1 True ist:
    [do something]
    ...
    [do something more]
elif <condition_2>:
    # Wenn condition_1 False und condition_2 True ist:
    [do something]
    ...
    [do something more]
else:
    # Wenn condition_1 und condition_2 False sind:
    [do something]
    ...
    [do something more]

Natürlich kann die Abfrage um beliebig viele elif-Blöcke erweitert werden. condition_1 und condition_2 müssen hier bool’sche Variablen, also entweder True oder False sein. Die Einrückung des unter der if-Anweisung stehenden Codes bestimmt was bei Gültigkeit von condition_1 alles getan wird. Der Codeblock, der jeweils ausgeführt wird endet bei der letzten eingerückten Zeile. Beachte dazu die Ausgabe folgendes Codes:

x = 0.8

if x < 0.5:
    print("Ich gehöre zur Fallunterscheidung")
    print("Ich auch")
    
if x < 0.5:
    print("Ich gehöre zur Fallunterscheidung")
print("Ich nicht") # Nur diese Zeile wird ausgeführt
Ich nicht
x = 0.2

if x < 0.5:
    print("Ich gehöre zur Fallunterscheidung")
    print("Ich auch")
    
if x < 0.5:
    print("Ich gehöre zur Fallunterscheidung")
print("Ich nicht")
Ich gehöre zur Fallunterscheidung
Ich auch
Ich gehöre zur Fallunterscheidung
Ich nicht

In folgendem Beispiel wird eine Zufallszahl zwischen 1 und 10 erzeugt und getestet, ob es sich um eine gerade oder ungerade Zahl handelt. Dazu lernen wir gleich auch den sogenannten Modulo-Operator % kennen, welcher den Divisionsrest bei Division zweier ganzer Zahlen zurückgibt. Beispiel: 8%3 ergibt 2, da 8=23+28=2\cdot 3+\textbf{2}. Dies wird 10 mal wiederholt. Eine Erläuterung zu for-Schleifen folgt im nächsten Abschnitt.

for k in range(10):
    x = random.randint(1,11)
    if x % 2 == 0:
        print(x, "ist eine gerade Zahl")
    else:
        print(x, "ist eine ungerade Zahl")
10 ist eine gerade Zahl
9 ist eine ungerade Zahl
1 ist eine ungerade Zahl
5 ist eine ungerade Zahl
11 ist eine ungerade Zahl
6 ist eine gerade Zahl
5 ist eine ungerade Zahl
1 ist eine ungerade Zahl
8 ist eine gerade Zahl
3 ist eine ungerade Zahl

Schleifen

for-Schleifen

Um die Notwendigkeit von Schleifen zu verstehen, betrachten wir zunächst folgenden Code, in dem die Summe von Zahlen aus einer Liste berechnet wird:

a = [1,4,7,4,2]
value = 0

value += a[0]
value += a[1]
value += a[2]
value += a[3]
value += a[4]

print("Die Summe der Zahlen", a, "ist", value)
Die Summe der Zahlen [1, 4, 7, 4, 2] ist 18

Wir wollen hier offensichtlich alle Elemente einer Liste durchgehen. Problematisch wird es dann, wenn die Liste mehrere Tausend Elemente beinhaltet. Unser Code würde dann auf mehrere Tausend Zeilen anwachsen. Zudem haben wir hier ausgenutzt, dass wir bereits die Anzahl der Elemente in der Liste kennen. Die Zeilen value += a[i] unterscheiden sich lediglich im Index i und mit Schleifen lassen sich all diese Zeilen zusammenfassen.

Die erste Art von Schleifen, die wir hier diskutieren möchten, ist die for-Schleife. Die allgemeine Syntax lautet:

for <elem> in <iterable_object>:
    [do something]
    ...
    [do something more]
  • <iterable_object> kann jede Art von Objekt sein, das einen Iterator bereitstellt (mehre dazu in Abschnitt Iteratoren)

  • Beispiele: Liste, ein Tupel oder Numpy-Array (siehe Abschnitt Lineare Algebra mit NumPy), String

Das obige Beispiel lässt sich mit einer for-Schleife vereinfachen:

value = 0
for elem in a:
    value += elem
print("Die Summe der Zahlen", a, "ist", value)
Die Summe der Zahlen [1, 4, 7, 4, 2] ist 18

Hier wird die Zeile value += elem so oft wiederholt, wie die Liste Elemente enthält. Die Variable elem nimmt nacheinander die Werte a[0], a[1], ..., a[4] an.

Neben den oben genannten iterierbaren Datentypen gibt es noch die Klasse range. Ein Range-Objekt repräsentiert ein Intervall für den Iterationsindex mit Start- und Endindex. Aus der Python-Dokumentation (zugänglich via range?) erfahren wir beispielsweise

range(stop) -> range object
range(start, stop[, step]) -> range object

Return an object that produces a sequence of integers from start (inclusive)
to stop (exclusive) by step.  range(i, j) produces i, i+1, i+2, ..., j-1.
start defaults to 0, and stop is omitted!

Hier ein kleiner Test:

# 5 Schleifendurchläufe
for i in range(5):
    print("Schleifendurchlauf", i)
Schleifendurchlauf 0
Schleifendurchlauf 1
Schleifendurchlauf 2
Schleifendurchlauf 3
Schleifendurchlauf 4
# 3 Schleifendurchläufe, beginnend mit Iterationsindex 2
for i in range(2,5):
    print("Schleifendurchlauf", i)
Schleifendurchlauf 2
Schleifendurchlauf 3
Schleifendurchlauf 4

Merke: Falls vor dem ersten Schleifendurchlauf bereits die Anzahl der Schleifendurchläufe feststeht, sollte eine for-Schleife verwendet werden. Andernfalls, eine while-Schleife, welche wir im folgenden Abschnitt diskutieren.

Scheifen können mit den Befehlen continue und break innerhalb des Schleifenblocks weiter beeinflusst werden. Mit break lässt sich ein weiteres Abbruchkriterium einbauen. Im Beispiel wird in einer Liste die Zahl 5 gesucht und beim ersten Auftauchen dieser Zahl wird die Schleife unterbrochen.

L = [1,4,5,7,9]
for x in L:
    if x == 5:
        print("Liste enthält", 5)
        break;
    else:
        print(x, "ist nicht 5")
1 ist nicht 5
4 ist nicht 5
Liste enthält 5

Im vorherigen Beispiel wird die Schleife nach dem Auftauchen der Zahl 5 mit break abgebrochen - sie wird also nach dem dritten Durchlauf nicht weiter ausgeführt.

Im Gegensatz dazu unterbricht continue nur den aktuellen Schleifendurchlauf und springt direkt zum nächsten Durchlauf.

Beispiel: Berechne die Summe aller geraden Zahlen von 1 bis 10:

val = 0
for x in range(1,11):
    if x%2 == 1: # falls x ungerade ist 
        continue
        
    val += x
print("2+4+...+10 =", val)
2+4+...+10 = 30

while-Schleifen

Bei for-Schleifen war das Abbruchkriterium bereits vor dem ersten Schleifendurchlauf bekannt.
Ändert sich das Abbruchkriterium während der Schleife, verwendet man while-Schleifen.
Die allgemeine Syntax lautet:

while <condition>:
    [do something]
    ...
    [do something more]
  • <condition> ist ein bool’scher Ausdruck.

  • Der eingerückte Schleifenblock wird so lange ausgeführt, wie <condition> True ist.

  • Damit keine Endlosschleife entsteht, muss sichergestellt werden, dass <condition> nach endlich vielen Durchläufen False wird.

In folgendem Beispiel werden so lange Zufallszahlen erzeugt, bis eine durch 5 teilbare Zahl erzeugt wurde:

number = 1 # Initialisiere mit 1 um die Schleifenbedingung zu erfüllen

while not number%5 == 0:
    number = random.randint(1,100)
    print("Zufallszahl:", number)
    
print(number, "ist unsere durch 5 teilbare Zufallszahl")
Zufallszahl: 54
Zufallszahl: 92
Zufallszahl: 98
Zufallszahl: 43
Zufallszahl: 39
Zufallszahl: 48
Zufallszahl: 71
Zufallszahl: 45
45 ist unsere durch 5 teilbare Zufallszahl

Auch bei while-Schleifen wirken die Befehle continue und break.

Funktionen

Eigene Funktionen definieren

Oft wiederverwendete Programmbestandteile können zur besseren Lesbarkeit des Programmcodes in Funktionen ausgelagert werden. Eine Funktion muss vor ihrem ersten Aufruf definiert werden.

Die allgemeine Syntax einer Funktionsdefinition lautet

def <function_name>(<param1>, <param2>[, ...]):
    [do something]
    ...
    [do something more]
    return <ret1>, <ret2>[, ...]

Die Parameter der Funktion <param1>, <param2> etc. werden beim Funktionsaufruf übergeben. Die Rückgabewerte <ret1>, <ret2> usw. werden von der Funktion zurückgegeben und können beispielsweise als Tupel gespeichert werden. Falls die Funktion keine Werte zurückgeben soll, beispielsweise bei einer Funktion welche nur etwas auf der Konsole ausgeben soll, kann die Zeile mit return auch weggelassen werden.

Beachte: Beim Aufruf von return wird die Funktion sofort verlassen. Eventuell nachfolgender Programmcode innerhalb der Funktion wird dann nicht mehr ausgeführt.

Die Syntax eines Funktionsaufrufs lautet

<ret1>, <ret2>[, ...] = <function_name>(<param1>, <param2>[, ...])

Wir beginnen mit einem einfachen Beispiel. Die folgende Funktion summiert alle Elemente eines iterierbaren Objekts L auf und gibt das Ergebnis zurück:

def sum(L):
    val = 0
    for elem in L:
        val += elem
    return val

Um diese Funktion nun auszuführen schreiben wir:

L = [1,4,7]
sum_L = sum(L)
print("Die Summe der Zahlen", L, "ist", sum_L)

L.pop()
L.append(9)
L.append(13)

sum_L = sum(L)
print("Die Summe der Zahlen", L, "ist", sum_L)
Die Summe der Zahlen [1, 4, 7] ist 12
Die Summe der Zahlen [1, 4, 9, 13] ist 27
#en
L = [1,4,7]
sum_L = sum(L)
print("The sum of the numbers", L, "is", sum_L)

L.pop()
L.append(9)
L.append(13)

sum_L = sum(L)
print("The sum of the numbers", L, "is", sum_L)
The sum of the numbers [1, 4, 7] is 12
The sum of the numbers [1, 4, 9, 13] is 27

Beachte, dass wir unsere Funktion sum mit Parametern eines beliebigen Typs aufrufen können, für den alle in sum verwendeten Operationen definiert sind.

Unsere Summenfunktion lässt sich daher auch mit Tupeln aufrufen:

sum((1,2,3))
6

Nicht jedoch mit Strings. Strings sind zwar ebenfalls iterierbar, was die Verwendung in einer for-Schleife erlaubt, aber die Operation += zwischen einem Integer (beachte: val ist aufgrund der Zeile val = 0 ein Integer) und einem String ist nicht definiert:

sum("Test")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[20], line 1
----> 1 sum("Test")

Cell In[16], line 4, in sum(L)
      2 val = 0
      3 for elem in L:
----> 4     val += elem
      5 return val

TypeError: unsupported operand type(s) for +=: 'int' and 'str'

Lokale und globale Variablen

Zu beachten ist, dass alle Variablen, die innerhalb einer Funktion definiert werden, lediglich lokale Variablen sind. Sie sind also außerhalb der Funktion nicht sichtbar.

Existiert bereits eine globale Variable mit demselben Namen, so wird innerhalb der Funktion nur mit der lokalen Variable gearbeitet:

value = 1                    # globale Variable namens 'value'

def get_successor(a):    
    value = a+1              # lokale Variable namens 'value'
    return value

print("Value ist", value)    # Ausgabe der globalen Variable 'value'
print("Der Nachfolger von 3 ist", get_successor(3))
print("Value ist", value)    # Ausgabe der globalen Variable 'value'
Value ist 1
Der Nachfolger von 3 ist 4
Value ist 1

Im letzten Beispiel wurde erst durch die Zeile value = ... die lokale Variable value angelegt.

Fehlt diese Zeile, beispielsweise in einer Funktion, die den Wert von value nur lesen, aber nicht verändern soll, dann wird innerhalb der Funktion die globale Variable mit demselben Namen verwendet:

value = 1

def print_global_variable():    
    print("Value is", value) # Ausgabe der globalen Variable
    
print_global_variable()
Value is 1

Möchte man eine globale Variable innerhalb einer Funktion verändern, muss man zu Beginn der Funktion klarstellen, dass keine neue lokale Variable angelegt werden soll. Dies geschieht mit dem Schlüsselwort global:

value = 1

def increment_value():
    global value
    value = value + 1 # Schreibt in die globale Variable 'value'
    
print("Value is", value)
increment_value()
print("Value is", value)
Value is 1
Value is 2

Diese Variante sollte man jedoch nach Möglichkeit vermeiden. Andere Funktionen verlassen sich möglicherweise darauf, dass sich der Wert der globalen Variablen nicht unerwartet ändert.

  1. Welche Ausgabe erzeugt dieses Programm?

  2. Erkläre, warum sich der Wert der globalen Variable value nicht verändert.

  3. Ändere die Funktion so, dass die globale Variable tatsächlich verändert wird.

Mutable vs. Immutable

Eine weitere interessante Frage ist: Was passiert mit Funktionsparametern, wenn wir sie innerhalb einer Funktion verändern?
Die Antwort liefert ein einfacher Test:

def add_something(a):
    a += 5

a = 2
print("a =", a)
add_something(a)
print("a =", a)
a = 2
a = 2

Obwohl wir a also in der Funktion verändern haben, ändert sich der Wert von a außerhalb der Funktion nicht.

Das liegt daran, dass beim Aufruf der Funktion add_something eine Kopie der Referenz auf den Wert von a übergeben wird. Da a ein immutabler Datentyp (Integer) ist, führt die Operation a += 5 dazu, dass ein neues Objekt erzeugt wird, auf das nur die lokale Variable a innerhalb der Funktion zeigt.

Jetzt wird es allerdings etwas verwirrend. Im folgenden Beispiel ist der Funktionsparameter eine Liste, die innerhalb der Funktion verändert wird. Wenn innerhalb der Funktion nur mit einer Kopie gearbeitet wird, sollte sich die Liste L nach dem Funktionsaufruf eigentlich nicht geändert haben. Dies ist jedoch offensichtlich nicht der Fall:

def append_something(L):
    L.append(5)

L = [1,2,3,4]
print("Liste vor Funktionsaufruf  :", L)
append_something(L)
print("Liste nach Funktionsaufruf :", L)
Liste vor Funktionsaufruf  : [1, 2, 3, 4]
Liste nach Funktionsaufruf : [1, 2, 3, 4, 5]

Offensichtlich wurde innerhalb von append_something nicht mit einer Kopie der Liste gearbeitet, sondern direkt mit dem Objekt L.

Um dieses Verhalten zu verstehen, hilft ein genauerer Blick auf die Funktionsweise von Variablen in Python. Variablen sind Bezeichner für Objekte im Speicher.

Mit der Funktion id, welche eine Identifikationsnummer eines Objekts zurückgibt, können wir uns anschauen, wo der Wert einer Variablen im Speicher abgelegt ist. Im folgenden Beispiel legen wir zwei Variablen an, die offensichtlich dieselbe ID besitzen:

a = 5
b = 5
print("a :", id(a))
print("b :", id(b))
print("a und b sind gleich :", a is b)

b = b+1
print("a :", id(a))
print("b :", id(b))
print("a und b sind gleich :", a is b)
a : 108490145835504
b : 108490145835504
a und b sind gleich : True
a : 108490145835504
b : 108490145835536
a und b sind gleich : False

Dies kann so interpretiert werden, dass a und b im Prinzip nur Namen sind, welche wir an ein Objekt binden (hier die Konstante 5, die irgendwo im Speicher liegt).

Am Anfang zeigen sowohl a als auch b auf dasselbe Objekt im Speicher. Deshalb liefern id(a) und id(b) die gleiche Identifikationsnummer und der Ausdruck a is b ergibt True.

Wird anschließend der Wert von b verändert (b = b + 1), dann passiert Folgendes:

  1. Es wird ein neues Objekt mit dem Wert 6 erzeugt.

  2. Der Name b wird an dieses neue Objekt gebunden.

  3. a bleibt weiterhin an das ursprüngliche Objekt 5 gebunden.

Das ursprüngliche Objekt im Speicher wird also nicht verändert, sondern es entsteht ein neues Objekt.

Implementieren wir einen ähnlichen Test für Listen-Objekte:

a = [1,2,3]
b = [1,2,3]

print("a :", id(a))
print("b :", id(b))

print("a und b sind gleich :", a is b)
a : 127782197529152
b : 127782197531200
a und b sind gleich : False

Hier sind a und b zwar Listen mit genau dem gleichen Inhalt, die Namen zeigen aber auf verschiedene Objekte im Speicher. Das erkennt man daran, dass id(a) und id(b) unterschiedliche Werte liefern und der Ausdruck a is b False ergibt.

Im Gegensatz zu den vorher betrachteten Integers wird hier also für jede Liste ein neues Objekt erzeugt, auch wenn der Inhalt identisch ist.

Wir verändern das Beispiel leicht:

a = [1,2,3]
b = a

print("a :", id(a))
print("b :", id(b))
print("a und b sind gleich :", a is b)

b.append(4)
print("a :", id(a))
print("b :", id(b))
print("a und b sind gleich :", a is b)
print("a =", a)
print("b =", b)
a : 127782197493376
b : 127782197493376
a und b sind gleich : True
a : 127782197493376
b : 127782197493376
a und b sind gleich : True
a = [1, 2, 3, 4]
b = [1, 2, 3, 4]

Durch die Zuweisung b=a haben wir b an das gleiche Objekt wie a gebunden. Und nun müssen wir aufpassen. Wir haben lediglich das Objekt hinter b durch b.append(4) verändert, da a und b aber an die gleichen Objekte gebunden sind hat diese Operation auch a verändert. Die Position des Objektes im Speicher hat sich dabei nicht verändert. Damit ist das Objekt selbst also veränderbar.

Wie unsere Beispiele gezeigt haben ist ein Objekt vom Typ int immutable, was bedeutet, dass dieses nicht verändert werden kann. Verändert man den Wert eines Integers, so wird ein neues Objekt erzeugt an den der ursprüngliche Name gebunden wird:

a = 1
print("a :", id(a))
a+= 1
print("a :", id(a))
a : 108490145835376
a : 108490145835408

Listen sind hingegen mutable (veränderlich) und verhalten sich anders:

L = [1,2]
print("L :", id(L))
L.append(3)
print("L :", id(L))
L : 127782197527360
L : 127782197527360

Dies erklärt auch das unterschiedliche Verhalten bei der Verwendung von Listen als Funktionsparameter. Das Verhalten bei immutable (veränderlichen) Datentypen erklärt sich leicht, wenn wir unser Beispiel von oben um einige Ausgaben ergänzen:

def add_something(a_):
    print("Funktionsbeginn    : a ->", id(a_), " Wert =", a_)
    a_+=1
    print("Funktionsende      : a ->", id(a_), " Wert =", a_)
    
a = 1
print("Vor Funkt.-Aufruf  : a ->", id(a), " Wert =", a)
add_something(a)
print("Nach Funkt.-Aufruf : a ->", id(a), " Wert =", a)
Vor Funkt.-Aufruf  : a -> 108490145835376  Wert = 1
Funktionsbeginn    : a -> 108490145835376  Wert = 1
Funktionsende      : a -> 108490145835408  Wert = 2
Nach Funkt.-Aufruf : a -> 108490145835376  Wert = 1

Der Name a ist zunächst an das Objekt mit der Konstanten 1 gebunden. Der Name wird an die Funktion add_something übergeben, heißt nun a_ (zur besseren Unterscheidung, wir hätten es auch weiterhin a nennen können), ist aber immer noch an das gleiche Objekt gebunden. Innerhalb der Funktion wird nun eine Operation auf dieses Objekt angewendet, hier +=. Dabei wird eine Kopie erstellt, da Integer immutable sind, und der Name a_ zeigt auf dieses neue Objekt, welches die Konstante 2 beinhaltet. Der Name a außerhalb der Funktion add_something ist allerdings weiter an das alte Objekt gebunden und bleibt dadurch unbeeinflusst von dem, was innerhalb der Funktion passiert.

Wäre a im vorherigen Beispiel eine Liste, also ein mutable Objekt, würde sich bei einer Operation, die die Liste verändert, die Bindung des Namens an das Objekt nicht ändern.

Fazit:

  • Immutable Objekte (int, float, string, tuple) → Änderungen erzeugen neue Objekte.

  • Mutable Objekte (list, dict, set, …) → Änderungen wirken sich direkt auf das übergebene Objekt aus.

Rekursive Funktionen

Rekursive Funktionen sind Funktionen, welche sich, bis zu einem bestimmten Abbruchkriterium, immer wieder selbst aufrufen. Ein typisches Beispiel wäre folgende Funktion zur Berechnung der Fakultät:

def faculty(n):
    if n==1: 
        # Abbruchbedingung der Rekursion
        return 1
    else:
        # Rekursiver Aufruf
        return n*faculty(n-1)
    
print("5! =", faculty(5))
5! = 120

Beachte, dass viele Algorithmen sowohl rekursiv, als auch iterativ (mit einer Schleife) realisierbar sind. Aus Effizienzgründen ist in diesem Fall immer die iterative Variante zu bevorzugen.