Sie sind auf Seite 1von 124

Konzepte des

skriptsprachenorientierten
Programmierens
Prof. Dr. Manuel Mayer

Teil 3
© FOM Hochschule für Oekonomie & Management
gemeinnützige Gesellschaft mbH (FOM), Leimkugelstraße 6, 45141 Essen

Dieses Werk ist urheberrechtlich geschützt und nur für den persönlichen Gebrauch im Rahmen der Veranstaltungen der FOM
bestimmt.
Die durch die Urheberschaft begründeten Rechte (u.a. Vervielfältigung, Verbreitung, Übersetzung, Nachdruck) bleiben dem
Urheber vorbehalten.
Das Werk oder Teile daraus dürfen nicht ohne schriftliche Genehmigung der FOM reproduziert oder unter Verwendung
elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden.

2
https://xkcd.com/889/

0b1010. Turtle Graphics 🤩


Turtle-Modul

v Wurde anfangs entwickelt, um Kindern das Programmieren näher zu bringen.

v Eine Schildkröte (Turtle) kann über den Bildschirm bewegt werden. Dabei hinterlässt
die Turtle auf ihrer Bewegungsbahn eine Linie. So können Grafiken erstellt werden.

v Dokumentation: https://docs.python.org/3/library/turtle.html

4
Challenges (7)

C13:
Zeichnen Sie mit den Turtle Graphics
a) ein Quadrat C14:
b) ein Dreieck Zeichnen Sie folgenden Stern:
c) einen Kreis

C15:
Knobelaufgabe: Wie kann folgendes Muster erzeugt werden?

5
https://xkcd.com/534/

0b1011. Funktionen
Funktionen

v Eine Funktion beinhaltet eine Reihe von Anweisungen, die einen Wert an
den Aufrufer zurückliefert.

v Zur Rückgabe wird das Schlüsselwort return verwendet.

v Wenn kein return angegeben wird, wird None zurückgeliefert.

v Funktionen können beim Aufruf Argumente übergeben werden.

7
Funktionsdefinitionen erzeugen ein Objekt

Variable Objekt

Funktionen können wie ein Objekt behandelt werden.


Wenn eine Programmiersprache Funktionen wie gewöhnliche Variablen
behandelt, spricht man von first class functions. 8
Funktionsparameter (Round 1): Default, Position, Keyword

9
Funktionsparameter (Round 1): Default, Position, Keyword

1. Default-Argumente stehen hinter Nicht-Default-Argumenten.

2. Keyword-Agrumente stehen hinter Positions-Argumenten.

3. Alle Keyword-Argumente, die beim Funktionsaufruf übergeben werden,


müssen einem Argument, das von der Funktion akzeptiert wird,
entsprechen. Die Reihenfolge ist dabei unwichtig.

4. Kein Argument darf beim Funktionsaufruf mehr als einen Wert


zugewiesen bekommen.

5. Default-Argumente sind optionale Argumente.

10
1. Default- hinter Nicht-Default-Argumenten

Gegenbeispiel:

def add(a=5,b,c):
^
SyntaxError: non-default argument follows default argument

11
2. Keyword- hinter Positions-Argumenten

Gegenbeispiel:

print (add(a=10,3,4))
^
SyntaxError: positional argument follows keyword argument

12
3. Keywords müssen bekannt sein; die Reihenfolge ist irrelevant

Gegenbeispiel:

print (add(a=10,b1=5,c=12))
TypeError: add() got an unexpected keyword argument 'b1'

13
4. Ein Wert pro Argument

Gegenbeispiel:

print (add(a=10,b=5,b=10,c=12))
^
SyntaxError: keyword argument repeated

14
5. Default-Argumente sind optional

15
Funktionsparameter (Round 2): Variable Parameteranzahl

16
1. Positionsargumente variabler Länge

17
2. Keyword-Argumente variabler Länge

18
Ein Beispiel zu args und kwargs

Wichtig ist hierbei, dass eine Referenz zu f beibehalten wird.

Die Funktion doubler gibt eine Funktion (g) zurück, die den Funktionswert
der übergebenen Funktion (f) verdoppelt.

19
Ein Beispiel zu args und kwargs (II)

Der Aufruf funktioniert. Unsere konkrete Funktion f1 erwartet 1 Parameter.


Wird nun die Funktion doubler mit dem Paramter f1 aufgerufen, erhalten wir
die zuvor beschriebene (jetzt konkretisierte) Funktion g.

Welchen Wert erhalten wir durch den Aufruf:

a) g(3)

b) g(-1)
20
Ein Beispiel zu args und kwargs (III)

Wie der try/except-Block vermuten lässt, schlägt dieser Aufruf fehl.

Warum?

Wie lässt sich das Problem lösen?

21
Ein Beispiel zu args und kwargs (IV)

22
Argumente entpacken

v Gerade haben wir gesehen, dass eine beliebige Anzahl an Argumenten


entgegengenommen werden kann und in Form eines iterierbaren
Objektes dem Funktionsrumpf zur Verfügung steht.

v Es ist jedoch auch praktisch, ein iterierbares Objekt an eine Funktion


zu übergeben, wobei die Elemente des Iterables als einzelne Parameter
angesehen (und somit bei der Übergabe an die Funktion „entpackt“)
werden.

v Dies wird in Python mit derselben Syntax beim Funktionsaufruf erreicht:


* bzw **.

23
Ein einfaches Beispiel (*)

Funktioniert auch mit einem Tupel:

24
Ein einfaches Beispiel (**)

Mit einem Dictionary funktioniert es auch, allerdings muss die kwargs-Syntax


(**) verwendet werden. Die Dictionary-Keys werden als Argumentname
verwendet:

25
Funktionsparameter (Round 3): Spezielle Parameter

26
Funktionsparameter (Round 3): Spezielle Parameter

v / bzw * sind spezielle Symbole und dienen der Definition von reinen
Positionsargumenten oder reinen Keyword-Argumenten.

v / separiert reine Positionsargumente vom Rest der Argumentliste.

v * separiert reine Keyword-Argumente vom Rest der Argumentliste.

v Sie sind optional. Fehlen sie, sind die Argumente Positions-oder-Keyword-


Argumente.

v Bei falscher Verwendung (Keyword für Nur-Position oder Position für Nur-
Keyword) wird ein TypeError erzeugt.

27
Beispiel reine Positionsarguemnte

28
Beispiel reine Keyword-Argumente

29
Beispiel Kombination beider

30
Anmerkungen

v Verwenden Sie rein positionsbezogene Argumente nur dann, wenn der Name
keine Bedeutung hat und Sie die Reihenfolge der übergebenen Argumente
erzwingen wollen.

v Verwenden Sie reine Keyword-Argumente nur dann, wenn der Name des
Arguments für ein besseres Verständnis sorgt und Sie verhindern möchten,
dass sich der Aufrufer lediglich auf die Position des Arguments beruft.

v Übertreiben Sie es nicht! KISS gilt auch hier.

31
Call by name/reference

v Python verwendet ein System, das auch als „call by object reference“ oder
„call by assignment“ bekannt ist.

v Werden Parameter wie ganze Zahlen, Strings oder Tupel übergeben,


entspricht dies einem „call by value“, da es sich um immutable Datentypen
handelt.

v Mutable Datentypen, wie z. B. Listen, können als „call by reference“


angesehen werden, da Änderungen innerhalb einer Funktion auch außerhalb
der Funktion Anwendung finden.

32
Denksport (V)

v Die Listen visited1 und visited 2 seien wie folgt initialisiert:

v Welches Ergebnis erhalten Sie nach den folgenden Modifikationen? Begründen Sie.

33
Challenges (8)

C16:
Schreiben Sie eine Funktion, die ihr Argument um 1 erhöht
zurückgibt. Es soll gelten: increment(42) = 43
Optional soll es auch möglich sein, um mehr als 1 zu erhöhen,
d.h. es soll zudem gelten: increment(11, 3) == 14

C17:
Schreiben Sie eine Funktion givemefive, die 5 Elemente aus
einer Liste ab einem bestimmten Index zurückgibt. Wird kein
Index angegeben, sollen die ersten 5 Elemente zurückgegeben
werden.

34
https://xkcd.com/2453/

0b1100. Lambdas und Closures


Lambda-Funktionen lambda arguments : expression

v Lambda-Funktionen (kurz: Lambdas) sind anonyme Funktionen.

v Eine Lambda-Funktion in Python kann eine beliebige Anzahl von Parametern


besitzen, jedoch nur 1 Ausdruck.

Beispiel:

36
Lambda-Funktionen lambda arguments : expression

v Lambda-Funktionen (kurz: Lambdas) sind anonyme Funktionen.

v Eine Lambda-Funktion in Python kann eine beliebige Anzahl von Parametern


besitzen, jedoch nur 1 Ausdruck.

Der Begriff Lambda geht das von Alonzo Church und Stephen Cole Kleene eingeführte Lambda-Kalkül

zurück, einer formalen Sprache zur Untersuchung von Funktionen. Eine Funktion 𝑥 ⟼ 𝑥 − 5 wird im

Lambda-Kalkül mit 𝜆𝑥. 𝑥 − 5 notiert. Man sagt, dass die freie Variable 𝑥 durch 𝜆-Abstraktion gebunden

wird. Da λ-Terme als Funktionen gesehen werden, kann man sie auf ein Argument anwenden: (𝜆𝑥. Φ)𝛼

berechnet sich durch Ersetzung von 𝑥 durch 𝛼 im Term Φ.


37
Erinnern Sie sich noch an das defaultdict-Beispiel?

Wie könnte man dies vereinfachen?

38
Closures

Bevor wir uns im Detail mit Closures beschäftigen, müssen wir zuerst klären, was
eine innere Funktion (nested function) und nicht-lokale (nonlocal) Variablen sind.

Eine innere Funktion haben wir bereits bei dem args/kwargs-Beispiel gesehen:

Doch was sind nun nicht-lokale Variablen?

39
Exkurs: Scope - Globale Variablen

VS.

40
Exkurs: Scope - Globale Variablen (II)

v Damit – wie gerade gesehen – keine weitere lokale Variable erstellt wird, kann
das Keyword global verwendet werden.

41
Exkurs: Scope - Globale Variablen (III)

42
Exkurs: Scope - Globale Variablen (IV)

VS.

43
Exkurs: Scope - Globale Variablen (V)

v Auch wenn das vorangegangene Beispiel eine offensichtliche Manipulation der globalen Variable

darstellt, heißt es aufpassen: In komplexeren Code-Strukturen kann es schnell zu unerwarteten

Ergebnissen führen.

v Zum Lesen globaler Variablen wird das Keyword global nicht benötigt, sondern erst dann, wenn

wir die Variable in einem lokalen Kontext verändern möchten.

v Es gibt wenige Gründe, global zu verwenden (Stichwort Seiteneffekte), aber es gibt sie. So

können bspw. Globals als Konstanten verwendet werden (Python hat keine echten Konstanten),

bei kontrollierter Verwendung eines „global states“.

v In Python bedeutet global eine Sichtbarkeit innerhalb eines Moduls.

44
Scope - Nicht-lokale Variablen (nonlocal)

v Globale Variablen sind auch hilfreich bei inneren Funktionen (nested functions).

v Python 3 führte das Keyword nonlocal ein und schafft somit einen
Gültigkeitsbereich zwischen lokal und global, speziell für Verschachtelungen.

45
Aber jetzt: Closure (de: Funktionsabschluss)

v Nachdem wir bereits wissen, dass eine Methode eine an ein Objekt gebundene
Funktion ist (2. Semester) und ein Lambda-Ausdruck (Funktionsliteral) eine
anonym definierte Funktion, ist eine Closure ein Muster der funktionalen
Programmierung und auf den Punkt gebracht:

Eine Closure ist ein Funktionsobjekt, das an eine Umgebung gebunden ist.

v „Eine Closure ist eine anonyme Funktion“ – falsch!! Dies ist nur eine
Eigenschaft von Lambdas.

46
Closure: Lambda-Ausdruck

v Nehmen wir an, dass es neben der gebundenen Variable eines Lambda-Ausdrucks
(auch Symbol genannt) weitere Symbole gibt:
𝑥
𝜆𝑥. + 2
𝑦

v 𝑥 ist durch die Lambda-Abstraktion gebunden. 𝑦 ist nicht gebunden und somit frei.

v Dies bringt uns zu 2 Kategorien von Lambda-Ausdrücken: geschlossen (alles


bekannt) und offen (= zumindest 1 Symbol ist nicht gebunden, somit frei und wir
benötigen Informationen „von außen“ → von der Umgebung).

v Eine Closure schließt (closes) somit einen offenen Lambda-Ausdruck – daher auch
der Name Closure.
47
Closures: Code-Beispiel

ohne
Klammern

48
Closures: Wann sollten sie genutzt werden?

v Innere Funktionen sind nur innerhalb der äußeren Funktion gültig. Mit Closures
wird eine Ausführung auch außerhalb ihres Gültigkeitsbereichs möglich.

v Da Closures oft als Callback-Funktionen genutzt werden, ermöglichen sie eine


Form von Data-Hiding*. Globale Variablen können so vermieden werden.

v Bei nur wenigen Funktionen in einem Programm, können Closures eine effiziente
Methode sein. Ansonsten sollte man eine Klassenstruktur vorziehen.

Der Compiler muss in der Lage sein, zu erkennen, dass der Wert (Zustand) der Variablen außerhalb deren
eigentlichen Gültigkeitsbereich (scope) benötigt wird, und dies bei der Kompilierung aktiv berücksichtigen.
Technisch werden diese Variablen dann meist nicht mehr auf dem Stack abgelegt, sondern dies wird anders
gelöst, z. B. indem tatsächlich im Hintergrund eine (anonyme) Klasse samt Instanz erzeugt wird, die die
benötigten (Member-)Variablen und die innere Funktion (als Memberfunktion) enthält.

* siehe Prinzipien der OOP 2. Semester 49


Denksport (VI)

Durch Verwendung des falschen Scopes können sich leicht Fehler einschleichen.
Führen Sie die folgenden Zellen aus. Machen Sie sich bewusst, wie deren
Ergebnisse zustande kommen und was eventuell schief läuft:

a) b)

c)

50
Challenges (9)

C18:

1) Erzeugen Sie (programmatisch) eine Liste von Funktionen, die die Vorschriften (konkret
Potenzierungen) 𝑥 ! , 𝑥 " , 𝑥 # , …, 𝑥 $% ausführen.
2) Erzeugen Sie eine zweite Liste, die die Strings ”x^2”, “x^4”... enthält.
3) Nutzen Sie die beiden Listen, um für ein gegebenes 𝑥 eine Bildschirmausgabe der Form
(hier für 𝑥 = 2)
x^2 = 4
x^4 = 16
...
x^18 = 262144

Hinweis: Beim Erzeugen der Funktionen in einer for-Schleife referenzieren u. U. alle


Funktionen dieselbe Loop-Variable und können daher unerwarteterweise identisch sein.
Hier muss man sich Gedanken um den Scope machen.
51
https://xkcd.com/754/

0b1110. Komplexität
53
Laufzeitanalyse
v 1. Ansatz: Direktes Messen der Laufzeit (z.B. in ms)

v Abhängig von vielen Parametern (Rechnerkonfiguration, -last, OS, ...)

v Daher kaum übertragbar und ungenau

v Aber (!): In der Praxis eine gängige Methode, um die Performanz grundsätzlich
sicherzustellen oder z. B. um Flaschenhälse zu identifizieren (lang laufende DB-Queries etc.)

v 2. Ansatz: Zählen der Elementaroperationen in Abhängigkeit von der Größe 𝑛 der Eingabe.

v Das algorithmische Verhalten wird als Funktion der Elementaroperationen dargestellt.

v Die Charakterisierung dieser elementaren Operationen ist abhängig von der jeweiligen
Problemstellung und dem zugrundeliegenden Algorithmus.

v Beispiele für Elementaroperationen: Zuweisungen, Vergleiche, arithmetische Operationen,


Arrayzugriffe, …
54
Problem: Hardware-Abhängigkeit

v Die Laufzeit einzelner Elementaroperationen ist abhängig von der eingesetzten Hardware:

v “Frühere Rechner” (inkl. klassischer Smartphones (aktuell im Wandel), PDAs, ...)

v “billig”: Fallunterscheidungen, Wiederholungen

v “mittel”: Rechnen mit ganzen Zahlen (Multiplikation etc.)

v “teuer”: Rechnen mit reellen Zahlen

v GPU

v “billig“: Rechnen mit reellen udn ganzen Zahlen

v “teuer”: (verhältnismäßig) Fallunterscheidungen


55
Die Eingabegröße n
v Das Maß für die Größe 𝑛 der Eingabe ist abhängig von der Problemstellung, z. B.:

v Suche eines Elementes in einem Array: 𝑛 = 𝐿ä𝑛𝑔𝑒 𝑑𝑒𝑠 𝐴𝑟𝑟𝑎𝑦𝑠

v Multiplikation zweier Matrizen: 𝑛 = 𝐷𝑖𝑚𝑒𝑛𝑠𝑖𝑜𝑛 𝑑𝑒𝑟 𝑀𝑎𝑡𝑟𝑖𝑧𝑒𝑛

v Sortierung einer Liste von Zahlen: 𝑛 = 𝐴𝑛𝑎ℎ𝑙 𝑑𝑒𝑟 𝑍𝑎ℎ𝑙𝑒𝑛

v Laufzeit

v Benötigte Elementaroperationen einer bestimmten Eingabelänge 𝑛

v Speicherplatz

v Benötigter Speicher bei einer bestimmten Länge 𝑛

56
Asymptotisches Laufzeitverhalten

v „Kleine“ Probleme (z. B. 𝑛 = 5) sind uninteressant:

Die Laufzeit des Programms ist eher bestimmt durch die Initialisierungskosten (OS,

Programmiersprache, etc.) als durch den Algorithmus selbst.

v Interessanter:

v Wie verhält sich der Algorithmus bei sehr großen Problemgrößen?

v Wie verändert sich die Laufzeit, wenn ich die Problemgröße variiere?

→ asymptotisches Laufzeitverhalten

57
Schranken

v Es ist sehr schwer, die genaue Verteilung der Daten zu finden, die dem realen Fall entspricht.

Deswegen betrachten wir in der Regel den schlimmsten Fall und finden auf diese Weise eine

obere Schranke für die Laufzeit.

v Wenn diese, als Funktion formulierte, obere Schranke korrekt ist, garantieren wir, dass für

beliebige Eingabedaten die Laufzeit unseres Algorithmus immer kleiner oder gleich dieser

Schranke ist.

v Selbstverständlich können auch weitere Schranken (z.B. die untere Schranke: mindestens) von

Interesse sein.

v Wir konzentrieren uns auf die obere Schranke.

58
Addition in der Schule

Eingabegröße:

𝑛 = 𝑍𝑎ℎ𝑙𝑒𝑛𝑏𝑟𝑒𝑖𝑡𝑒

Berechnungsschritt:

Addition von zwei Ziffern

Komplexitätsanalyse:

𝑇 𝑛 = 𝐴𝑛𝑧𝑎ℎ𝑙 𝑑𝑒𝑟 𝐵𝑒𝑟𝑒𝑐ℎ𝑛𝑢𝑛𝑔𝑠𝑠𝑐ℎ𝑟𝑖𝑡𝑡𝑒

𝑢𝑚 2 𝑍𝑎ℎ𝑙𝑒𝑛 𝑚𝑖𝑡 𝑛 𝑍𝑖𝑓𝑓𝑒𝑟𝑛 𝑧𝑢 𝑎𝑑𝑑𝑖𝑒𝑟𝑒𝑛

Im schlimmsten Fall:

𝑻 𝒏 = 𝟐𝒏 𝑇 𝑛 ist eine lineare Funktion

59
Multiplikation zweier Zahlen mit n (Schulmethode)

Eingabegröße:

𝑛 = 𝐴𝑛𝑧𝑎ℎ𝑙 𝑑𝑒𝑟 𝑍𝑖𝑓𝑓𝑒𝑟𝑛

Berechnungsschritt:

Multiplikation von zwei Ziffern

Komplexitätsanalyse:

𝑇 𝑛 = 𝐴𝑛𝑧𝑎ℎ𝑙 𝑑𝑒𝑟 𝐵𝑒𝑟𝑒𝑐ℎ𝑛𝑢𝑛𝑔𝑠𝑠𝑐ℎ𝑟𝑖𝑡𝑡𝑒

𝑢𝑚 2 𝑍𝑎ℎ𝑙𝑒𝑛 𝑚𝑖𝑡 𝑛 𝑍𝑖𝑓𝑓𝑒𝑟𝑛 𝑧𝑢 𝑚𝑢𝑙𝑡𝑖𝑝𝑙𝑖𝑧𝑖𝑒𝑟𝑒𝑛

60
Multiplikation zweier Zahlen mit n (Schulmethode) (II)

p=0 : ℕ Pseudocode
for j:=0 to n-1 do
p:=p
+ // n+1 Ziffernadditionen (optimiert)
a * b[j] // n Add. und n Mult. (siehe Backup-Folie)
* B^j // schieben (keine Ziffernarithmetik)

𝑛 " 𝑀𝑢𝑙𝑡𝑖𝑝𝑙𝑖𝑘𝑎𝑡𝑖𝑜𝑛𝑒𝑛
𝑛 " + 𝑛 − 1 𝑛 + 1 = 2𝑛 " + 1 𝐴𝑑𝑑𝑖𝑡𝑖𝑜𝑛𝑒𝑛

𝑻 𝒏 = 𝒏𝟐 + 𝒏𝟐 + 𝒏 + 𝟏 𝒏 − 𝟏 = 𝟑𝒏𝟐 − 𝟏 ≤ 𝟑𝒏𝟐 𝑇 𝑛 ist eine quadratische Funktion

61
Summe und Multiplikation in der Schule

Allgemein:

Ab einem bestimmten 𝑛! gilt


𝑐𝑛 < 𝑎𝑛"

𝑛! 62
Die O-Notation

v Mit der O-Notation haben Informatiker einen Weg gefunden, die asymptotische Komplexität

(bzgl. der Laufzeit oder des Speicherplatzbedarfs) eines Algorithmus zu charakterisieren.

Seien T(n): ℕ → ℕ und g: ℕ → ℕ zwei Funktionen. Die Funktion T(n) ist


von der Größenordnung 𝑂(𝑔), geschrieben T(n) 𝜖 𝑂(𝑔), wenn es 𝑐 ∈ ℕ
und 𝑛# ∈ ℕ gibt, so dass gilt:

Für alle n ∈ ℕ mit n ≥ 𝑛# ist T(𝑛) ≤ 𝑐 V 𝑔(𝑛).

v Einordnung:

𝑂(1) ⊆ 𝑂(log 𝑛) ⊆ 𝑂(𝑛) ⊆ 𝑂(𝑛 log 𝑛) ⊆ 𝑂(𝑛 " ) ⊆ ⋯ ⊆ 𝑂(2$ )

63
Rechnen mit der O-Notation

v Elimination von Konstanten:

$
2 ∗ 𝑛 ∈ 𝑂(𝑛) + 1 ∈ 𝑂(𝑛)
"

v Bei einer Summe zählt nur der am stärksten wachsende Summand

(mit dem höchsten Exponenten)

9𝑛 % + 2𝑛 & + 10𝑛 + 20 ∈ 𝑂 𝑛 %

Beachte: 1000 ∗ 𝑛 " ist nach diesem Leistungsmaß immer “besser” als 0,001 ∗ 𝑛 ' , auch wenn das 𝑛# ,

ab dem die 𝑂(𝑛 " )-Funktion unter der 𝑂(𝑛 ' )-Funktion verläuft, in diesem Fall sehr groß ist (𝑛# = 1

Million).
64
Rechnen mit der O-Notation – Noch 2 Beispiele

3𝑛_ + 𝑛` + 1000𝑛 + 500 ∈ 𝑂(𝑛_ )


Ignoriert
Proportionalitätskonstante Ignoriert Teile der Funktion
mit kleinerer Ordnung

5𝑛` + log ` 𝑛 ∈ 𝑂(𝑛` )

Ignoriert Teilaufgaben des


Algorithmus mit kleinem
Umfang

65
Anmerkungen zur O-Notation

v 𝑐 ist unabhängig von 𝑛: 𝑐 muss dieselbe Konstante sein, die für alle 𝑛 ∈ ℕ garantiert, dass

𝑇 𝑛 ≤ 𝑐 ∗ 𝑔(𝑛).

v Existiert keine solche Konstante 𝑐, ist 𝑇(𝑛) nicht von der Größenordnung 𝑂(𝑔).

v Man findet in der Literatur häufig T(n) = 𝑂(𝑔) statt 𝑇(𝑛) ∈ 𝑂(𝑔).

Aber Vorsicht: es gelten nicht die entsprechenden Gesetze, bspw. 𝑂 𝑔 = 𝑇(𝑛) gilt nicht!

v Man trifft bei der O-Notation üblicherweise weitere Annahmen. So wird zum Beispiel für das

Addieren von 2 Zahlen eine Laufzeit von 𝑂(1) verwendet und nicht 𝑂(𝑛). Dies wird

vernachlässigt, da man sich meist mit Zahlen mit fixer Größe (heutzutage 32 oder 64 Bit)

zufrieden gibt. Es spielt jedoch durchaus eine Rolle, wenn es beispielsweise um Mathematik-

Bibliotheken mit beliebig großen Zahlen geht (n = Anzahl Bits).


66
Wichtige Klassen von Funktionen

Sprechweise Typische Algorithmen / Operationen


𝑂(1) konstant Addition, Vergleichsoperationen, rekursiver Aufruf, ...
𝑂(log 𝑛) logarithmisch Suchen auf einer sortierten Menge
𝑂(𝑛) linear Bearbeiten jedes Elementes einer Menge
𝑂(𝑛 log 𝑛) - Gute Sortierverfahren
...
𝑂(𝑛" ) quadratisch Primitive Sortierverfahren

𝑂 𝑛# , 𝑘 ≥ 2 polynommiell
...
𝑂(2$ ) exponentiell Ausprobieren von Kombinationen

67
Visualisierung des Größenwachstums

68
Abschließende Bemerkungen

v Die O-Notation hilft insbesondere bei der Beurteilung, ob ein Algorithmus für großes n noch

geeignet ist bzw. erlaubt einen Effizienzvergleich zwischen verschiedenen Algorithmen für

große n.

v Schlechtere als polynomielle Laufzeit gilt als nicht effizient, kann aber für manche Probleme

das Bestmögliche sein,

v … d.h. für manche Probleme gibt es untere Schranken, d.h. kein Algorithmus kann schneller

laufen, als die untere Schranke angibt.

→ oft schwer zu bestimmen


→ O liefert die obere Schranke für ein Problem.
→ Ω: Untere Komplexitätsgrenze (“mindestens so schnell wie”)
→ θ: Genaue Komplexität („genau so schnell wie“)
69
Challenges (10)
C19:
Geben Sie die Zeitkomplexität nach der O-Nation für folgende Programme an:
a) 1) O(N) 2) O(N*log(N))
3) O(N*sqrt(N)) 4) O(N*N)

b) 1) O(N) 2) O(sqrt(N))
3) O(N/2) 4) O(log N)

C20:
Zwei Algorithmen, A und B, haben eine Worst-Case-Laufzeit von O(n) bei A und O(log n) bei
B. Die Aussage, dass Algorithmus B immer schneller ist als A ist a) wahr oder b) falsch.
70
https://xkcd.com/1739/

0b1111. Rekursion (vs. Iteration)


Beispiel: Die Funktion sum

v Die Funktion sum berechnen für ein gegebenes 𝑛 > 0, 𝑛 ∈ Ν die Summe aller Zahlen von 1 bis 𝑛.

Iterativ:
𝑇 𝑛 = 𝑐( + 𝑐" 𝑛, c" = Zeitkosten eines Schleifendurchgangs

𝑇 𝑛 = 𝑂(𝑛)

Rekursiv:
𝑇 𝑛 = 𝑐( + 𝑐" 𝑛, c" = Zeitkosten eines Funktionsaufrufs

𝑇 𝑛 = 𝑂(𝑛)

Direkt:
Formel von Gauß, 𝑇 𝑛 = 𝑂(1)

72
Beispiel: Die Funktion factorial

v Fakultät(0) = 1, Fakultät(n) = n * Fakultät(n-1)

Rekursiv:
Rechenzeit: 𝑇 𝑛 = 𝑂(𝑛)
Speicherplatz: 𝑇 𝑛 = 𝑂(𝑛)

Iterativ:
Rechenzeit: 𝑇 𝑛 = 𝑂(𝑛)
Speicherplatz: 𝑇 𝑛 = 𝑂(1)

73
Warum ist Rekursion oftmals ineffizient?
v Eine rekursive Funktion verursacht eine Kette von Funktionsaufrufen.

v Eine Funktion arbeitet in ihrer eigenen lokalen Umgebung.

v Werte aller lokaler Variablen und die Stelle, an der die Ausführung der Funktion sich

gerade befindet.

v Wenn innerhalb einer Funktion f(…) eine Funktion g(…) aufgerufen wird:

v Die gesamte lokale Umgebung von f wird gespeichert. Die Werte der Parameter von g

werden gesetzt. Das Programm springt zum Anfang der Funktion g und die Funktion g

wird entsprechend ausgeführt. Das Programm springt zurück zu f und das Ergebnis der

Funktion g wird an f übergeben. Die gesamte Umgebung von f wird zurückgesetzt und

die Ausführung der Funktion f wird fortgesetzt.


74
Rekursion: Kette von Funktionsaufrufen

Laufzeitkeller

75
Spezielle Rekursionsarten
v Lineare Rekursion

Rekursive Funktionen, die in jedem Zweig ihre Definition maximal einen rekursiven Aufruf

beinhalten, werden als linear rekursiv bezeichnet. Beispiel:

v Endrekursion (tail recursion)

Linear rekursive Funktionen werden als endrekursive Funktionen klassifiziert, wenn der

rekursive Aufruf in jedem Zweig der Definition die letzte Aktion zur Berechnung der

Funktion ist. D.h. keine weiteren Operationen müssen nach der Auswertung der Rekursion

berechnet werden.
76
Lineare Rekursion
v Eine nicht endrekursive Funktion ist folgende Definition der Fakultätsfunktion:

factorial 0 = 1

factorial n = n * factorial (n-1)

v Ablauf der Berechnung:


Der Ausführungsstapel wächst bei jedem
factorial 6 => 6 * factorial 5
=> 6 * (5 * factorial 4) rekursiven Aufruf und Teilausdrücke müssen ständig
=> 6 * (5 * (4 * factorial 3)) zwischengespeichert werden.
=> 6 * (5 * (4 * (3 * factorial 2)))
=> 6 * (5 * (4 * (3 * (2 * factorial 1))))
=> 6 * (5 * (4 * (3 * (2 * (1 * factorial 0)))))
=> 6 * (5 * (4 * (3 * (2 * (1 * 1)))))
=> 6 * (5 * (4 * (3 * (2 * 1))))
=> 6 * (5 * (4 * (3 * 2)))
=> 6 * (5 * (4 * 6)) Die Endberechnungen finden erst beim
=> 6 * (5 * 24)
=> 6 * 120
Abbau des Ausführungsstapels statt.
=> 720
77
Endrekursion

v Beispielhafte Implementierung einer endrekursiven Fakultätsfunktion:

78
Endrekursion – Ablauf der Berechnung

factorial 6 => factorial_helper(1, 6)


=> factorial_helper(6, 5)
=> factorial_helper(30, 4)
=> factorial_helper(120, 3)
=> factorial_helper(360, 2)
=> factorial_helper(720, 1)
=> factorial_helper(720, 0)
=> 720

v Es müssen keine Zwischenausdrücke gespeichert werden.

v Endrekursive Funktionen können sehr einfach zu einer iterativen Implementierung

abgewandelt werden.

79
Endrekursion – Mögliche Probleme

Endrekursive Funktionen werden in Python


nicht optimiert! 😞

“Tail Recursion Elimination (TRE) is


incompatible with nice stack traces... and
makes debugging hard” (Guido van Rossum)
→ siehe auch hier und hier

...
def recurse(counter): Depth 2977
print("Depth", counter) Depth 2978
counter += 1 ...
recurse(counter) RecursionError: maximum recursion depth exceeded while
calling a Python object

import sys 3000


print(sys.getrecursionlimit())
# sys.setrecursionlimit(12345)
80
Beispiel: Berechnung der Fibonacci-Zahlen

Rekursiv:

Die rekursive Berechnung der


Fibonacci-Zahlen hat eine
exponentielle Komplexität: 𝑂(2& )

81
Rekursive Berechnung der Fibonacci-Zahlen

82
Rekursive Berechnung der Fibonacci-Zahlen (II)

wiederholte
Berechnungen
83
Rekursive Berechnung der Fibonacci-Zahlen (III)

Mit unserer rekursiven Implementierung führt fib(40) zu:

fib(39) wird 1 x berechnet


fib(38) wird 2 x berechnet
Beim Aufruf von fib(40) werden
fib(37) wird 3 x berechnet
fib(36) wird 5 x berechnet 331.160.281 Funktionsaufrufe
fib(35) wird 8 x berechnet durchgeführt!
...
fib(0) wird 165.580.141 x berechnet
84
Endrekursive Berechnung der Fibonacci-Zahlen

Rechenzeit: 𝑇 𝑛 = 𝑂(𝑛)
Speicherplatz: 𝑇 𝑛 = 𝑂(𝑛)

fib_tail_rec(2978)
10368302567224130725402808720944745269169846358949206566546969004533913542980215297205069908032363496696295407802127339192049934635164785051
30546204118661607980410950286585809535664784349752677513026617225896806817079373241043331134208597785649274963993818359352645662049244104479
84598985541328622216290673559387996294654439808580297380157421264514600913663805613498253061272525605861214069246722624684271906736421352658
59269726057763349069496969887205666371709866621751717323402940230024941883749617398064589257637762440693437179535768949926413523261190831382
310521657050401255725846464639099512865670912780076725915476289

fib_tail_rec(2979)
...
RecursionError: maximum recursion depth exceeded while calling a Python object 😞
85
Iterative Berechnung der Fibonacci-Zahlen

Rechenzeit: 𝑇 𝑛 = 𝑂(𝑛)
Speicherplatz: 𝑇 𝑛 = 𝑂(1)

fib_iter(8000)
35615332044606267397689149054274603871413695391101540829735006389918858194987118153048292462239633737498734230832168897820342285216932671755
94214186111978816819236959743284321273097535654614718808050244321699002512466203835566030351092652496815708455980825654877181538741827129421
68912899187964953324613616899859004496573503581085677460538362837897929058053913579198506348499287793247348705406889947693739929519390552742
07929759029138360121990626870635375101517537581006264025917511839258831516176483750053134534932716812482330598584969517901132558974295395606
54496639601132039360167542277472498901884679404509894269174519328918160745655327632006736189766801968534195725815421784083495026969542066047
75888502969525726333071922395630904319565393034798349683080175557298241982188127556917992297341573601028956170069947702148863550978450916801
95896401902343500216738028568363657674462494249072730166890533880007856374449215234146023608600015301399336152153832209270847505282937794910
02813557093860863839463287251443115581618266959802005566973874793475256663122039030056061200186123236430592279484254766158650545069933528061
68014104657411510301453210159584182247476421388938511417454335213785668069468724409796809992418381568965277930293732972925367857964921588407
83344283380373274512207228105876801722558787954495247815549730971091741406326231676590274505504610450558838722256597968128470752864752082059
23875668405160707778568995306926178023176315799965539425437791083258303238592641010878264249883586034912756021070468742995902773902487497010
335873840408520900059054071283266816325489230566003110549946685475230821114509971542662742044237174282248020953398789607528748909125

86
tl;dr

v Jede rekursive Funktion kann als Iteration umgeschrieben werden.

v Jede iterative Funktion kann in eine rekurisve Funktion umgeschrieben werden.

v Wann soll in Python eine Iteration und wann eine Rekursion verwendet werden?

v Ist die Implementierung übersichtlicher durch eine Rekursion und sowohl die

Komplexität als auch die maximale Rekursionstiefe sind unter Kontrolle?

→ Rekursion

v Soll die Implementierung effizient sein?

→ Iteration

87
Dynamische Programmierung am Bsp. der Fibonacci-Zahlen

𝑇 𝑛 = 𝑂(𝑛)

Die Ergebnisse bereits berechneter Fibonacci-Zahlen


werden in einer “Tabelle” (Liste, der Index entspricht
der gesuchteb Fibonacci-Zahl) gespeichert. Dieser
Ansatz wird als dynamische Programmierung
bezeichnet.
Bei referenziell transparenten Funktionen (d.h. die
Funktion liefert bei jedem Aufruf mit gleichen
Argumenten dasselbe Ergebnis und produziert nichts
anderes außer dem Rückgabewert), kann das Ergebnis
für weitere Aufrufe gespeichert werden. Dies nennt
man Memoization.

88
Exkurs: Dekoratoren
Dekoratoren sind „syntaktischer Zucker“, um die Definition von Funktionen zu beinflussen:

@some_decorator
def some_function(arg1, arg2, ...):
statement1
statement2
D.h. nach der ersten
...
Definition von some_function
wird some_decorator
Dies ist äquivalent zu:
ausgeführt und kann eine

def some_decorator(arg1, ...): beliebige neue Definition der


... Funktion zurückgeben.
def some_function(arg1, arg2, ...):
...

some_function = some_decorator(some_function)
89
Exkurs: Dekoratoren - Beispiel

Dekorator, der Funktionsargumente und Rückgabewerte ausgibt (z. B. zum Debugging):

*args ist hierbei das Tupel aller Argumente der Funktion func.

print_sum(3, 4)

Output:
-- print_sum called with arguments (3, 4)
7
-- print_sum returned 7

90
Exkurs: Dekoratoren - Metadaten

Dummerweise:
print_sum.__name__ print_sum.__doc__
Output: Output:
'new_func' None

Lösung: functools.wraps

91
Exkurs: Memoization mittels functools.lru_cache

Erinnerung: Die naive Fibonacci-Implementierung ist verhältnismäßig langsam:

# recursive
def fib(n):
if n <= 1:
return n
else:
return fib(n - 1) + fib(n - 2)

10 x ausgeführt und die Gesamtzeit gemessen (mit dem Modul timeit)


print(f"Naive Fibonacci: {str(timeit.Timer(partial(fib, 30)).timeit(10))}")
Output:
Naive Fibonacci: 2.3686264369999996

2,37 Sekunden sind ganz schön lange...


92
Exkurs: Memoization mittels functools.lru_cache (cont.)

Mit lru_cache werden Ergebnisse gespeichert und wiederverwendet. Da Python nicht wissen kann,

ob eine Funktion referenziell transparent ist, geben wir es mit dem Dekorator des Moduls explizit

an. maxsize bestimmt die maximale Größe der gespeicherten Ergebnisse (hier: unbegrenzt).

@lru_cache(maxsize=None)
def fib_memoized(n):
if n <= 1:
return n
else:
return fib_memoized(n - 1) + fib_memoized(n - 2)

print(f"Fibonacci with DP: {str(timeit.Timer(partial(fib_memoized, 30)).timeit(10))}")

Output:
Fibonacci with DP: 1.371900000002313e-05

93
Challenges (11)

C21:
Wir betrachten eine typische Dateistruktur der Form
{'documents':{‘diss.docx':3, 'skripte':{'matheskript.pdf':15, 'pythontutorial.pdf':22}}, 'selfie.jpg':100}

und somit ein Dictionary, dessen values entweder Zahlen oder wiederum Dictionaries sein können.
Schreiben Sie eine Funktion disk_usage, die die Summe aller Zahlen-Werte ermittelt. Gestalten Sie sie
so, dass sie für ein beliebig tief verschachteltes Dictionary verwendbar ist.

C22:
In der Veranstaltung haben wir das Modul timeit kurz kennengelernt, um damit die
Ausführungszeit zu messen. Eine einfachere, aber oft ausreichende Variante ist das Modul
time. Schreiben Sie mithilfe des Moduls time einen Dekorator, der bei Funktionsaufruf ausgibt,
wie lange das Ausführen einer Funktion dauert. Lesen Sie sich dazu die Modulbeschreibung
von time durch.

94
Challenges 11 – Pro-Tour
C23:
Schreiben Sie eine rekursive Funktion, die aus gegebenen 𝑥 und 𝑛 ∈ ℕ die Potenz 𝑥 $ berechnet.
Verwenden Sie dazu die Rekursion

$ (𝑥 $/" )" , 𝑛 𝑔𝑒𝑟𝑎𝑑𝑒


𝑥 =w
𝑥(𝑥 ($+()/" )" , 𝑛 𝑢𝑛𝑔𝑒𝑟𝑎𝑑𝑒
und 𝑥 $ = 1.
Hinweis: Die Funktion soll natürlich im Prinzip ohne Verwendung des **-Operators implementiert
werden. Für die Quadrate können Sie aber der Einfachheit halber (...)**2 verwenden.
b) Schreiben Sie nun eine iterative Variante davon. Dabei darf die simpelste Lösung (einfach 𝑛 mal mit 𝑥
multiplizieren) nicht benutzt werden.

C24:
Schreiben Sie einen Dekorator für die Funktion aus Aufgabe C23, der die Rekursionstiefe zählt und bei
jedem Aufruf ausgibt. Hinweis: Jede Funktion, die dekoriert wird, braucht einen eigenen Rekursions-
Zähler. Eine Möglichkeit ist das Erzeugen einer solchen Variable im Scope des Decorators, so dass bei
jedem Verwenden des Decorators ein neuer Zähler erzeugt wird.
95
Challenges 11 – Pro-Tour (II)
C25:
Schreiben Sie eine Funktion, die die Primfaktoren einer Zahl zählt (mit Vielfachheit, d.h. 8 hat 3
Primfaktoren). Dabei kann man wie folgt vorgehen:
1. Testen Sie der Reihe nach alle Zahlen zwischen 2 und √𝑛, ob sie Teiler von 𝑛 sind.
2. Der kleinste Teiler 𝑘, den Sie finden, ist automatisch eine Primzahl. Die Anzahl der Primfaktoren
von 𝑛 ist dann um eins größer als die Anzahl der Primfaktoren von 𝑛/𝑘.
3. Wenn Sie keinen Teiler finden, dann ist 𝑛 selbst prim, d.h. das Resultat ist 1.
Bestimmen Sie die Summe der Anzahl der Primfaktoren für alle Zahlen zwischen 2 und 100000.
Verwenden Sie das Modul timeit, um die Berechnungsdauer der Summe zu bestimmen. Wie können
Sie diese Berechnung auf einfache Weise beschleunigen?

C26:
Schreiben Sie einen Dekorator, der einen Cache der bisherigen Funktionsaufrufe anlegt, bestehend
aus Argumenten und resultierenden Funktionswerten. Speichern Sie dieses in Form eines Dictionaries,
das bei jedem Funktionsaufruf auf dem Bildschirm ausgegeben wird.
Hinweis: Hier ist der Exkurs zum Thema Scope hilfreich. 96
https://xkcd.com/1171/

0b10000. Reguläre Ausdrücke


Formale Sprachen - Syntax

v Jede Sprache (natürliche und formale) hat Regeln.

v Für einen Text lässt sich feststellen, ob er zu einer Sprache gehört:

v Deutsch: Der Studierende sitzt in der Vorlesung.

v Python: print “Hey there“

v Eine Grammatik beschreibt, nach welchen Regeln eine Sprache syntaktisch


aufgebaut ist.

v Bei einer Programmiersprache prüft der Compiler/Interpreter die Syntax.

98
Formale Sprachen - Semantik

v Auch bei grammatikalisch korrektem Aufbau kann ein Text sinnlos sein:

v Deutsch: Die Vorlesung sitzt in dem Studierenden.

v Python: print “sin(x) = %f“ % cos(x)

v Eine Semantikkorrektur für Programmiersprachen gibt es (leider J) noch nicht.

99
Formale Grammatik

v Eine formale Grammatik ist ein 4-Tupel 𝐺 = (𝑉, Σ, 𝑃, 𝑆).

v 𝑉 ist eine endliche Menge: das Vokabular.


v Σ ⊂ 𝑉 ist das Alphabet, die Elemente heißen Terminalsymbole.
𝑁 = 𝑉\Σ sind die Nichtterminalsymbole oder auch Variablen.
v 𝑋 ∗ ist die Kleensche Hülle der Menge 𝑋. Sie enthält beliebige Konkatenationen von
Elementen aus der Menge.
v 𝑃 ⊂ 𝑉 ∗ \Σ ∗ ×𝑉 ∗ ist die Menge der Produktionsregeln.
Überführung eines Wortes/Texts R, das mindestens ein Nichtterminal enthält (𝑅 ∈
𝑉 ∗ \Σ ∗ ), in ein beliebiges Wort 𝑄 ∈ 𝑉 ∗ .
v 𝑆 ∈ (𝑉\Σ) ist das Startsymbol.

100
Chomsky-Hierarchie
Typ 0: Unbeschränkte Grammatik
v Enthält alle formalen Grammatiken
v Zugehörige Sprachen werden von einer Turingmaschine akzeptiert.
Typ 1: Kontextsensitive Grammatik
v Produktionsregeln der Form 𝛼𝐵𝛾 → 𝛼𝛽𝛾
(B Nichtterminal, griechische Buchstaben Worte aus 𝑉 ∗ )
v Sprache wird von linear beschränkter Turingmaschine erkannt.
Typ 2: Kontextfreie Grammatik
v Produktionsregeln der Form A → 𝛼
v Sprache wird von einem Kellerautomaten erkannt
v Programmiersprachen sind Typ 2.
Typ 3: Reguläre Grammatik
v Produktionsregeln der Form A → 𝛼 und A → 𝑎𝐵
v Sprache wird von linear beschränkter Turingmaschine erkannt.
101
Fragen zu allgemeinen Regelsprachen (Typ 0)
v Es ergeben sich sofort einige interessante Fragen, die zu Grammatiken gestellt werden können:
v Ist die Sprache zu einer Grammatik leer?
v Ist die Sprache zu einer Grammatik endlich?
v Gehört ein vorgegebenes Wort zu der von einer vorgegebenen Grammatik erzeugten
Sprache?
v Kann man ein Wort in ein anderes ableiten?
v Erzeugen zwei Grammatiken dieselbe Sprache?
v Leider sind alle diese Fragen nicht entscheidbar (Beweis: siehe Fachliteratur theoretische
Informatik). Unter nicht entscheidbar versteht man, dass es kein Verfahren gibt, das diese
Fragen für beliebige Worte und Grammatiken korrekt beantwortet. Entscheidbarkeit ist sehr
eng mit einem präzisen Algorithmusbegriff und mit dem Begriff der Berechenbarkeit gekoppelt.
Letztlich liegt die Ursache der Nichtentscheidbarkeit dieser Fragen darin, dass die Regelmenge
ohne jede Struktur sein darf. Dies ist für die Praxis nicht geeignet. Daher will man sich
einschränken und nur bestimmte Regeltypen zulassen (z.B. Reguläre Sprachen).
102
Reguläre Grammatiken (Typ 3) und reguläre Sprachen

v Wie gerade ausgeführt, bleiben zu viele Fragen offen, wenn man alle möglichen Regeln ohne
Einschränkung zulässt (allgemeine Regelsprachen). Wir lassen daher nur noch ganz wenige
Regeln zu und definieren:

Eine Grammatik 𝐺 = (𝑉, Σ, 𝑃, 𝑆) heißt regulär, wenn in allen Produktionen


jeweils genau ein Nichtterminal ersetzt werden kann durch
a) genau ein Nichtterminal oder genau ein Terminal oder
b) genau ein Nichtterminal verknüpft mit genau einem Terminal.

Eine Sprache 𝐿(𝐺) heißt regulär, falls es eine reguläre Grammatik 𝐺 gibt,
die diese Sprache erzeugt.

103
Beispiel: Reguläre Grammatik der Exponentialzahlen

v Einschränkende Annahmen:

v Beide Vorzeichen (Anfang + Exponent) sind zwingend anzugeben.

v Genaue eine Vor- und mindestens eine Nachkommastelle.

𝐺 = (𝑉, Σ, 𝑃, 𝑆)

𝑉 =Σ∪𝑁

𝑁 = 𝑆, 𝑀, 𝑁1, 𝑃, 𝑁2, 𝐸1, 𝐸2

Σ = −, +, 0,1,2,3,4,5,6,7,8,9, . , 𝑒

𝑧 ∈ {0 − 9}
104
z ist ein Platzhalter
Beispiel: Reguläre Grammatik der Exponentialzahlen (II)

v Produktionsregeln:

Regeln Beispiel für -3.81e-11


S -> -M S -> +M S -> -M
M -> zP -> -3P
P -> .N1 -> -3.N1
N1 -> zN2 -> -3.8N2
N2 -> zN2 N2 -> eE1 -> -3.81N2 -> -3.81eE1
E1 -> -E2 E1 -> +E2 -> -3.81e-E2
E2 -> zE2 E2 -> z -> -3.81e-1E2 -> -3.81e-11

105
Reguläre Sprache und Ausdrücke??

v Die Menge der regulären Sprachen über einem Alphabet Σ und die zugehorigen
regulären Ausdrücke (regular expressions, kurz: regex) sind rekursiv definiert:
v Die leere Sprache ∅ ist eine reg. Sprache und der zugehörige reg. Ausdruck ist ∅.
v Der leere String {∧} ist eine reg. Sprache und der zugehörige reg. Ausdruck ist ∧.
v Für jedes 𝑎 in Σ ist die einelementige Sprache {𝑎} eine reguläre Sprache und der
zugehörige reguläre Ausdruck ist 𝑎.
v Wenn 𝐴 und 𝐵 reg. Sprachen sind mit zug. reg. Ausdrücken 𝑟1 und 𝑟2, dann:
v ist 𝐴 ∪ 𝐵 (Vereinigung) eine reg. Sprache und der zug. reg. Ausdruck ist
𝑟1 𝑟2
v ist 𝐴𝐵 (Verknüpfung) eine reg. Sprache und der zug. reg. Ausdruck ist 𝑟1𝑟2
v ist 𝐴∗ (Kleene star) eine reg. Sprache und der zug. reg. Ausdruck ist (𝑟1∗ )

106
Beispiel: Regulärer Ausdruck für Exponentialzahlen
v Es gibt unendlich viele Sprache über jedem endlichen, nichtleeren Alphabet.
v z.B. für Σ = {𝑎} existieren die Sprachen: {}, 𝑎 , 𝑎𝑎 , 𝑎, 𝑎𝑎 , 𝑎𝑎𝑎 , ...
v Wir wollen zeigen, dass sich die Sprache der Exponentialzahlen und der zugehörige
reguläre Ausdruck rekursiv herleiten lassen:
v Alphabet: {−, +, 0,1,2,3,4,5,6,7,8,9, . , 𝑒}
v Einelementige Sprachen: 𝑆( = − , 𝑆) = {+}, 𝑆* = {0}, ... , 𝑆+ = {𝑒}
v Zugehörige reguläre Ausdrücke: 𝑟( = −, 𝑟) = + , 𝑟* = 0, ... , 𝑟+ = 𝑒
v Vereinigung zweier Sprachen:
𝑎, 𝑏 ∪ 0,1,2 = 𝑎, 𝑏, 0,1,2 ⇒ 𝑆$ + 𝑆! 𝐸𝑙𝑒𝑚𝑒𝑛𝑡𝑒
v Verknüpfung zweier Sprachen:
𝑎, 𝑏 0,1,2 = 𝑎0, 𝑎1, 𝑎2, 𝑏0, 𝑏1, 𝑏2 ⇒ 𝑆$ \ 𝑆! 𝐸𝑙𝑒𝑚𝑒𝑛𝑡𝑒
v Kleensche Hülle einer Sprache:
0,1 ∗ = ∅, 0,1,00,01,10,11,000, … ⇒ ∞ 𝐸𝑙𝑒𝑚𝑒𝑛𝑡𝑒
107
Beispiel: Regulärer Ausdruck für Exponentialzahlen (II)

Erzeugung weiterer Sprachen aus den einelementigen:


Operation Sprache Ausdruck
𝑆+ ∪ 𝑆- 𝑆. = {−, +} 𝑟. = (−, +)
𝑆# ∪ 𝑆( ∪ ⋯ 𝑆/ = {0,1,2, … , 9} 𝑟/ = 0 1 … 9 = (0 − 9)
𝑆. 𝑆/ 𝑆0 𝑆1 = {−0. , … , −9. , … } 𝑟1 = − + 0 − 9 .
𝑆1 𝑆/ 𝑆2 = {−0.0, −0.1, … } 𝑟2 = − + 0 − 9 . (0 − 9)
𝑆2 𝑆/∗ 𝑆4 = {−0.0, −0.00, −0.000, … } 𝑟4 = − + 0 − 9 . (0 − 9)(0 − 9)∗
kürzer: − + 0 − 9 . (0 − 9)-
𝑆4 𝑆5 𝑆6 = {−0.0𝑒, … } 𝑟6 = − + 0 − 9 . (0 − 9)- 𝑒
-
𝑆6 𝑆. 𝑆7 = {−0.0𝑒−, … } 𝑟7 = − + 0 − 9 . 0 − 9 𝑒(−|+)
-
𝑆7 𝑆/ 𝑆8 = {−0.0𝑒 − 0, … } 𝑟8 = − + 0 − 9 . 0 − 9 𝑒(−|+)(0 − 9)
𝑆8 𝑆/∗ 𝑆9 = {−0.0𝑒 − 00, … } 𝑟9 = − + 0 − 9 . 0 − 9 -
𝑒(−|+)(0 − 9)-

108
Beispiel: Regulärer Ausdruck für Exponentialzahlen (II)

Erzeugung weiterer Sprachen aus den einelementigen:


Operation Sprache Ausdruck
𝑆+ ∪ 𝑆- 𝑆. = {−, +} 𝑟. = (−, +)
𝑆# ∪ 𝑆( ∪ ⋯ 𝑆/ = {0,1,2, … , 9} 𝑟/ = 0 1 … 9 = (0 − 9)
𝑆. 𝑆/ 𝑆0 𝑆1 = {−0. , … , −9. , … } 𝑟1 = − + 0 − 9 .
𝑆1 𝑆/ 𝑆2 = {−0.0, −0.1, … } 𝑟2 = − + 0 − 9 . (0 − 9)
𝑆2 𝑆/∗ 𝑆4 = {−0.0, −0.00, −0.000, … } 𝑟4 = − + 0 − 9 . (0 − 9)(0 − 9)∗
kürzer: − + 0 − 9 . (0 − 9)-
𝑆4 𝑆5 𝑆6 = {−0.0𝑒, … } 𝑟6 = − + 0 − 9 . (0 − 9)- 𝑒
-
𝑆6 𝑆. 𝑆7 = {−0.0𝑒−, … } 𝑟7 = − + 0 − 9 . 0 − 9 𝑒(−|+)
-
𝑆7 𝑆/ 𝑆8 = {−0.0𝑒 − 0, … } 𝑟8 = − + 0 − 9 . 0 − 9 𝑒(−|+)(0 − 9)
𝑆8 𝑆/∗ 𝑆9 = {−0.0𝑒 − 00, … } 𝑟9 = − + 0 − 9 . 0 − 9 -
𝑒(−|+)(0 − 9)-

109
Beispiel: Regulärer Ausdruck für Exponentialzahlen (III)

Der reguläre Ausdruck 𝑟, beschreibt die Sprache 𝑆, , die die zuvor vorgestellte Sprache
der Exponentialzahlen darstellt.

In Python: rI = r"[-+][0-9]\.[0-9]+e[-+][0-9]+”

kürzer: rI = r"[-+]\d\.\d+e[-+]\d+”

r = raw string
110
So what...

What does all this mean to you, as a user? Absolutely


nothing. As a user, you don’t care if it’s regular, nonregular,
unregular, irregular, or incontinent. So long as you know
what you can expect from it, you know all you need to care
about. — Jeffrey Friedl

111
That’s why...

Wo fast jeder schon reguläre Ausdrücke verwendet hat:


v Suche nach einer Zeichenfolge in einem Text
v Tabulator-Vervollständigung in der Shell
v Mit * eine Gruppe von Dateien auswählen (*.txt)
Einschränkungen
v Für Anfänger wirken sie zwar sehr kryptisch und
v nach der reinen Lehre ist „Zählen“ nicht möglich (z.B. geht 𝑎& 𝑏 & nicht), aber
Vorteile
v Sehr nütztliches Werkzeug zum Finden von Mustern (pattern matching).
v Eine vergleichbare „manuelle“ Implementierung wäre viel aufwändiger.
v Python hat Erweiterungen, die sogar das „Zählen“ ermöglichen.
112
RegEx in Python – Grundlegende Syntax
Syntax Beschreibung
. entspricht einem beliebigen Zeichen außer Zeilenumbruch/newline
^ entspricht dem Beginn eines Strings
$ entspricht dem Ende eines Strings
* voriger Ausdruck beliebig oft (inkl. nullmal) greedy
+ voriger Ausdruck beliebig oft (exkl. nullmal) greedy
? voriger Ausdruck null oder einmal greedy
*?, +?, ?? nicht gierige Versionen von *, +, ? non-greedy
{m} voriger Ausdruck genau m-mal
{m,n} voriger Ausdruck m- bis n–mal greedy
{m,n}? nicht gierige Version von {m,n} non-greedy
[...] ein beliebiges Zeichen aus der Menge (...)
[^...] ein beliebiges Zeichen, das nicht in der Menge ist
A|B A oder B (A und B sind reguläre Ausdrücke)
(...) Gruppierung: Speichert den gefundenen Inhalt in Klammern
113
RegEx in Python – Maskierungszeichen

v Zeichen mit einer speziellen Bedeutung (., *, ...) können mit ihrer eigentlichen
Bedeutung durch voranstellen eines Backslashs (\) verwendet werden.
v Bekannte Maskierungszeichen (\n, \t, ...) funktionieren wie erwartet, z.B. entspricht
r‘\n+\‘ einem oder mehreren Zeilenumbrüche.
Syntax Beschreibung
\number entspricht dem n-ten zuvor gefundenen Text (Index startet mit 1)
\d entspricht [0-9]
\D entspricht [^0-9]
\S entspricht beliebigem Whitespace (also [\t\n\r\f\v])
\S alles außer Whitespace
\w beliebiges alphanumerisches Zeichen
\W beliebiges nicht alphanumerisches Zeichen
\A entspricht dem Beginn eines Strings
\Z entspricht dem Ende eines Strings
\b (Neg.: \B) Leerer String am Anfang oder Ende eines Wortes. Formell gesehen die Grenze
(boundary) zwischen \w und \W (oder umgekehrt). Somit matcht r’\bfoo\b’
‘foo’, ‘foo.’, ‘(foo)’, ‘bar foo baz’ aber nicht ‘foobar’ oder ‘foo3’ 114
Ein Beispiel

v Email-Adresse:

\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b

115
RegEx in Python – Das Module re

v Das Modul re erlaubt RegEx-Matching ähnlich zu Perl, sowohl für Unicode-Strings


als auch für 8-bit-Strings (Bytes).
v Es wäre sehr unpraktisch, normale Strings zu verwenden: So würde man \\\\
schreiben müssen, um einen Backslash im regulären Ausdruck, der mit \\ dargestellt
würde, zu formulieren. Daher werden Raw-Strings verwendet.
v Bereitgestellte Funktionen sind Shortcuts, damit man den regulären Ausdruck nicht
vorher kompilieren muss.
v Neben re gibt es das Third-Party-Modul regex. Dieses stellt erweiterte Funktionen
bereit.

116
Das Module re: Zentrale Flags

Flags verändern das Verhalten der Auswertung:


Syntax Beschreibung
re.A Matching von \w, \W, \b, \B, \d, \D, \s und \S auf der Grundlage von ASCII-Zeichen
re.ASCII (relevant nur für Unicode-Muster/Texte)
re.I Groß-/Kleinschreibung ist irrelevant: [A-Z] matcht nun auch Kleinbuchstaben. Ein paar
re.IGNORECASE Eigenheiten bei Unicode sind zu beachten (siehe Dokumentation).
re.M Matching von ^ bei jedem Zeilenanfang und Matching von $ bei jedem Zeilenende.
re.MULTILINE
re.S . matcht beim Setzen des Flags auch einen Zeilenumbruch. Wird in manchen Kreisen als
re.DOTALL “bad practice” angesehen.
re.X Erlaubt das Einfügen von Zeilenumbrüchen und Kommentaren innerhalb eines Musters; alle
re.VERBOSE Whitespaces (außer solchen in Zeichenklassen oder mit vorangestelltem \) im Musters
werden ignoriert.

117
Das Module re: Zentrale Funktionen
Syntax Beschreibung
re.compile(pattern, flags=0) Kompiliert ein Muster in ein RegEx-Objekt (was ein RegEx-
Objekt ist, sehen wir gleich)
re.search(pattern, string, flags=0) Sucht nach dem ersten Auftreten des Musters im String und
gibt ein Match-Objekt oder None bei erfolgloser Suche zurück.
re.match(pattern, string, flags=0) Prüft, ob das Muster am Anfang(!) des Strings auftritt (auch bei
re.M) und gibt ein passendes Match-Objekt oder None zurück.
re.fullmatch(pattern, string, flags=0) Prüft, ob der gesamte String das Muster matcht.
re.split(pattern, string, maxsplit=0, Trennt den String an den Stellen, wo das Muster gefunden wird
flags=0) und gibt das Ergebnis als Liste zurück.
re.findall(pattern, string, flags=0) Gibt alle nicht-überlappenden Matches als Liste zurück.
re.finditer(pattern, string, flags=0) Dasselbe wie findall, nur wird ein Iterator zurückgegeben.
re.sub(pattern, repl, string, count=0, Liefert einen String, in dem die gefundenen Muster im
flags=0) übergebenen String durch repl ersetzt wurden. repl kann ein
String oder eine Funktion sein.
re.subn(pattern, repl, string, Dasselbe wie sub, gibt aber ein Tupel
count=0, flags=0) (neuer_string, anzahl_substitutionen) zurück
re.escape(pattern) Escaped spezielle Regex-Zeichen in pattern.
118
Das Module re: RegEx-Objekte

v Ein mit compile kompiliertes RegEx-Objekt ist bereit, einen übergebenen String zu
verarbeiten.
v Kompilierte RegEx-Objekte bieten Methoden, die den soeben gesehen Funktionen sehr
ähnlich sind:
search, match, fullmatch, split, findall, finditer, sub, subn
v Es stehen zudem einige Attribute zur Verfügung, mit denen man die Flags, das Pattern
selbst und Informationen zu den Gruppierungen abrufen kann.

119
Das Module re: Match-Objekte

v Manche Funktionen geben keine Strings, sondern Match-Objekte zurück.


v Match-Objekte selbst sind immer True, da aber match() und search() ggf. None
zurückliefern, ist eine einfache If-Abrage ohne Probleme möglich.
v Zentrale Methoden:

Syntax Beschreibung
Match.expand(template) Das übergebene String-Template wird mit den Inhalten des
Match-Objektes ausgewertet (ähnlich zu sub()), nur dass man
hier bereits mit einem Match-Objekt arbeitet.
Match.group([group1, ...]) Gibt die jeweilige Gruppe des Matches zurück (siehe
Gruppierungs-Syntax). Wird 0 als Argument übergeben, werden
alle Match-Strings (somit aller Gruppen) zurückgegeben.
Match.groups(default=None) Gibt alle Gruppen als Tupel zurück. Der Default-Wert gibt an,
welchen Wert leere Gruppen (also solche, die nicht gematcht
wurden) annehmen sollen.

120
Beispiel

v Datumsformat:

121
Challenges (12)

C27:
Schreiben Sie ein Programm, das eine String-
Ersetzung durchführt und dabei die
Großkleinschreibung nicht beachtet (case C28:
insensitive). Das Wort eine soll durch das Wort Schreiben Sie ein Programm, das in
DIE ersetzt werden: einem String sämtliche Werte

Eine Katze hat einen Schwanz, eine Maus ebenso.


zwischen zwei doppelten
Anführungszeichen in eine Liste
extrahiert und wiedergibt:
“Python”, “Java”, “PHP”, “Javascript”, “C++”

122
Challenges (12) – cont.

C29:
Laden Sie vom Onlinecampus die c29.zip runter. Darin enthalten finden Sie mehrere html-Dateien, die die
beliebtesten Babynamen in den USA für verschiedene Jahre enthalten. Zudem finden Sie eine Rahmendatei
(names.py), die Sie ausimplementieren sollen:
o Implementieren Sie die Funktion extract_filenames(filename), die einen Dateinamen (z.B. names1990.html
entgegennimmt) und die Daten der Datei als Liste zurückgibt (Jahreszahl, gefolgt von dem Namen und den Rang),
also z.B. [‘2020’,’Aaron 34’]. Die Liste soll alphabetisch nach dem Namen sortiert sein.
o Implementieren Sie main() so, dass sie die Funktion aufruft und das Ergebnis ausgibt. Die Ausgabe soll in etwa
folgendermaßen aussehen (hier nur ein Bsp., nicht Teil der echten Ausgabe):
2006
Aaron 34
Abbey 68
Abbie 99
...
Hinweise:
o Jungen- und Mädchennamen werden in einem gemeinsamen Pool erfasst.
o Manchmal kommen Namen doppelt vor, dann soll nur 1 Rang verwendet werden (Pro: verwenden Sie den
niedrigsten Rang).
o Es gibt nicht nur eine Lösung für die regulären Ausdrücke, die Sie zum Extrahieren der Daten verwenden.
Manche sind strikter, andere weniger (Diskussion). Eine funktionierende für diesen Datensatz ist ausreichend.
123
Challenges (12) – cont.

C29b:
Das soeben erstellte Script von der Challenge 29 soll nun um einen kleinen Punkt erweitert werden:
o Die Ausgabe soll nun nicht mehr im Standard-Out ausgegeben serden, sondern in eine Textdatei geschrieben
werden.
o Wir haben nicht detailliert behandelt, wie Dateien in Python geöffnet und geschrieben werden. Es ist aber kein
Hexenwerk und eine kurze Internetrecherche führt zu den benötigten Informationen.
o Im Zusammenhang mit der zu erzeugenden Textdatei soll sich unser Kommandozeilenprogramm folgendermaßen
verhalten:
o Ein Flag --summary soll optional zur Verfügung stehen. Falls dieses beim Aufruf des Programms übergeben
wird, wird die Ausgabe in eine Textdatei geschrieben, ansonsten direkt ausgegeben.
o Die Summary-Datei soll den Suffix .summary erhalten, somit wird aus names1990.html die Summary-Datei
names1990.html.summary.
o In der Kommandozeile können Wildcards verwendet werden. Gibt man also z.B. den Befehl
names.py --summary names*.html ein, werden die jeweiligen Dateinamen als Liste dem Script übergeben. Ihr
Script sollte also darauf ausgelegt sein, dass potentiell mehrere Dateinamen verarbeitet werden sollen.

124

Das könnte Ihnen auch gefallen