149 lines
5.4 KiB
Python
149 lines
5.4 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
|
||
|
|
# erzeuge_kombinatorik_wand.py
|
||
|
|
"""
|
||
|
|
Erzeugt das Wachstumsdiagramm zur Einfuehrung:
|
||
|
|
|
||
|
|
bilder_04/kap_einfuehrung_kombinatorik.svg
|
||
|
|
|
||
|
|
Die Handrechnung des Abschnitts rechnet vor, wie lange vollstaendiges
|
||
|
|
Durchprobieren dauert, wenn ein Rechner eine Milliarde Reihenfolgen je Sekunde
|
||
|
|
prueft. Das Bild macht daraus eine Kurve - und zeigt daneben, was polynomiales
|
||
|
|
Wachstum im selben Massstab bedeutet: eine Linie, die am unteren Rand klebt.
|
||
|
|
|
||
|
|
Der didaktische Punkt ist nicht "n! waechst schnell", sondern die SCHMALHEIT
|
||
|
|
des Uebergangs: Zwischen n=15 (22 Minuten, machbar) und n=20 (77 Jahre,
|
||
|
|
hoffnungslos) liegen fuenf Auftraege. Deshalb der markierte Streifen.
|
||
|
|
|
||
|
|
ZU DEN ZAHLEN: Sie werden gerechnet, nicht eingetragen. pruefe_gegen_buch()
|
||
|
|
vergleicht sie anschliessend mit der abgedruckten Tabelle des Kapitels - weicht
|
||
|
|
eine ab, bricht das Skript ab, statt ein Bild zu erzeugen, das der Tabelle
|
||
|
|
daneben widerspricht.
|
||
|
|
|
||
|
|
Aufruf (aus dem Repository-Wurzelverzeichnis):
|
||
|
|
python3 bilder_04/erzeuge_kombinatorik_wand.py
|
||
|
|
|
||
|
|
Benoetigt: numpy, matplotlib
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import math
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||
|
|
from stil_04 import FARBEN, neue_figur, speichere # noqa: E402
|
||
|
|
|
||
|
|
PRUEFUNGEN_JE_SEKUNDE = 1e9
|
||
|
|
SEKUNDEN_JE_JAHR = 365.25 * 24 * 3600
|
||
|
|
|
||
|
|
# Die vier Marken der Handrechnung. Werte NUR zum Gegenpruefen - gezeichnet
|
||
|
|
# wird, was gerechnet wurde.
|
||
|
|
TABELLE_IM_BUCH = {
|
||
|
|
10: "0,004 Sekunden",
|
||
|
|
15: "22 Minuten",
|
||
|
|
20: "77 Jahre",
|
||
|
|
25: "490 Millionen Jahre",
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def sekunden(n: int) -> float:
|
||
|
|
return math.factorial(n) / PRUEFUNGEN_JE_SEKUNDE
|
||
|
|
|
||
|
|
|
||
|
|
def _signifikant(wert: float, stellen: int = 2) -> int:
|
||
|
|
"""Rundet auf 'stellen' signifikante Ziffern.
|
||
|
|
|
||
|
|
Die Tabelle des Kapitels tut genau das: 491,5 Millionen Jahre stehen dort
|
||
|
|
als '490 Millionen'. Volle Millionen zu runden ergaebe 492 - und damit ein
|
||
|
|
Bild, das der Tabelle daneben widerspricht."""
|
||
|
|
if wert == 0:
|
||
|
|
return 0
|
||
|
|
ordnung = math.floor(math.log10(abs(wert)))
|
||
|
|
faktor = 10 ** (ordnung - stellen + 1)
|
||
|
|
return int(round(wert / faktor) * faktor)
|
||
|
|
|
||
|
|
|
||
|
|
def lesbar(n: int) -> str:
|
||
|
|
"""Formatiert eine Dauer so, wie die Tabelle des Kapitels es tut."""
|
||
|
|
s = sekunden(n)
|
||
|
|
if s < 1:
|
||
|
|
return f"{s:.3f}".replace(".", ",") + " Sekunden"
|
||
|
|
if s < 3600:
|
||
|
|
return f"{round(s / 60)} Minuten"
|
||
|
|
jahre = s / SEKUNDEN_JE_JAHR
|
||
|
|
if jahre < 1000:
|
||
|
|
return f"{round(jahre)} Jahre"
|
||
|
|
if jahre < 1e9:
|
||
|
|
return f"{_signifikant(jahre / 1e6)} Millionen Jahre"
|
||
|
|
return f"{jahre:.1e} Jahre"
|
||
|
|
|
||
|
|
|
||
|
|
def pruefe_gegen_buch() -> None:
|
||
|
|
"""Die Zahlen im Bild muessen die der abgedruckten Tabelle sein."""
|
||
|
|
for n, erwartet in TABELLE_IM_BUCH.items():
|
||
|
|
ist = lesbar(n)
|
||
|
|
if ist != erwartet:
|
||
|
|
raise SystemExit(
|
||
|
|
f"Kombinatorik-Wand: Fuer n={n} ergibt die Rechnung {ist!r}, "
|
||
|
|
f"die Tabelle im Kapitel nennt {erwartet!r}. Bild und Text "
|
||
|
|
f"wuerden sich widersprechen.")
|
||
|
|
|
||
|
|
|
||
|
|
def zeichne() -> None:
|
||
|
|
figur, achse = neue_figur(breite=7.8, hoehe=5.0)
|
||
|
|
n = np.arange(1, 31)
|
||
|
|
|
||
|
|
fakultaet = np.array([sekunden(int(k)) for k in n])
|
||
|
|
quadrat = n.astype(float) ** 2 / PRUEFUNGEN_JE_SEKUNDE
|
||
|
|
kubik = n.astype(float) ** 3 / PRUEFUNGEN_JE_SEKUNDE
|
||
|
|
|
||
|
|
achse.semilogy(n, fakultaet, "-", color=FARBEN["fehler"], linewidth=2.4,
|
||
|
|
label=r"$n!$ — alle Reihenfolgen durchprobieren", zorder=4)
|
||
|
|
achse.semilogy(n, kubik, "-", color=FARBEN["zweit"], linewidth=1.8,
|
||
|
|
label=r"$n^3$", zorder=3)
|
||
|
|
achse.semilogy(n, quadrat, "-", color=FARBEN["haupt"], linewidth=1.8,
|
||
|
|
label=r"$n^2$", zorder=3)
|
||
|
|
|
||
|
|
# Der schmale Uebergang, um den es geht.
|
||
|
|
achse.axvspan(15, 20, color=FARBEN["warnung"], alpha=0.12, zorder=1)
|
||
|
|
achse.annotate("fünf Aufträge mehr —\n22 Minuten wird zu 77 Jahren",
|
||
|
|
xy=(17.5, sekunden(17) * 0.02), fontsize=9,
|
||
|
|
color=FARBEN["warnung"], ha="center", fontweight="bold")
|
||
|
|
|
||
|
|
# Waagerechte Bezugsmarken: was ein Mensch noch abwartet.
|
||
|
|
for hoehe, text in ((1.0, "1 Sekunde"), (3600.0, "1 Stunde"),
|
||
|
|
(SEKUNDEN_JE_JAHR, "1 Jahr")):
|
||
|
|
achse.axhline(hoehe, color=FARBEN["linie"], linewidth=0.9,
|
||
|
|
linestyle="--", zorder=2)
|
||
|
|
achse.text(30.4, hoehe, text, fontsize=8, color=FARBEN["gedaempft"],
|
||
|
|
va="center")
|
||
|
|
|
||
|
|
for k in sorted(TABELLE_IM_BUCH):
|
||
|
|
achse.plot([k], [sekunden(k)], "o", color=FARBEN["fehler"],
|
||
|
|
markersize=6, zorder=5)
|
||
|
|
achse.annotate(f"$n={k}$: {lesbar(k)}",
|
||
|
|
xy=(k, sekunden(k)), xytext=(-6, 12),
|
||
|
|
textcoords="offset points", fontsize=8.5,
|
||
|
|
color=FARBEN["text"], ha="right",
|
||
|
|
arrowprops=dict(arrowstyle="-", color=FARBEN["linie"],
|
||
|
|
linewidth=0.8))
|
||
|
|
|
||
|
|
achse.set_xlabel("Zahl der Aufträge $n$")
|
||
|
|
achse.set_ylabel(r"Rechenzeit in Sekunden bei $10^9$ Prüfungen/s")
|
||
|
|
achse.set_title("Warum Ausprobieren scheitert — dieselbe Achse für alle "
|
||
|
|
"drei Kurven", fontsize=10.5, color=FARBEN["text"])
|
||
|
|
achse.set_xlim(1, 30)
|
||
|
|
achse.set_ylim(1e-9, 1e26)
|
||
|
|
achse.legend(fontsize=9, loc="upper left", frameon=False)
|
||
|
|
|
||
|
|
speichere(figur, "kap_einfuehrung_kombinatorik")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
pruefe_gegen_buch()
|
||
|
|
print("Zahlen stimmen mit der Tabelle des Kapitels überein.")
|
||
|
|
zeichne()
|