Skip to content
Snippets Groups Projects
Uebung_11.ipynb 32.8 KiB
Newer Older
Daniel Hahn's avatar
Daniel Hahn committed
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497
{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8c57efff",
   "metadata": {},
   "source": [
    "# <font color='blue'>**Übung 11 - Primfaktorzerlegung und Speichern von objektorientierten Datenstrukturen**"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad48d2ea",
   "metadata": {},
   "source": [
    "In dieser Übung soll eine objektorientierte Umgebung zur Berechnung von Primzahlzerlegungen aufgebaut werden. Dies wiederholt Konzepte der objektorientierten Programmierung und die Umsetzung von formulierten Abläufen in Code. Die Objekte werden anschließend in Dateien gespeichert und wieder eingelesen.\n",
    "\n",
    "### <font color='blue'>**Vorkenntnisse - Notebooks**\n",
    "* Übung 1\n",
    "* Übung 2\n",
    "* Übung 3\n",
    "* Übung 10\n",
    "* Grundlagen Python\n",
    "* Grundlagen Objektorientierte Programmierung\n",
    "* Grundlagen Dateien\n",
    "\n",
    "### <font color='blue'>**Notebooks, die helfen können**\n",
    "* Zusatznotebook \"Dateien\"\n",
    "\n",
    "### <font color='blue'>**Lernziele**\n",
    "* Wiederholung OOP\n",
    "* Umsetzung mathematischer Abläufe in Algorithmen\n",
    "* Lesen und Schreiben von hierarchischen Strukturen (OOP) in Dateien"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "88fd3cb5",
   "metadata": {},
   "source": [
    "## <font color='blue'>**Problemstellung 1: Primfaktorzerlegung**\n",
    "\n",
    "<font color='blue'>**Aufgabenstellung**\n",
    "     \n",
    "Es soll ein objektorientiertes Programm erstellt werden, mit dem sich die Primfaktorzerlegung von beliebigen Ganzzahlen berechnen lässt. Die bereits berechneten Zerlegungen sollen gespeichert werden. \n",
    "\n",
    "<font color='blue'>**Vorüberlegung**\n",
    "    \n",
    "*Hintergrund Primfaktorzerlegung*\n",
    "\n",
    "Jede Ganzzahl größer $2$ lässt sich in Faktoren aus Primzahlen zerlegen. $40$ setzt sich zum Beispiel aus $2^3\\cdot5$ zusammen, $100$ aus $2\\cdot2\\cdot5\\cdot5 = 2^2\\cdot5^2$. Für jede Zahl gibt es exakt eine Primfaktorzerlegung. Die Primfaktorzerlegung wird z.B. genutzt, wenn der größte gemeinsame Teiler (ggT) oder das kleinste gemeinsame Vielfache (kgV) zweier Zahlen gesucht wird.\n",
    "    \n",
    "Den ggT benötigt man z.B., um einen Bruch zu kürzen. Man erhält den ggT, indem man die gemeinsamen Primfaktoren multipliziert. Bei $40 = 2^3\\cdot5^1$ und $100 = 2^2\\cdot5^2$ ist das $2^2\\cdot5^1 = 20$. Wir können also den Bruch $\\frac{40}{100}$ mit $20$ kürzen: $\\frac{2}{5}$.\n",
    "    \n",
    "Das kgV benötigt man zum Beispiel zum Addieren von Brüchen mit unterschiedlichem Nenner. Die Brüche können nur addiert werden, wenn sie auf einen gemeinsamen Nenner (von denen das kgV die kleinste Möglichkeit ist) gebracht werden. Das kgV erhält man, indem alle auftretenden Primfaktoren in ihrer Höchsten Potenz multipliziert werden. Bei den Beispielzahlen 40 und 100 entspricht das $2^3\\cdot5^2 = 200$. 40 müsste also mit 50, 100 mit 2 erweitert werden.\n",
    "    \n",
    "*Berechnung Primfaktorzerlegung*\n",
    "\n",
    "Die Primfaktorzerlegung kann berechnet werden, indem wir die Zahl durch die kleinste Primzahl teilen, bei der die Division ganzzahlig aufgeht. Diese Primzahl ist ein Primfaktor. Mit dem Ergebnis der Division wird wieder Verfahren, wie mit der Zahl selbst. Wenn die verbleibende Zahl selbst eine Primzahl ist (und dann als letzter Faktor eingeht), bzw. nur durch sich selbst geteilt werden kann und 1 ergibt, sind alle Primfaktoren gefunden. Ausführliches Bsp:\n",
    "    \n",
    "$100 : 2 = 50$. Neuer Primfaktor 2, neue Zahl 50, Primfaktoren: $2^1$\\\n",
    "$50 : 2 = 25$. Neuer Primfaktor 2, neue Zahl 25, Primfaktoren: $2^2$\\\n",
    "$25 : 2 = $ (keine Ganzzahl), nächsthöhere Primzahl testen\\\n",
    "$25 : 3$ -> (keine Ganzzahl), nächsthöhere Primzahl testen\\\n",
    "$25 : 5 = 5$ Neuer Primfaktor 5, neue Zahl 5, Primfaktoren: $2^2\\cdot5^1$\\\n",
    "5 ist selbst Primzahl. Primfaktoren: $2^2\\cdot5^2$\n",
    "    \n",
    "*Programm*\n",
    "    \n",
    "Diesen Algorithmus wollen wir gleich in einem Programm umsetzen. Wir benötigen dafür eine Liste mit allen Primzahlen, die als Primfaktor in Frage kommen (also alle Primzahlen bis zur Zahl selbst, da sie im Extremfall, wenn sie selbst Primzahl ist, der einzige Primfaktor ist). Darum kümmern wir uns später und nehmen die Liste nun erstmal als gegeben an.\n",
    "    \n",
    "Wir wollen die Primfaktorzerlegung als Liste von Zahlen (den Primfaktoren) ausgeben. Eine Anforderung aus der Problemstellung ist, dass bereits berechnete Zerlegungen gespeichert werden sollen. Hierfür bietet sich ein Dictionary an. Als Key verwenden wir die Zahl, die zerlegt wird, und als Wert speichern wir die Liste mit der Primfaktorzerlegung.\n",
    "    \n",
    "Das Programm soll objektorient sein, um die Daten zu strukturieren. Wir stellen eine Klasse als Werkzeug für die Primfaktorzerlegung zusammen, die folgende Elemente hat:\n",
    "\n",
    "````class Primfaktor_Werkzeug````\n",
    "    \n",
    "* Attribute:\n",
    "    * Eine Liste mit lückenlos aufsteigenden Primzahlen bis zur aktuellen Grenze ````prim_liste````\n",
    "    * Ein Dictionary mit allen bisher berechneten Zerlegungen in der Form {Zahl:Liste_mit_Primfaktoren} ````zerlegungen````\n",
    "* Der Kern ist eine Methode, die:\n",
    "    * die Primfaktorzerlegung für eine gegebene Zahl berechnet, ausgibt, und in das Dictionary einträgt ````zerlegung(zahl)````\n",
    "* Hilfsmethoden sind:\n",
    "    * Der Konstruktor ````__init__()````\n",
    "    * get-Methoden für die Attribute, um außerhalb der Klasse auf Kopien der Attribute zuzugreifen ````get_prim_liste()````,````get_zerlegungen()````\n",
    "   \n",
    "*Primzahl-Liste*\n",
    "    \n",
    "Wir benötigen eine Liste mit allen Primzahlen bis zur aktuell angefragten Zahl (s.o.). Wenn wir diese manuell eingeben, könnte es immer passieren, dass die Liste nicht weit genug geht. Es ist also besser, diese Liste vom Programm bis zur aktuell benötigten höchsten Zahl erstellen zu lassen. Wir könnten diese Funktionalität zwar auch in die Klasse ````Primfaktor_Werkzeug```` integrieren, aber eigentlich geht es in der Klasse um Primfaktorzerlegungen, und nicht um Primzahlen selbt. In der OOP versucht man immer, den Umfang der Klassen so klein wie möglich zu halten, sodass sie nur eine Kernaufgabe haben. Das steigert die Wiederverwendbarkeit und Wartbarkeit. Eine Liste von Primzahlen könnte auch in diversen anderen Programmen nützlich sein, sodass wir eine neue Klasse ````Primzahl_Liste```` anlegen. Im Kern hat diese Klasse eine Liste mit Zahlen als Attribut. Wir statten sie jedoch mit Methoden aus, die dafür sorgen, dass es sich um eine erweiterbare, aufsteigende Liste mit Primzahlen handelt. Die Primzahl-Liste in der ````Primfaktor_Werkzeug```` Klasse wird dann ein Objekt der Klasse ````Primzahl_Liste```` sein.\n",
    "    \n",
    "Unsere neue Klasse ````Primzahl_Liste```` erhält folgende Elemente:\n",
    "* Attribute:\n",
    "    * Eine Liste mit lückenlos aufsteigenden Primzahlen bis zur aktuellen Grenze ````primzahlen````\n",
    "* Methoden:\n",
    "    * Die Kernmethode, die die Liste lückenlos bis zu einer gegebenen Zahl erweitert ````liste_erweitern_bis(zahl)````\n",
    "    * Den Konstruktor ````__init__()````\n",
    "    * Eine get-Methode, um außerhalb der Klasse auf eine Kopie der Liste zuzugreifen ````get_primzahlen()````\n",
    "    * Zwei interne Methode, die prüfen, ob eine Zahl eine Primzahl ist ````_ist_primzahl(zahl)````, und ob eine Zahl durch eine andere teilbar ist ````_ist_teilbar(zahl, teiler)```` (das ist nicht zwingend nötig, allerdings sorgt das Auslagern dieser Funktionen für deutlich verständlicheren Code)\n",
    "    \n",
    "Bevor wir mit der Implementierung beginnen, noch kurz die Überlegung zur Erweiterung der Liste:\n",
    "\n",
    "Wenn die Liste bis zu einer übergebenen Zahl erweitert werden soll, sollten wir zunächst prüfen, ob die Liste bereits groß genug ist. Wenn nicht, müssen wir jede Zahl zwischen dem Ende der Liste und dem Ziel der Erweiterung darauf testen, ob sie eine Primzahl ist. Falls ja, wird sie der Liste angehängt. Falls nicht, wird die nächste Zahl getestet.\n",
    "\n",
    "Ob die Zahl eine Primzahl ist, finden wir heraus, indem wir die Zahl mit allen bekannten Primzahlen aus der Liste testen. Ist sie durch keine der Primzahlen ganzzahlig teilbar, ist sie selbst eine Primzahl. Sonst nicht.\n",
    "    \n",
    "Ob eine Zahl durch einen Teiler ganzzahlig teilbar ist, finden wir heraus, indem wir die Zahl mit dem Modulo-Operator (% - Division mit Rest) und dem Teiler testen. Kommt bei der Division Rest 0 heraus, ist die Zahl teilbar, ansonsten nicht.\n",
    "    \n",
    "Anhand dieser Beschreibung lässt sich vielleicht schon nachvollziehen, warum es sinnvoll ist, das Testen, ob eine Zahl eine Primzahl ist, und ob eine Zahl durch eine andere Zahl ganzzahlig teilbar ist, auszulagern. So bleibt jede Methode kompakt beschreibbar und übersichtlich."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8619775b",
   "metadata": {},
   "source": [
    "<font color='blue'>**Umsetzung**\n",
    "    \n",
    "Wir beginnen mit der Klasse ````Primzahl_Liste````, da wir diese dann später für die Zerlegung bereits verwenden können. Wir werden die Klasse zunächst implementieren und für sich testen, damit wir sicher sein können, dass dieser Baustein funktioniert und im weiteren Verlauf verwendet werden kann. Aufgrund des kompakten Codes mit relativ aussagekräftigen Namen, und der Erklärung in dieser Vorbesprechung ist relativ wenig im Code selbst kommentiert.\n",
    "    \n",
    "**Generell sollte Code, bzw. die Namen, so aussagekräftig sein und fein aufgegliedert sein, dass man wenig Kommentare benötigt.**\n",
    "    \n",
    "*Konstruktor*\n",
    "    \n",
    "Es wäre sinnvoll, dem Konstruktor bereits eine Zahl, bis zu der die Liste erstellt werden soll, zu übergeben. Der Konstruktor kann auch die klasseneigene Methode zum Erweitern der Liste aufrufen. Dazu braucht es nur eine Startliste mit den ersten Primzahlen. Diese Liste könnte ````[2,]```` sein, oder ````[2,3]````. Die 3 wird eigentlich bereits automatisch von unserem Algorithmus für das Erweitern gefunden, aber nach meinem persönlichen Geschmack sieht die Liste mit nur einem Element weniger vollständig aus, als mit zwei Elementen. Daher entscheide ich mich für ````[2,3]````.\n",
    "    \n",
    "*Liste erweitern bis*\n",
    "    \n",
    "Diese Methode benötigt auch die Zahl, bis zu der erweitert werden soll. Als erstes benötigen wir den Startwert. Dieser ist der letzte Eintrag in der aktuellen Primzahlliste. Falls der Zielwert kleiner ist als der Startwert, müssen wir gar nichts mehr machen. Die Liste ist dann ja bereits lang genug. Ansonsten können wir mit dem Erweitern beginnen. Dazu gehen wir in einer Schleife jede Zahl vom Startwert bis zum Endwert durch (bei Verwendung von ````range()```` müssen wir dran denken, dass die obere Grenze exklusiv ist - den Wert also nicht mehr verwendet). Wir nutzen die interne Funktion ````_ist_primzahl(zahl)````, um zu prüfen, ob die Zahl eine Primzahlen ist. Ist sie das, hängen wir sie an die Primzahlliste an. (Falls sie keine ist, müssen wir ncihts weiter unternehmen. Es wird dann direkt der nächsten Schleifendurchlauf mit der nächsten Zahl begonnen).\n",
    "\n",
    "*_ist_primzahl*\n",
    "    \n",
    "Diese ausgelagerte Methode testet, ob eine Zahl eine Primzahl ist. Dazu benötigen wir eine Schleife über alle bekannten Primzahlen, und testen, ob die Zahl ganzzahlig durch eine dieser Primzahlen teilbar ist. Finden wir einen möglichen teilbar, können wir die Funktion bereits mit ````False```` beenden. Wurden alle Primzahlen getestet, ohne dass ein Teiler gefunden wurde, geben wir ````True```` zurück.\n",
    "    \n",
    "*_ist_teilbar*\n",
    "    \n",
    "Diese Methode teilt die Zahl mittels Ganzzahldivision mit Rest (Modulo, Operator %) durch den Teiler. Gibt es keinen Rest (Rest 0), so ist die Zahl teilbar und wir geben ````True```` zurück. Ansonsten ````False````. Da der Ausdruck in der Abfrage bereits den Wahrheitswert als Ergebnis hat, können wir diesen auch direkt zurückgeben, ganz ohne if-Abfrage. (````return ausdruck```` statt ````if ausdruck: return true    else: return false```` )\n",
    "\n",
    "Das ist ein gutes Beispiel, wie das feine Aufgliedern in kleine Funktionen die Übersichtlichkeit erhöhen kann. Dadurch, dass die Funktion den Namen ````_ist_teilbar```` erhält, können wir sie in der Primzahlprüfung nutzen, und es ist sofort ersichtlich, was in der Zeile passiert (````if self._ist_teilbar(zahl, teiler):````). Die gleiche Zeile könnten wir ohne die Funktion schreiben als (````if zahl%teiler == 0:````). Das hat zwar weniger Zeichen, aber es ist viel schlechter ersichtlich, dass hier eine Zahl auf Teilbarkeit geprüft wird. Dazu müsste man nämlich erstmal nachvollziehen, was bei der Modulo-Operation passiert, und was es bedeutet, wenn dabei 0 herauskommt. \n",
    "    \n",
    "*get_primzahlen*\n",
    "\n",
    "Die Methode gibt eine Kopie der Liste zurück. Dafür kann ein Slicing über alle Elemente genutzt werden. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16ecfaad",
   "metadata": {},
   "outputs": [],
   "source": [
    "'?' '?':\n",
    "    \n",
    "    def __init__(self, '?'):\n",
    "        self.primzahlen = '?'\n",
    "        self.'?'\n",
    "    \n",
    "    def liste_erweitern_bis'?':\n",
    "        start = '?'\n",
    "        if '?':\n",
    "            for zahl in range('?'):\n",
    "                if '?'('?'):\n",
    "                    '?'.'?'.'?'(zahl)\n",
    "                    \n",
    "    def _ist_primzahl'?':\n",
    "        for '?':\n",
    "            if '?':\n",
    "                return '?' #In welche Ebene muss das andere return?\n",
    "        \n",
    "    \n",
    "    def _ist_teilbar'?':\n",
    "        return '?'\n",
    "                                 \n",
    "    def get_primzahlen('?'):\n",
    "        '?'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0622bb9",
   "metadata": {},
   "source": [
    "Wir testen die neue Klasse:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff4be127",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"Test der Klasse Primzahl_Liste:\")\n",
    "\n",
    "# Erstellt die Primzahlen bis 30 als Liste\n",
    "primes = '?'\n",
    "print(primes.'?')\n",
    "\n",
    "# Erweitert bis 200\n",
    "primes.'?'\n",
    "print('?')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0c48eea",
   "metadata": {},
   "source": [
    "Nachdem wir nun eine funktionierende Primzahl_Liste haben, können wir uns dem eigentlichen Ziel der Aufgabe widmen, der Primfaktorzerlegung.\n",
    "\n",
    "Wir haben die Grundstruktur der Klasse bereits entworfen und überlegen nun die Details der Implementierung.\n",
    "\n",
    "*Konstruktor*\n",
    "\n",
    "Die erste Frage ist, ob wir der ````__init__()````-Methode Parameter übergeben müssen. Wir könnten zwar eine erste Zahl zur Zerlegung übergeben, müssen aber nicht. Wir werden daher in dieser Klasse auf Parameter bei der Objekterstellung (Instanziierung) verzichten. Die init Methode legt die beiden Attribute an: eine Primzahlliste, die wir zum Beispiel mit 5 initialisieren und ein leeres Dictionary, in dem wir später Ergebnisse ablegen können.\n",
    "\n",
    "*zerlegung*\n",
    "\n",
    "Das ist die eigentliche Kernmethode. Sie bekommt die zu zerlegende Zahl übergeben. Wir müssen zunächst überprüfen, ob die eingegebene Zahl kleiner als 2 ist, da dann keine Primfaktorzerlegung gemacht werden kann. Wir könnten einen Fehler erzeugen (das haben wir aber bisher nicht in Übungen behandelt), belassen es aber hier dabei, eine 1 zurückzugeben.\n",
    "\n",
    "Dann müssen wir sicherstellen, dass die Primzahlliste weit genug reicht. Dazu erweitern wir sie (mit ihrer Methode) bis zur eingegebenen Zahl.\n",
    "\n",
    "Da wir im folgenden Algorithmus die Zahl verändern, speichern wir die Zahl als ````save_key```` ab, damit wir später beim beschreiben des Dictionaries noch wissen, welche Zahl zerlegt wurde.\n",
    "\n",
    "Wir benötigen eine leere Liste, die wir mit Primfaktoren füllen.\n",
    "\n",
    "Nach diesen Vorbereitungen beginnt die eigentliche Berechnung nach dem Schema, das in der Vorüberlegung erklärt wurde. Dabei teilen wir die Zahl immer so oft durch eine Primzahl, wie möglich und gehen dann zur nächsthöheren, bis wir alle Primzahlen getestet haben. Programmtechnisch setzen wir das so um, dass wir als erstes eine Schleife über alle Primzahlen der Primzahlliste starten. Nun prüfen wir, ob die eingegebeneZahl ganzzahlig durch ide Primzahl teilbar ist. Hierbei müssen wir bedenken, dass wir noch nicht wissen, wie oft die Zahl teilbar ist. Würden wir eine if-Abfrage verwenden, könnten wir die Zahl nur ein Mal teilen (oder sooft, wieviele if Abfragen wir aneinander reihen). Daher ist das ein idealer Anwendungsfall für eine While-Schleife. Nur wenn die Bedingung erfüllt ist, wird der Inhalt abgearbeitet (wie bei if), aber danach wird geprüft, ob die Bedingung immer noch gültig ist, und dann gegebenfalls wiederholt. Analog zur Primzahlliste lagern wir die Methode ````_ist_teilbar```` aus, um den Quellcode verständlich zu halten. In der Schleife teilen wir die Zahl durch die Primzahl und nutzen dafür den kombinierten Zuweisungsoperator ````/=````. Außerdem fügen wir die Primzahl der Liste von Primfaktoren hinzu.\n",
    "\n",
    "Diese Schleife führt bereits die gesamte Primzahlzerlegung durch. Am Ende verbleibt die Zahl bei 1. 1 kann gar nicht ganzzahlig geteilt werden und so werden zwar alle Primzahlen, die noch nicht dran waren getestet, aber es passiert nichts weiter. Man könnte noch überlegen, Abbruchbedingungen einzubauen, um aufzuhören, wenn die Zahl bei 1 angekommen ist. Wir verzichten hier allerdings darauf.\n",
    "\n",
    "Zuletzt soll das Ergebnis in dem Dictionary gespeichert werden. Dazu legen wir einen Eintrag an und verwenden als Key die zwischengespeicherte Zahl und als Wert die Liste der Primfaktoren. Diese Liste geben wir auch mit return zurück.\n",
    "\n",
    "*get_zerlegungen*\n",
    "\n",
    "Diese Methode gibt eine Kopie des Dictionaries zurück. Wir verwenden die .copy() Methode.\n",
    "\n",
    "*get_prim_liste*\n",
    "\n",
    "Diese Methode gibt die Primzahlliste zurück. Wir nutzen dazu die entsprechende Methode der Primzahlliste und geben das Ergebnis lediglich weiter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f51d4e45",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Primfaktor_Werkzeug:\n",
    "    def __init__(self):\n",
    "        self.prim_liste = '?'\n",
    "        self.zerlegungen = '?'\n",
    "    \n",
    "    # Alle Schritte sind im Beschreibungstext oben angesprochen\n",
    "    def zerlegung('?'):\n",
    "        if '?':\n",
    "            return 1\n",
    "        \n",
    "        self.prim_liste.'?'\n",
    "        save_key = '?'\n",
    "        \n",
    "        primfaktoren = '?'\n",
    "        \n",
    "        for '?' in '?':\n",
    "            while self.'?'('?', '?'):\n",
    "                '?'\n",
    "                primfaktoren.'?'\n",
    "        \n",
    "        self.zerlegungen['?']='?'\n",
    "        return '?'\n",
    "    \n",
    "    def _ist_teilbar'?':\n",
    "        '?'\n",
    "        \n",
    "    def get_zerlegungen('?'):\n",
    "        '?'\n",
    "    def get_prim_liste('?'):\n",
    "        '?'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55a4f204",
   "metadata": {},
   "source": [
    "Auch diese Klasse testen wir. Zunächst mit den beiden Beispielen aus der Vorüberlegung (40 und 100). Anschließend verwenden wir eine Schleife, um alle Zerlegungen bis z.B. 20 zu ermitteln und lassen uns das Ergebnis Dictionary ausgeben."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23a78c3f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test der Klasse Primfaktorzerlegung\n",
    "pf_werkzeug = '?'\n",
    "print(pf_werkzeug.zerlegung('?'))\n",
    "print('?')\n",
    "\n",
    "# Alle Zerlegungen bis einschliesslich 20\n",
    "for '?' in '?'\n",
    "    pf_werkzeug.'?'\n",
    "\n",
    "# Ausgabe der gespeicherten Zerlegungen\n",
    "print('?')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c15b337",
   "metadata": {},
   "source": [
    "## <font color='blue'>**Problemstellung 2: Speichern in Dateiformaten**\n",
    "\n",
    "<font color='blue'>**Aufgabenstellung**\n",
    "     \n",
    "Damit andere Programme die berechneten Primzahlen und Primfaktorzerlegungen nutzen können, sowie neue Primfaktor_Werkzeug Objekte ohne Berechnung wieder mit den Daten gefüllt werden können, soll eine Datei- Ein- und Ausgabe vorbereitet werden.\n",
    "* Es soll eine Methode implementiert werden, mit der die Daten eines Primfaktor_Werkzeug Objekts in einer Zeichenkette formatiert werden (die man dann in eine Datei schreiben könnte). Das Format ist frei wählbar, soll aber Menschenlesbar und verständlich sein.\n",
    "* Ebenso wird eine Methode benötigt, die ein neues Primfaktor_Werkzeug Objekt erstellt und mit den Daten aus der Zeichenkette füllt.\n",
    "* Zur einfacheren Übertragung innerhalb Pythons soll eine Speicherung mittels ````pickle```` (Binärdatei) ermöglicht werden.\n",
    "\n",
    "<font color='blue'>**Vorüberlegung**\n",
    "    \n",
    "Für den ersten Teil der Aufgabe wollen wir eins der im Grundlagennotebook vorgestellten Standardformate nutzen und stellen die Möglichkeiten gegenüber. Wir haben eine leicht hierarchische Datenstruktur: Auf der ersten Ebene eine Liste mit Primzahlen und ein Dictionary mit den Primzahlzerlegungen. Auf der zweiten Ebene haben wir die einzelnen Primzahlzerlegungen als Listen mit beliebiger Länge.\n",
    "    \n",
    "Die Daten sind daher nicht sehr geeignet für **csv**, wobei man auch für **csv** Lösungen finden kann.    \n",
    "*(Für Interessierte: z.B. eine Tabelle mit Spalten für alle Primzahlen der Primzahl-Liste und Zeilen für die einzelnen Primfaktorzerlegungen nach Zahl. In der Zellen könnte dann für den entsprechenden Primfaktor der Exponent eingetragen werden. Eine solche Tabelle würde auch alle Daten enthalten, die Umwandlung in das Format wäre aber etwas aufwendiger, als wir es hier in der Augabe machen wollen)*\n",
    "\n",
    "Wir können nun also zwischen **json** und **xml** wählen. Beides würde funktionieren und die Anforderungen erfüllen. Wir entscheiden uns hier für **json**, da es etwas einfacher aufzubauen ist.\n",
    "    \n",
    "Wir werden dazu eine Funktion definieren, die ein Objekt der Klasse Primfaktor_Werkzeug erhält. Sie legt dann ein Dictionary mit den Einträgen für die Primzahlliste und die Primzahlzerlegungen an. Als Wert für die Primzahlzerlegungen wird wieder ein Dictionary verwendet. Wir können dieses direkt aus dem Objekt kopieren. und müssen es nicht weiter umformatieren. Zuletzt nutzen wir das ````json```` Modul, um das Gesamt-Dictionary nach json zu formatieren.\n",
    "    \n",
    "Für das Einlesen definieren wir eine weitere Funktion, die eine Zeichenkette im json-Format erhalten soll, in der die zuvor definierte Dictionary-Struktur enthalten ist. Die Funktion erstellt ein neues Primzahl_Werkzeug Objekt und greift direkt auf die Attribute zu, um die in der json-Zeichenkette enthaltenen Informationen einzutragen, ohne dass etwas berechnet werden muss.\n",
    "\n",
    "Achtung: Wir greifen hier direkt auf die Attribute zu. Das ist ursprünglich in der Idee der Datenkapselung bei Objektorientierter Programmierung nicht vorgesehen. Unsere Klasse ist so aufgebaut, dass in der Primzahlliste wirklich nur Primzahlen lückenlos und in aufsteigender Reihenfolge stehen können (da sie selbst berechnet werden). Die Primfaktorzerlegung ist darauf angewiesen, dass die Liste genau diese Bedingungen erfüllt. Ansonsten liefert sie falsche Ergebnisse. Mit dem direkten Zugriff auf die Primzahlliste könnten wir aber theoretisch eintragen, was wir wollen. Das sollte man beachten.\n",
    "    \n",
    "Der letzte Teil der Aufgabenstellung ist simpel zu lösen. Das Modul ````pickle```` übernimmt fast alle Schritte. Wir müssen nur eine Datei öffnen und mit ````pickle.dump()```` das Objekt darin speichern, bzw. mit ````pickle.load()```` wieder laden. Wir verpacken diese Funktionalität auch in Funktionen."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "70023d64",
   "metadata": {},
   "source": [
    "<font color='blue'>**Umsetzung**\n",
    "     \n",
    "Wir beginnen mit der Funktion ````pfw_objetct_to_json_string()````. Sie bekommt ein Objekt der Klasse Primfaktor_Werkzeug. Wir legen ein Dictionary ````json_dict```` an und tragen zunächst unter einem Key \"Primzahlen\" die Primzahlliste ein, die wir über die Methoden des Objekts erhalten. Anschließend legen wir einen weiteren Key \"gespeicherte Zerlegungen\" an und tragen analog das Dictionary der Zerlegungen ein, wie es vom Objekt ausgegeben wird. Damit ist das Dateiformat vorbereitet und wir können im letzten Schritt mit ````json.dumps()```` die Datenstruktur in das json Format umwandeln.\n",
    "    \n",
    "Wir testen es durch vollständige Ausgabe des Json-Strings, der aus dem oben getesteten Objekt erzeugt wird."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f11a208",
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "import json\n",
    "\n",
    "def pfw_object_to_json_string('?'):\n",
    "    json_dict = {'?': '?'}\n",
    "    json_dict['?'] = '?'\n",
    "    return json.'?'('?', indent=4)\n",
    "\n",
    "#Test\n",
    "print('?'('?'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e954b334",
   "metadata": {},
   "source": [
    "Als nächstes das Einlesen: ````json_string_to_pfw_object````. Hier gehen wir sozusagen rückwärts vor. Die Methode erhält die Zeichenkette. Wir erstellen ein Objekt der Klasse Primfaktor_Werkzeug. Wir nutzen ````json.loads()```` um die Zeichenkette in ein Python Dictionary umzuwandeln. Dann greifen wir direkt auf die Attribute des neuen Objekts zu und tragen mittels der oben definierten Keys die entsprechenden Inhalte ein. Dabei müssen wir Besonderheit beachten: Die Dictionaries in json können als Key nur Zeichen haben. Wir verwenden aber eigentlich Ganzzahlen. Statt also \"gespeicherte Zerlegungen\" sofort in das Attribut \"zerlegungen\" einzutragen, iterieren wir über alle Keys in \"gespeicherte Zerlegungen\" und legen im Attribut \"zerlegungen\" jeden Eintrag einzeln an, indem wir den Key zu int konvertieren und dann verwenden, und als Wert den Inhalt aus \"gespeicherte Zerlegungen\" am entsprechenden Key eintragen. Zum Schluss wird das neue Objekt zurückgegeben.\n",
    "\n",
    "Wir testen es, indem wir zunächst das oben getestete Objekt zu json konvertieren, und dann ein neues Objekt mit den Daten einlesen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cee9f298",
   "metadata": {},
   "outputs": [],
   "source": [
    "def json_string_to_pfw_object('?'):\n",
    "    pfw_object = '?'\n",
    "    json_dict = json.'?'('?')\n",
    "    pfw_object.prim_liste = '?'\n",
    "    \n",
    "    # Keys in json sind immer str. Wir benoetigen aber int. Deswegen legen wir die Antraege in der Schleife einzeln an, anstatt das Attribut analog zur Liste direkt zu ueberschreiben.\n",
    "    for '?' in json_dict['?']:\n",
    "        pfw_object.'?'['?'('?')] = json_dict['?']['?']\n",
    "        \n",
    "    return '?'\n",
    "\n",
    "#Test\n",
    "save_string = pfw_object_to_json_string(pf_werkzeug)\n",
    "imported_pfz = json_string_to_pfw_object(save_string)\n",
    "\n",
    "print(imported_pfz.zerlegungen[20])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bfe4321e",
   "metadata": {},
   "source": [
    "Zuletzt ````pickle````, mit dem wir recht einfach ein Objekt zur späteren Verwendung in Python in einer Binärdatei ablegen können. Allerdings darf sich nicht die Python Version ändern und die Klassendefinition muss beim Laden der Pickledatei bekannt und unverändert sein.\n",
    "\n",
    "Wir erstellen zwei Funktionen. Die Funktion zum Speichern erhält ein Objekt und einen Dateinamen. Die Funktion zum Laden benötigt nur einen Dateinamen. In der Funktion öffnen wir wie im Grundlagennotebook die Datei im entsprechenden Modus (````wb````,````rb````) und nutzen ````dump```` oder ````load```` um das Objekt in der Datei abzulegen, oder auszulesen. Wir testen das analog zu json:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "215c715b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pickle\n",
    "\n",
    "def pfw_object_to_pickle(pfw_object, filename):\n",
    "    with '?':\n",
    "        pickle.'?'('?')\n",
    "        \n",
    "def pfw_object_from_pickle('?'):\n",
    "    '?':\n",
    "        return '?'\n",
    "    \n",
    "#Test:\n",
    "pfw_object_to_pickle(pf_werkzeug, \"Primfaktoren.pkl\")\n",
    "object_from_pickle = pfw_object_from_pickle(\"Primfaktoren.pkl\")\n",
    "\n",
    "print(object_from_pickle.zerlegungen[20])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e6aed72d",
   "metadata": {},
   "source": [
    "## <font color='blue'> **Schlussbemerkung**\n",
    "    \n",
    "In dieser Übung haben wir noch einmal für eine neue Problemstellung eine Klassenstruktur entwickelt und mathematische Berechnungsvorschriften aus einer Beschreibung als Algorithmus in Code umgesetzt. Wir haben versucht, den Code so nachvollziehbar wie möglich zu gestalten, indem wir nicht viele Schleifen und Abfragen verschachtelt haben, sondern die Berechnung in kleinere, abgetrennte Bausteine mit aussagekräftigem Namen zerlegt haben. Anschließend haben wir diese hierarchische Struktur in geeignete Dateiformate für verschiedene Zwecke überführt.\n",
    "    \n",
    "Das schließt die Inhalte des ersten Semesters ab. Das Ziel war, möglichst anwendungsorientiert die Herangehensweise an die Erstellung von Skripten näherzubringen und diverse nützliche Pakete vorzustellen und zu sehen, wie man diese einsetzen kann. Alle Pakete bieten mehr, als wir im Rahmen der Übung zeigen konnten. Wir sind sicher, dass euch diese Inhalte oder Teile davon noch auf die ein- oder andere Weise nützlich sein können. Ihr habt nun viel gesehen, und könnt viele der Begriffe einordnen, die euch noch/wieder begegnen werden.\n",
    "\n",
    "## <font color='blue'> **Ausblick**    \n",
    "    \n",
    "In der letzten Übung beschäftigen wir uns nach dem Testat mit einem kleinen Extra: Einer einfachen 3D-Animation. Das entsprechende Python-Paket (vpython) macht sehr viel selbst, sodass es deutlich einfacher zu benutzen ist, als man vielleicht bei dem Begriff 3D_Animation denken würde. \n",
    "\n",
    "## <font color='blue'> **Aufgaben zum selbst probieren**\n",
    "    \n",
    "* Erstelle Methoden für die Primfaktor_Werkzeug-Klasse, die den größten gemeinsamen Teiler (ggT) und das kleinste gemeinsame Vielfache (kgV) zweier Zahlen ausgeben soll. In den Methoden müssen für beide Zahlen die Primfaktorzerlegungen gemacht, verglichen und ausgewertet werden. Die Berechnung von ggT und kgV ist in der Vorüberlegung von Problemstellung 1 beschrieben.\n",
    "* Die Berechnung der Primzahlzerlegung ist relativ simpel implementiert, aber nicht sehr rechenschritt-effizient (unnötige Berechnungen). Versuche, die Effizienz der Berechnung zu verbessern. Anhaltspunkte hierfür sind: Müssen wir wirklich bei dem Test, ob eine Zahl eine Primzahl ist, alle Primzahlen testen? Können wir die bereits abgespeicherten Zerlegungen oder Primzahlen zu unserem Vorteil nutzen und uns Rechenschritte sparen? Nutze die in Übung 9 gezeigte Methode, um die Rechenzeiten zu bestimmen. (Hinweise: Hohe Zahlen zeigen die Unterschiede deutlicher, die Zeiten sind z.B. wegen der gespeicherten Primzahlliste auch immer abhängig von den zuvor berechneten Werten)\n",
    "* Erstelle eine Bruch-Klasse, die die entwickelten Methoden nutzt, um den Bruch zu Kürzen und Brüche zu addieren (und Subtrahieren). Nutze dazu die Magic-Methods (Vgl. Grundlagen OOP) und ergänze Multiplikation und Division.\n",
    "* Baue eine export und import Funktion für xml. Orientiere dich dabei an dem Vorgehen im \"Grundlagen Dateien\" Notebook, und dem Zusatznotebook \"Dateien\".\n",
    " \n",
    "Füge deiner Kopie des Notebooks beliebig viele Codezellen hinzu, um die Aufgaben zu lösen."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "83014351",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}