Bedingungen


In [ ]:
# Im Notebook basics_1.ipynb haben wir die Zeilen aus der Datei
# names_short.txt in eine Liste von Zeilen namens clean_names eingelesen.
# Das tun wir noch einmal, weil wir diese Liste weiterhin brauchen.
with open('data/vornamen/names_short.txt', encoding='utf-8') as fh:
    clean_names = [line.rstrip() for line in fh.readlines()]
print(clean_names)

Oft muss im Programmablauf eine Abzweigung genommenen werden. Stellen wir uns vor, wir programmieren einen Bankomaten:

abzuhebender_betrag = input('Wieviel wollen Sie abheben? ')

WENN kontostand - abzugebender_betrag > ueberziehungsrahmen:
    Geld auszahlen   
SONST
    AUSGABE: Ihr Kontostand reicht nicht aus

Die allgemeine Form einer Bedingung in Python (und den meisten höheren Programmiersprache) sieht so aus:

if BEDINGUNG:
    tue das eine
else:
    tue etwas anderes

Wobei else weggelassen werden kann.

if

Ermitteln wir nun als Beispiel alle Namen aus unserer Liste clean_names, die länger als 8 Zeichen sind:


In [ ]:
for name in clean_names:
    if len(name) > 8:
        print(name)

Bedingungen in List Comprehensions

Das letzte Beispiel lässt sich auch mit einer List Comprehension lösen:


In [ ]:
[name for name in clean_names if len(name) > 8]

if ... else

Mit else können wir alle Fälle behandeln, die nicht die bei if gestellte Bedingung erfülllen. Im folgenden else-Abschnitt wollen wir zählen, wie viele Namen kürzer oder gleich 8 Zeichen sind:


In [ ]:
num_of_short_names = 0
num_of_long_names = 0

for name in clean_names:
    if len(name) > 8:
        num_of_long_names += 1
    else:
        num_of_short_names += 1
print("{} kurze Namen und {} lange Namen".format(num_of_short_names, num_of_long_names))

Unterbedingungen

If-Bedingungen können verschachtelt werden:


In [ ]:
short_length_names = 0
medium_length_names = 0
long_length_names = 0

for name in clean_names:
    if len(name) > 8:
        long_length_names += 1
    else:
        if len(name) < 5:
            short_length_names += 1
        else:
            medium_length_names += 1
print('{} kurze Namen, {} mittellange und {} lange Namen'.format(
    short_length_names, medium_length_names, long_length_names))

if ... elif ... else

In Python können solche verschachtelten Bedingungen oft vermieden werden, wenn man elif verwendet:


In [ ]:
short_length_names = 0
medium_length_names = 0
long_length_names = 0

for name in clean_names:
    if len(name) > 8:
        long_length_names += 1
    elif len(name) < 5:
        short_length_names += 1
    else:
        medium_length_names += 1
        
print('{} kurze Namen, {} mittellange und {} lange Namen'.format(
    short_length_names, medium_length_names, long_length_names))

Doppelt vorkommende Namen entfernen

Der in-Operator

In der Liste clean_names kommen manche Namen mehrfach vor. Je nach Fragestellung kann das erwünscht sein oder auch nicht. Versuchen wir daher, doppelt vorkommende Namen zu verhindern. Dazu müssen wir einen neuen Operator einführen, der testet, ob ein Wert in einer Sequenz vorhanden ist: in.


In [ ]:
'a' in 'Anakonda'

in funktioniert mit allen Sequenztypen und, wie wir noch sehen werden, ein paar anderen Typen, also auch mit Listen. Hier prüfen wir, ob der Integer 42 in einer Liste vorkommt:


In [ ]:
42 in [1, 55, 44, 32, 71, 41]

Und hier verwenden wir in, um zu prüfen, ob der Name bereits in einer Liste distinkter Namen erscheint:


In [ ]:
distinct_names = []
for name in clean_names:
    if name in distinct_names:
        pass
    else:
        distinct_names.append(name)
print('clean_names: {} Einträgen, distinct_names: {} Einträge'. format(len(clean_names), len(distinct_names)))

not in

Wenn wir statt in not in verwenden, können wir uns das else ersparen:


In [ ]:
distinct_names = []
for name in clean_names:
    # TODO
        distinct_names.append(name)
print('clean_names: {} Einträgen, distinct_names: {} Einträge'. format(len(clean_names), len(distinct_names)))

Sets

Wir können uns das Leben noch einfacher machen, wenn wir unsere Liste in einen neuen Datentyp umwandeln: set. Ein Set ist eine Menge im Sinne der Mengenlehre. In einer Menge darf jeder Wert nur einmal erscheinen.


In [ ]:
distinct_names = set(clean_names)
print('clean_names: {} Einträgen, distinct_names: {} Einträge'. format(len(clean_names), len(distinct_names)))

distinct_names ist jetzt aber keine Liste mehr, sondern vom Typ set:


In [ ]:
type(distinct_names)

Wir können ein Set einfach wieder in eine Liste zurückverwandeln: distinct_names = list(set(clean_names)), das ist aber vielfach nicht nötig, weil auch ein Set, obwohl es keine Sequenz ist, ähnliche Interaktionsmöglichkeiten bietet wie eine Sequenz, z.B. kann auch über ein Set mit for ... in iteriert werden:


In [ ]:
short_length_names = 0
medium_length_names = 0
long_length_names = 0

for name in distinct_names:
    if len(name) > 8:
        long_length_names += 1
    elif len(name) < 5:
        short_length_names += 1
    else:
        medium_length_names += 1
        
print('{} kurze Namen, {} mittellange und {} lange Namen'.format(
    short_length_names, medium_length_names, long_length_names))

Mengenoperatoren

Auch wenn Sets praktisch sind, um mehrfach vorkommende Werte auf einen Wert zu reduzieren, liegt ihr eigentlicher Daseinszweck in den Mengenoperatoren. Ich verweise dazu auf das Skriptum bzw. die Folien im Kurs und zeige hier nur, wie einfach z.B. die Schnittmenge zweier Mengen ermittelt werden kann:


In [ ]:
s1 = {'Anna', 'Otto', 'Franz', 'Willi'}
s2 = {'Hans', 'Franz', 'Anna'}
s1 & s2

Ein leeres Set anlegen und später befüllen


In [ ]:
student_set = set()
print(len(student_set))
student_set.add('Anna')
student_set.add('Berta')
student_set.add('Anna')
print(len(student_set))

Werte aus einem Set entfernen

Mit remove() lässt sich ein Wert wieder aus einem Set entfernen.


In [ ]:
my_set = {5, 2, 4, 3}
print(my_set)
my_set.remove(3)
print(my_set)

Dictionaries

Dictionaries, die in anderen Programmiersprachen auch Maps, Hashtables oder Assoziative Arrays heißen, sind (intern) einer der wichtigsten Typen in Python. In einem Dictionary wird jeder Wert über einen Key (Schlüssel) referenziert. Ein leeres Dictionary wird so angelegt:


In [ ]:
phone_numbers = {}

Dann können Werte eingefügt werden:


In [ ]:
phone_numbers['Anna'] = '0316 123456'
phone_numbers['Hans'] = '0664 345678'
phone_numbers['Otto'] = '0660 987654'

Dann können Werte abgefragt werden:


In [ ]:
phone_numbers['Hans']

Wichtig: in einem Dictionary kann ein Schlüssel nur einmal verwendet werden. Wird er ein zweites Mal verwendet, wird der ursprüngliche Wert überschrieben.


In [ ]:
phone_numbers['Hans'] = '0521 578978'

Wenn man will, kann man die Schlüssel-Wertpaare auch direkt anlegen:


In [ ]:
phone_numbers = {
    'Anna': '0316 123456',
    'Hans': '0664 345678',
    'Otto': '0660 987654'
}
phone_numbers

Ein Dictionary als Zähler

Wir können eine Dictionary verwenden, um unsere Namen zu zählen:


In [ ]:
name_counter = {}
for name in clean_names:
    if name in name_counter:
        name_counter[name] += 1
    else:
        name_counter[name] = 1
print(name_counter)

Wie wir sehen, funktioniert for ... in auch für Dictionaries. Dabei wird ein Key nach dem anderen geliefert.

Wenn wir nur an Namen interessiert sind, die mindestens zwei Mal erscheinen können wir das so lösen:


In [ ]:
for key in name_counter:
    if name_counter[key] > 1:
        print('{} erschein {} Mal'.format(key, name_counter[key]))

Sortieren

Listen lassen sich sehr einfach sortieren:


In [ ]:
clean_names.sort()
clean_names

Zum Nachdenken:

  1. Warum können wir distinct_names nicht sortieren?
  2. Was müsste man tun, um distinct_names sortierbar zu machen?

In [ ]:

Die Methode sort() sortiert inline, d.h. die existierende Liste wird umsortiert; die ursprüngliche Reihenfolge geht verloren. Wir können allerdings bei Bedarf auch eine sortierte Kopie der ursprünglichen Liste erzeugen:


In [ ]:
sorted_distinct_names = sorted(distinct_names)
print(distinct_names)
print(sorted_distinct_names)

Komplexere Listen sortieren

Solange eine Liste nur aus einzelnen Werten besteht, ist die Sortierung einfach. Aber was, wenn wie eine Liste von Listen oder eine Liste von Tupeln sortieren wollen? Erinnern wir uns an den name_counter, den wir oben geschrieben haben.


In [ ]:
name_counter

Wenn wir Python >= 3.6 verwenden, sollte die obige Ausgabe nach Keys sortiert sein (weil wir die Liste, aus der gezählt wurde, zuvor sortiert hatten). Für andere Python Versionen können wir uns darauf aber nicht verlassen. Wie sortieren wir nun nach den Schlüsseln? Dazu gibt es mehrere Möglichkeiten.

Möglichkeit 1: Zuerst die Keys sortieren


In [ ]:
for key in sorted(name_counter.keys()):
    print('{} -> {}'.format(key, name_counter[key]))

Möglichkeit 2: Das Dictionary in ein Set von Tupeln umwandeln.

Da wir uns wegen älterer Python-Versionen nicht darauf verlassen können, das in einem Dicitionary die Einträge sortiert vorliegen, können wir die Key-Value-Paare eines Dictionaries in eine Liste von Key-Value-Tupeln umwandeln. Vorläufig stellen wir uns ein Tupel einmal wie eine nachträglich nicht mehr veränderbare Liste vor.


In [ ]:
my_list = ['a', 'b', 'c']
my_tuple = ('a', 'b', 'c')
my_list.append('d')
my_tuple.append('d')

Tupel werden, wie wir oben gesehen haben, durch ein Paar runder Klammern gekennzeichnet.

Kehren wir zurück zum ursprünglichen Problem: wir wollten die Werte des Dictionaries sortieren. Dazu verwandeln wir jedes Schlüssel-Wert-Paar in ein Tuppel. Aus z.B. 'david': 2 wird ('david', 2). Das wird so oft benötigt, dass Dictionaries eine eigene Methode dafür eingebaut haben: items()


In [ ]:
name_counter.items()

Wir können uns das zunutze machen, um ein sortierte Liste von Tupeln zu erzeugen:


In [ ]:
sorted_by_name = sorted(name_counter.items())
sorted_by_name

Was aber, wenn wir nicht nach den Schlüsseln, sondern nach den Werten sortieren wollen? Hier wird die Sache etwas komplizierter. Sowohl sort() als auch sorted() kennen einen Parameter key=, der als Argument eine Funktion erwartet, die den zu sortierenden Wert zurückgibt. Da diese Funktion in unserem Fall nichts anderes tut, als den zweiten Wert eines jeden Tupels zurückzugeben, brauchen wir keine eigene Funktion zu schreiben, sondern können uns mit einem Lambda-Ausdruck begnügen (Details dazu in den Folien). Ein Lambda-Ausdruck hat diese Form:

lambda argument[, argument]: aktion
lambda mytuple: mytuple[1]

tut also nichts anderes, als ein Tupel an den Lambda-Ausdruck zu übergeben, der den zweiten Wert des Tupels zurückgibt.


In [ ]:
sorted_by_value = sorted(name_counter.items(), key=lambda x: x[1])
sorted_by_value

Wenn wir an den häufiger vorkommenden Namen stärker interessiert sind, können wir die Sortierreigenfolge mit einem weiteren Parameter reverse= umdrehen:


In [ ]:
sorted_by_value = sorted(name_counter.items(), key=lambda x: x[1], reverse=True)
sorted_by_value

In [ ]: