#!/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()