Im letzten Kapitel haben wir uns mit Super- und Quanten-Computer befasst:

  • Von Super-Computern wissen wir, dass diese sehr viel Parallelisieren. Können wir das auch auf unserem eigenen Computer? Bringt das etwas?
  • Bei den Quantencomputern haben wir gesehen, dass diese eine Gefahr für die auf dem Faktorisierungs-Problem basierenden asymmetrischen Verschlüsselungen darstellen

Faktorisieren

Wir möchten eine Zahl faktorisieren. Wie können wir vorgehen? Wie können wir das Vorgehen optimieren?

Aufgabe

Schreibe eine Python-Funktion (ein Unterprogramm), das eine Zahl in ihre Primfaktoren zerlegt:

  • Die Funktion erhält als Argument eine Zahl
  • Die Zahl liefert die Primfaktoren als Liste zurück
Lösung
def factorize(n):
zahl = n
factors = []
i = 2
while i <= n:
	if zahl % i == 0:
		factors.append(i)
		zahl = zahl // i
	else:
		i = i + 1
return factors
1
2
3
4
5
6
7
8
9
10
11

Zusatzaufgabe

Programmiere eine Funktion, welche Primfaktorenzerlegungen überprüft:

  • Die Funktion erhält eine Zahl und ihre Primfaktoren in Form einer Liste – also zwei Argumente
  • Sie liefert True oder False zurück, je nachdem ob die Zerlegung korrekt ist oder nicht.

Brute-Force

Wir testen alle möglichen Faktoren aus.

Optimieren:

  • nur die 2 und ungerade Zahlen testen
  • nur bis zur Hälfte der Zahl testen (aber Achtung Primzahl)

Aufgabe

Kopiere deine Funktion und benenne Sie neu. Optimiere nun die neue Funktion mit den oben beschriebenen Ideen (oder eigenen).

Du kannst nun die beiden Funktionen vergleichen. Verwende dazu die unten beschriebene Zeitmessung.

Lösung: optimiert
def factorize(n):
zahl = n
factors = []
i = 2
while zahl % i == 0:
	factors.append(i)
	zahl = zahl // i
i = 3
while i < n//2+1:
	if zahl % i == 0:
		factors.append(i)
		zahl = zahl // i
	else:
		i = i + 2
if len(factors) == 0:
	factors.append(zahl)

return factors
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Lösung: rekursiv
def factorize(n):
if n == 1:
	return []
for i in range(2,n+1):
	if n%i == 0:
		return [i] + factorize(n//i)
1
2
3
4
5
6

Zeit messen

Mit der Funktion time aus dem gleichnamigen Modul, können wir die aktuelle Systemzeit in Sekunden abfragen. Wenn wir das 2x machen und die Differenz berechnen, dann haben wir die dazwischen verstrichene Zeit in Sekunden:

from time import time

start = time()
# mach etwas
end = time()
print(end - start)
1
2
3
4
5
6

Dictionary

Wir könnten Primzahlen vorberechnen und so die Zeit der Primfaktorenzerlegung. Die folgende Webseite hat Primzahlen vorberechnet:

👉 http://www.math.uchicago.edu/~luis/allprimes.html

Wenn man aber grössere Listen hat, sollte man die in einer externen Datei speichern und in Python einlesen. Wenn man die Zahlen Komma-getrennt in ewiner CSV-Datei hat, kann man sie wie folgt einlesen:

txt_file = open("primes.csv", "r")
file_content = txt_file.read()
txt_list = file_content.split(",")

primes = []
for txt_prime in txt_list:
	primes.append(int(txt_prime))
1
2
3
4
5
6
7

Aufgabe

Versuche diese Dictionary-Attacke zu programmieren:

  • Ist sie schneller?
  • Gibt es Probleme?
Lösung

Es gibt viel zu viele Primzahlen! Wir können die gar nicht alle vorberechnen, geschweige denn mit sinnvollem Aufwand abspeichern!

Primzahlen

Aufgabe

Schreibe eine Funktion, welche für eine Zahl überprüft, ob es sich um eine Primzahl handelt.

  • Die zu prüfende Zahl als Argument
  • True oder False als Rückgabewert

Primzahlfunktion

π(x) sei die Primzahlfunktion, die Anzahl Primzahlen, die kleiner als x sind.

π(x):=|{pPpx}|

Primzahlsatz

limxπ(x)xln(x)=1

Bereits 1851 bewies Pafnuti Lwowitsch Tschebyschow eine schwächere Version des Primzahlsatzes:

0,92929π(x)xln(x)1,1056, für alle hinreichend grossen x.

Das heisst, dass die Anzahl der Primzahlen unter einer gegebenen Grösse um nicht mehr als ungefähr 10 % nach oben oder unten von der logarithmischen Funktion x/ln(x) abweicht.

Aufgabe

Wie viele Primzahlen gibt es < 1’000’000’000?

Zusatzaufgabe

Schreibe eine Funktion die (grosse) Primzahlen berechnet.

Parallelisieren

In Python kann man Abläufe mit Hilfe von sogenannten Threads parallelisieren:

Das folgende Beispiel starte eine Funktion 3x parallel.

import logging
import threading
import time

def thread_function(name):
	logging.info("Thread %s: starting", name)
	time.sleep(2)
	logging.info("Thread %s: finishing", name)


format = "%(asctime)s: %(message)s"
logging.basicConfig(format=format, level=logging.INFO,
					datefmt="%H:%M:%S")

threads = []
for index in range(3):
	logging.info("Main    : create and start thread %d.", index)
	x = threading.Thread(target=thread_function, args=(index,))
	threads.append(x)
	x.start()

for index, thread in enumerate(threads):
	logging.info("Main    : before joining thread %d.", index)
	thread.join()
	logging.info("Main    : thread %d done", index)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

Mehr zum Beispiel und zu Threads:
👉 https://realpython.com/intro-to-python-threading/

Aufgabe

Teste das obenstehende Beispiel und beantworte die folgenden Fragen mit Hilfe der verlinkten Quelle:

  • Wo wird der eigentliche Thread gestartet?
  • was macht das join auf Zeile 24?

Aufgabe

Wie könnte man Threads verwenden um Primzahlen zu berechnen oder Zahlen in Primfaktoren zu zerlegen?

  • Überlege dir wie man das machen könnte
  • Versuche deine Überlegungen in Code umzusetzen.