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.
In [ ]:
for name in clean_names:
if len(name) > 8:
print(name)
In [ ]:
[name for name in clean_names if len(name) > 8]
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))
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))
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))
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)))
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)))
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))
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
In [ ]:
student_set = set()
print(len(student_set))
student_set.add('Anna')
student_set.add('Berta')
student_set.add('Anna')
print(len(student_set))
In [ ]:
my_set = {5, 2, 4, 3}
print(my_set)
my_set.remove(3)
print(my_set)
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
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]))
Listen lassen sich sehr einfach sortieren:
In [ ]:
clean_names.sort()
clean_names
Zum Nachdenken:
distinct_names
nicht sortieren?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)
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.
In [ ]:
for key in sorted(name_counter.keys()):
print('{} -> {}'.format(key, name_counter[key]))
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 [ ]: