Suchen und Sortieren
Einführung
Das Ziel einer Suche ist es, in einer grossen Datenmenge möglichst schnell das gesuchte Element zu finden. Beispielsweise suchen wir in einem Lexikon oder Wörterbuch den Eintrag zu einem Begriff, im Telefonbuch die Nummer einer Person oder eines Geschäfts. Heutzutage suchen wir zumeist im Internet - aber wie schafft es dict.leo.org einen Suchbegriff innert Millisekunden zu finden? Wie findet tel.search.ch den richtigen Eintrag
?
Aufgabe A1: Suchen in einem Wörterbuch / Lexikon
Zu zweit oder dritt: Eine Person wählt in einem Wörterbuch oder Lexikon heimlich einen Eintrag, der auf einer Seite rechts oben steht. Das Buch wird geschlossen und eine andere Person versucht mit möglichst wenigen Suchschritten (1x Blättern ist ein Schritt), den Begriff zu finden. Es ist dabei verboten, auf die Markierungen mit den Anfangsbuchstaben an der Seite des Buches zu schauen.
Überlegt euch verschiedene Strategien, wie ihr vorgehen könnt. Welche Strategie benötigt die wenigsten Schritte?
Lineare Suche
Der einfachste Algorithmus sucht das ganze Buch von vorne nach hinten durch. Allerdings dauert das ziemlich lange. Ist das Buch doppelt so lang, wird die Suche auch doppelt so lange dauern. Wir sprechen deshalb von linearer Suche - der Aufwand steigt linear zusammen mit der Menge der Begriffe.
Aufgabe A2: Lineare Suche in Python
Betrachte folgenden Datensatz mit Namen und Telefonnummern. Zum Beispiel ist die Telefonnummer von Daniel 0791111111.
- lineare_suche.py
names = ['Amos', 'Daniel', 'Ezechiel', 'Habakuk',
'Haggai', 'Hosea', 'Jeremia', 'Jesaja',
'Joel', 'Jona', 'Maleachi', 'Micha',
'Nahum', 'Obadja', 'Sacharja', 'Zefanja']
numbers = ['0790000000', '0791111111', '0792222222', '0793333333',
'0794444444', '0795555555', '0796666666', '0797777777',
'0798888888', '0799999999', '0791234567', '0793213213',
'0794565656', '0797898989', '0799876543', '0798767676']
Implementiere die lineare Suche in einer Funktion linear_search(li,el)
. Die Funktion soll in der Liste li
nach dem Element el
suchen. Falls es gefunden wird, soll die Position (Index) des Elements in der Liste zurückgegeben werden (nicht geprintet!).
Test: Der Funktionsaufruf print(linear_search(names,'Haggai'))
soll $4$ ausgeben.
Nutze nun diese Funktion, um die Telefonnummer von 'Micha' zu ermitteln.
Achtung: Generell bei solchen Aufgaben gilt: Verwende keine vordefinierten Suchfunktionen. Es geht genau darum, dass du diese selbst programmierst.
Tipps (zuerst ohne Tipps versuchen!)
Gehe IN der Funktion die Liste Element für Element durch.
Vergleiche jedes dieser Elemente mit demjenigen, nach dem gesucht wird.
Wenn es gleich ist: Gebe die Position vom Element in der Liste zurück.
Ausserhalb der Funktion: Lese nun aus der anderen Liste das Element mit der eben ermittelten Position aus.
Lösung
def linear_search(li, el):
for i in range(len(li)):
if li[i] == el:
return i
return None
index = linear_search(names, 'Micha')
print(numbers[index])
Aufgabe A3: Postleitzahlen
Für das kleine Propheten-Telefonbuch ist es nicht so wichtig, wie schnell der Such-Algorithmus ist. Was aber, wenn wir ein grösseres Dataset verwenden?
Lade die Datei plz_data.py herunter und speichere sie im selben Verzeichnis wie dein Code. Du kannst die Daten zu Schweizer Postleitzahlen, Ortschaften und Kantonen wie folgt verwenden. Natürlich muss deine linear_search
Funktion von Aufgabe 2 vorhanden sein.
import plz_data
# Liste aller Schweizer Postleitzahlen:
postleitzahlen = plz_data.postleitzahlen()
# Liste der dazugehörigen Ortschaften
ortschaften = plz_data.ortschaften()
# Liste der dazugehörigen Kantone
kantone = plz_data.kantone()
plz = 1000
index = linear_search(postleitzahlen, plz)
ort = ortschaften[index]
kanton = kantone[index]
print("{0} {1}, {2}".format(plz, ort, kanton))
1000 Lausanne, Waadt
Die Listen sind nach Postleitzahlen sortiert.
Löse folgende Teilaufgaben alle in einer Datei. Nutze Kommentare, z.B. # 1
als Überschriften für die einzelnen Teilaufgaben.
Wie heisst die Ortschaft mit der ultra-nerdigen Postleitzahl 4242 und in welchem Kanton liegt sie?
Überprüfe deinen Code, indem du die PLZ deines Wohnortes eingibst.
Welche Postleitzahl hat die Ortschaft Niederbipp?
Wie viele Postleitzahlen gibt es für die Stadt Bern?
Wie viele Postleitzahlen gibt es im Kanton Thurgau?
Tipps zu 4 und 5.
Diese Aufgaben löst man ohne die linear_search
-Funktion.
Du kannst die Aufgabe lösen, indem du eine neue Funktion programmierst (die natürlich viele Ähnlichkeiten mit der linear_search
-Funktion hat). Du kannst aber auch einfach eine Schleife programmieren, die nicht in einer Funktion steht.
Idee: Gehe alle Positionen der Postleitzahlen durch und schaue, zu welchem Kanton sie gehören. Falls sie zum richtigen gehören, erhöhe einen Counter um 1.
Lösung mit Code
- Aufgabe A3.py
import plz_data
postleitzahlen = plz_data.postleitzahlen()
ortschaften = plz_data.ortschaften()
kantone = plz_data.kantone()
def linear_search(li, el):
for i in range(len(li)):
if li[i] == el:
return i
return None
# 1. Wie heisst die Ortschaft mit der ultra-nerdigen Postleitzahl 4242 und in welchem Kanton liegt sie?
index = linear_search(postleitzahlen, 4242)
ort = ortschaften[index]
kanton = kantone[index]
print("{0}, {1}".format(ort, kanton))
# 2. Überprüfe deinen Code, indem du die PLZ deines Wohnortes eingibst.
index = linear_search(postleitzahlen, 8570)
print(ortschaften[index])
# 3. Welche Postleitzahl hat die Ortschaft Niederbipp?
index = linear_search(ortschaften, "Niederbipp")
print(postleitzahlen[index])
# 4. Wie viele Postleitzahlen gibt es für die Stadt Bern?
zaehler = 0
for ort in ortschaften: # andere Variante: for i in range(len(ortschaften)): if ortschaften[i] == "Thurgau"...
if ort == "Bern":
zaehler = zaehler + 1
print(zaehler)
# Wie viele Postleitzahlen gibt es im Kanton Thurgau?
zaehler = 0
for i in range(len(kantone)): # andere Variante: for k in kantone: if k == "Thurgau"...
if kantone[i] == "Thurgau": # ...
zaehler = zaehler + 1
print(zaehler)
Aufgabe A4: Postleitzahlen plus (optional)
Anmerkungen LP
Das Keyword in
bewirkt im Hintergrund nichts anderes als eine Suche nach dem gewünschten Element - ich finde ungünstig, wenn wir das hier als Lösung darstellen. Schöne wäre doch, wenn wir den existierenden Such-Code hierfür verwenden würden (à la if linear_search(li, el) is not None
).
Wie viele verschiedenen Ortsnamen gibt es im Kanton Bern? Beachte, dass es Ortschaften mit mehreren Postleitzahlen und Postleitzahlen mit mehreren Ortsnamen gibt.
Bestimme die Summe aller Quersummen von allen vierstelligen Zahlen, welche als Postleitzahl in der Schweiz vergeben sind.
Tipps zu 1.
Das Problem ist, dass du versuchen musst, Duplikate zu verhindern. Dazu kannst du alle Ortsnamen des Kantons Bern in eine Liste schreiben. Bevor du einen neuen hinzufügst, überprüfst du erst, ob der Name bereits in der Liste vorkommt. Nur wenn nicht, fügst du diesen hinzu.
Code zum Überprüfen, ob ein Element el
(nicht) bereits in einer Liste li
vorkommt:
if el in li:
# Code der ausgefuehrt wird, wenn el bereits in li vorkommt
if not el in li:
# Code der ausgefuehrt wird, wenn el NICHT bereits in li vorkommt
Tipps zu 2.
Erstelle zuerst eine Funktion quersumme(x), welche die quersumme der Zahl x zurückgibt: Also zum Beispiel 6 für die Zahl 123. Hierfür hilft fogelende Überlegung:
Wenn du 123 % 10
rechnest, erhältst du 3. Wenn du 123 // 10
rechnest, erhältst du 12: Noch eine Runde:
Wenn du 12 % 10
rechnest, erhältst du 2. Wenn du 12 // 10
rechnest, erhältst du 1: Noch eine Runde:
Wenn du 1 % 10
rechnest, erhältst du 1. Wenn du 1 // 10
rechnest, erhältst du 0: fertig.
Deine Funktion nutzt du, um die Quersummen der Postleizahlen zu berechnen.
Lösung ohne Code
Lösung mit Code
import plz_data
postleitzahlen = plz_data.postleitzahlen()
ortschaften = plz_data.ortschaften()
kantone = plz_data.kantone()
def quersumme(x):
cs = 0
while x > 0:
cs = cs + x % 10
x = x // 10
return cs
#1: Wie viele verschiedenen Ortsnamen gibt es im Kanton Bern?
ortsnamen = []
for i in range(len(kantone)):
if kantone[i] == "Bern":
if not ortschaften[i] in ortsnamen:
ortsnamen.append(ortschaften[i])
print(len(ortsnamen))
#2: Summe aller Quersummen aller Postleitzahl in der Schweiz.
summe = 0
for plz in postleitzahlen:
summe = summe + quersumme(plz)
print(summe)
Aufgabe A5: Postleitzahlen mit Zeitmessung
Schreibe Python-Code, um zu messen, wie lange die Suche dauert. Um die Zeit zu stoppen, kannst du das time
Modul verwenden. Wie lange dauert die Suche für die PLZ 1000 (Lausanne)? Wie lange für 8590 Romanshorn? Weshalb der Unterschied?
import time
start = time.time()
# do something
elapsed = time.time() - start
print("do something took {0}s".format(elapsed))
Lösung
- time_algos.py
import time
import plz_data
def stopwatch(plz):
start = time.time()
index = linear_search(plz_data.postleitzahlen(), plz)
elapsed = time.time() - start
print("Lineare Suche für {0} dauerte {1:.0f}s".format(plz, elapsed))
stopwatch(8590)
Aufgabe A6: Mehrere Suchresultate
In der Liste der Postzleitzahlen kommen einige Postleitzahlen mehrmals vor. Zum Beispiel kommt die Postleitzahl 8580 viermal vor. Wenn du aber mit der Funktion linear_search
in der Liste der Postzleitzahlen nach 8580 suchst, erhälst du bloss eine Position: Diejenige, an der die Postleitzahl 8580 zum ersten Mal auftaucht. An dieser Position befindet sich in der Ortsnamen-Liste der Eintrag „Sommeri“. Auch Amriswil hat die Postleitzahl 8580, erscheint aber weiter hinten in der Liste.
Erstelle eine neue Funktion linear_search2(li, el)
. Dazu kopierst du die bestehende und erweiterst sie: Neu soll nicht bloss eine Position zurückgegeben werden, sondern eine Liste mit Positionen. Falls bloss ein Eintrag gefunden wird, enthält die Liste nur ein Element; falls nichts gefunden wird, ist die Liste leer.
Teste die Funktion zum Beispiel so:
positionen = linear_search2(postleitzahlen,8580)
for p in positionen:
print(ortschaften[p])
Lösung ohne Code
Sommeri
Hagenwil b. Amriswil
Hefenhofen
Amriswil
Lösung mit Code
import plz_data
postleitzahlen = plz_data.postleitzahlen()
ortschaften = plz_data.ortschaften()
kantone = plz_data.kantone()
def linear_search2(li, el):
results = []
for i in range(len(li)):
if li[i] == el:
results.append(i)
return results
positionen = linear_search2(postleitzahlen,8580)
for p in positionen:
print(ortschaften[p])
Aufgabe A7: Verbesserte lineare Suche (optional)
Stelle dir vor, du suchst in der bereits alphabetisch sortierten Liste ['albert','christine','dominick','emma']
nach dem Namen 'barbara'
. Sobald du beim Suchen bei 'christine'
amkommst, weisst du, dass du abbrechen kannst und der gesuchte Wert (also 'barbara'
) nicht in der Liste enthalten ist. Achtung: Das geht nur, wenn man sicher weiss, dass die Liste sortiert ist.
Erweitere deine lineare Suche um diese Funktionalität. Füge deiner Funktion dazu ein weiteres Argument mit einem Defaultwert is_sorted = False
hinzu. Wenn die Liste nicht sortiert ist, soll die lineare Suche wie bisher ablaufen. Wenn sie sortiert ist, soll sie vorzeitig abbrechen.
Aufgabe A8: Besserer Algorithmus? (optional)
Hast du eine Idee für einen besseren, sprich effizienteren Suchalgorithmus einer bereits sortierten Liste? Bespreche mit der Lehrperson und implementiere danach in Python.
Weiter zu Binäre Suche