diff --git a/Uebung11/Uebung_11.ipynb b/Uebung11/Uebung_11.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..0bdf8d9c8d6bb8c71d3b103d747cfd2eff9202dd --- /dev/null +++ b/Uebung11/Uebung_11.ipynb @@ -0,0 +1,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 +} diff --git a/Uebung11/Uebung_11_LSG.ipynb b/Uebung11/Uebung_11_LSG.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..cf90c43f8c787e236903b92d8f1de562627eb41d --- /dev/null +++ b/Uebung11/Uebung_11_LSG.ipynb @@ -0,0 +1,655 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "6b8a8d99", + "metadata": {}, + "source": [ + "# <font color='blue'>**Übung 11 - Primfaktorzerlegung und Speichern von objektorientierten Datenstrukturen**" + ] + }, + { + "cell_type": "markdown", + "id": "297a4311", + "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": "71332cc2", + "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": "06c38e96", + "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": 1, + "id": "b31782a6", + "metadata": {}, + "outputs": [], + "source": [ + "class Primzahl_Liste:\n", + " \n", + " def __init__(self, ziel):\n", + " self.primzahlen = [2,3]\n", + " self.liste_erweitern_bis(ziel)\n", + " \n", + " def liste_erweitern_bis(self, ziel):\n", + " start = self.primzahlen[-1]\n", + " if ziel > start:\n", + " for zahl in range(start, ziel+1):\n", + " if self._ist_primzahl(zahl):\n", + " self.primzahlen.append(zahl)\n", + " \n", + " def _ist_primzahl(self, zahl):\n", + " for teiler in self.primzahlen:\n", + " if self._ist_teilbar(zahl, teiler):\n", + " return False\n", + " return True\n", + " \n", + " def _ist_teilbar(self, zahl, teiler):\n", + " return zahl%teiler == 0\n", + " \n", + " def get_primzahlen(self):\n", + " return self.primzahlen[:]" + ] + }, + { + "cell_type": "markdown", + "id": "2c7d1857", + "metadata": {}, + "source": [ + "Wir testen die neue Klasse:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "bc4c2eb5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test der Klasse Primzahl_Liste:\n", + "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]\n", + "[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199]\n" + ] + } + ], + "source": [ + "print(\"Test der Klasse Primzahl_Liste:\")\n", + "\n", + "# Erstellt die Primzahlen bis 30 als Liste\n", + "primes = Primzahl_Liste(30)\n", + "print(primes.get_primzahlen())\n", + "\n", + "# Erweitert bis 200\n", + "primes.liste_erweitern_bis(200)\n", + "print(primes.get_primzahlen())" + ] + }, + { + "cell_type": "markdown", + "id": "764ba627", + "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": 3, + "id": "56edc06e", + "metadata": {}, + "outputs": [], + "source": [ + "class Primfaktor_Werkzeug:\n", + " def __init__(self):\n", + " self.prim_liste = Primzahl_Liste(5)\n", + " self.zerlegungen = {}\n", + " \n", + " def zerlegung(self, zahl):\n", + " if zahl < 2:\n", + " return 1\n", + " \n", + " self.prim_liste.liste_erweitern_bis(zahl)\n", + " save_key = zahl\n", + " \n", + " primfaktoren = []\n", + " \n", + " for teiler_primzahl in self.get_prim_liste():\n", + " while self._ist_teilbar(zahl, teiler_primzahl):\n", + " zahl/=teiler_primzahl\n", + " primfaktoren.append(teiler_primzahl)\n", + " \n", + " self.zerlegungen[save_key]=primfaktoren[:]\n", + " return primfaktoren\n", + " \n", + " def _ist_teilbar(self, zahl, teiler):\n", + " return zahl%teiler == 0\n", + " \n", + " def get_zerlegungen(self):\n", + " return self.zerlegungen.copy()\n", + " def get_prim_liste(self):\n", + " return self.prim_liste.get_primzahlen()" + ] + }, + { + "cell_type": "markdown", + "id": "9a6f752c", + "metadata": {}, + "source": [ + "Auch diese Klasse testen wir. Zunächst mit den beiden Beispielen aus der Vorüberlegung. 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": 4, + "id": "7fc572b9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2, 2, 2, 5]\n", + "[2, 2, 5, 5]\n", + "{40: [2, 2, 2, 5], 100: [2, 2, 5, 5], 2: [2], 3: [3], 4: [2, 2], 5: [5], 6: [2, 3], 7: [7], 8: [2, 2, 2], 9: [3, 3], 10: [2, 5], 11: [11], 12: [2, 2, 3], 13: [13], 14: [2, 7], 15: [3, 5], 16: [2, 2, 2, 2], 17: [17], 18: [2, 3, 3], 19: [19], 20: [2, 2, 5]}\n" + ] + } + ], + "source": [ + "# Test der Klasse Primfaktorzerlegung\n", + "pf_werkzeug = Primfaktor_Werkzeug()\n", + "print(pf_werkzeug.zerlegung(40))\n", + "print(pf_werkzeug.zerlegung(100))\n", + "\n", + "# Alle Zerlegungen bis einschliesslich 20\n", + "for zahl in range(21):\n", + " pf_werkzeug.zerlegung(zahl)\n", + "\n", + "# Ausgabe der gespeicherten Zerlegungen\n", + "print(pf_werkzeug.get_zerlegungen())" + ] + }, + { + "cell_type": "markdown", + "id": "a5090804", + "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": "327071f5", + "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": 5, + "id": "e135839b", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"Primzahlen\": [\n", + " 2,\n", + " 3,\n", + " 5,\n", + " 7,\n", + " 11,\n", + " 13,\n", + " 17,\n", + " 19,\n", + " 23,\n", + " 29,\n", + " 31,\n", + " 37,\n", + " 41,\n", + " 43,\n", + " 47,\n", + " 53,\n", + " 59,\n", + " 61,\n", + " 67,\n", + " 71,\n", + " 73,\n", + " 79,\n", + " 83,\n", + " 89,\n", + " 97\n", + " ],\n", + " \"gespeicherte Zerlegungen\": {\n", + " \"40\": [\n", + " 2,\n", + " 2,\n", + " 2,\n", + " 5\n", + " ],\n", + " \"100\": [\n", + " 2,\n", + " 2,\n", + " 5,\n", + " 5\n", + " ],\n", + " \"2\": [\n", + " 2\n", + " ],\n", + " \"3\": [\n", + " 3\n", + " ],\n", + " \"4\": [\n", + " 2,\n", + " 2\n", + " ],\n", + " \"5\": [\n", + " 5\n", + " ],\n", + " \"6\": [\n", + " 2,\n", + " 3\n", + " ],\n", + " \"7\": [\n", + " 7\n", + " ],\n", + " \"8\": [\n", + " 2,\n", + " 2,\n", + " 2\n", + " ],\n", + " \"9\": [\n", + " 3,\n", + " 3\n", + " ],\n", + " \"10\": [\n", + " 2,\n", + " 5\n", + " ],\n", + " \"11\": [\n", + " 11\n", + " ],\n", + " \"12\": [\n", + " 2,\n", + " 2,\n", + " 3\n", + " ],\n", + " \"13\": [\n", + " 13\n", + " ],\n", + " \"14\": [\n", + " 2,\n", + " 7\n", + " ],\n", + " \"15\": [\n", + " 3,\n", + " 5\n", + " ],\n", + " \"16\": [\n", + " 2,\n", + " 2,\n", + " 2,\n", + " 2\n", + " ],\n", + " \"17\": [\n", + " 17\n", + " ],\n", + " \"18\": [\n", + " 2,\n", + " 3,\n", + " 3\n", + " ],\n", + " \"19\": [\n", + " 19\n", + " ],\n", + " \"20\": [\n", + " 2,\n", + " 2,\n", + " 5\n", + " ]\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "def pfw_object_to_json_string(pfw_object):\n", + " json_dict = {\"Primzahlen\": pfw_object.get_prim_liste()}\n", + " json_dict[\"gespeicherte Zerlegungen\"] = pfw_object.get_zerlegungen()\n", + " return json.dumps(json_dict, indent=4)\n", + " \n", + "print(pfw_object_to_json_string(pf_werkzeug))" + ] + }, + { + "cell_type": "markdown", + "id": "da5bfbeb", + "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": 6, + "id": "6ba1d95c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2, 2, 5]\n" + ] + } + ], + "source": [ + "def json_string_to_pfw_object(json_string):\n", + " pfw_object = Primfaktor_Werkzeug()\n", + " json_dict = json.loads(json_string)\n", + " pfw_object.prim_liste = json_dict[\"Primzahlen\"]\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 key in json_dict[\"gespeicherte Zerlegungen\"]:\n", + " pfw_object.zerlegungen[int(key)] = json_dict[\"gespeicherte Zerlegungen\"][key]\n", + " \n", + " return pfw_object\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": "8641deed", + "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": 7, + "id": "8c336583", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[2, 2, 5]\n" + ] + } + ], + "source": [ + "import pickle\n", + "\n", + "def pfw_object_to_pickle(pfw_object, filename):\n", + " with open(filename, \"wb\") as f:\n", + " pickle.dump(pfw_object, f)\n", + " \n", + "def pfw_object_from_pickle(filename):\n", + " with open(filename, \"rb\") as f:\n", + " return pickle.load(f)\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": "35dd9ca7", + "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": "7d710a72", + "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 +} diff --git a/Uebung11/Zusatz_Dateien.ipynb b/Uebung11/Zusatz_Dateien.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..0a009cdd301aef2d3b1768afa4d02470b6a49e2a --- /dev/null +++ b/Uebung11/Zusatz_Dateien.ipynb @@ -0,0 +1,510 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "049cfa99", + "metadata": {}, + "source": [ + "# <font color='blue'>**Zusatz - \"Dateien\"**</font>\n", + "\n", + "Im Grundlagennotebook zu Dateien wurde anhand von 2 Beispielen gezeigt, wie json und xml Dateien aussehen können. Anhand des kompakten ersten Beispiels wurde auch die Umsetzung in Python gezeigt. In diesem Zusatznotebook sind die Quellcodes zu finden, mit denen die Beispieldateien für das komplexere Beispiel (Bibliothek) erzeugt werden können. Dabei wird ein objektorientierter Ansatz genutzt. Wir verzichten hier auf das Abspeichern als Datei und beschränken uns darauf, die Zeichenketten zu erstellen, die dann noch in die Dateien geschrieben werden könnten.\n", + "\n", + "#### <font color='blue'>**Objektorientierte Struktur der Daten**</font>\n", + "\n", + "Die Datenstruktur ist hier noch mal als Grafik dargestellt:" + ] + }, + { + "attachments": { + "Hierarchisch_klein.PNG": { + "image/png": "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nWmwLuma28pjKV3DEphlElVp2vyNA0uBLcOxNfwDklfjQSq13aev1fRN7yR+JLDUsIeKj4rZW5lfNYnta6Mcf1yUMBwwmZ18qe8vguoGXz2KJWvghN5+GurY1Soy029qlF5t3CvOpzQw10ssjfiRC2OvbwPqVyL+9j0Bp+deKO7/AnoFFmajymbqAAABhWlDQ1BJQ0MgcHJvZmlsZQAAeJx9kT1Iw1AUhU9TpSIVBQuKdMhQnSyIijhKFYtgobQVWnUweekfNGlIUlwcBdeCgz+LVQcXZ10dXAVB8AfE1cVJ0UVKvC8ptIjxwuN9nHfP4b37AKFRYarZNQGommWk4jExm1sVA6/wYQgDCMMnMVNPpBcz8Kyve+qmuovyLO++P6tPyZsM8InEc0w3LOIN4plNS+e8TxxiJUkhPiceN+iCxI9cl11+41x0WOCZISOTmicOEYvFDpY7mJUMlXiaOKKoGuULWZcVzluc1UqNte7JXxjMaytprtMKI44lJJCECBk1lFGBhSjtGikmUnQe8/CPOP4kuWRylcHIsYAqVEiOH/wPfs/WLExNuknBGND9Ytsfo0BgF2jWbfv72LabJ4D/GbjS2v5qA5j9JL3e1iJHQP82cHHd1uQ94HIHGH7SJUNyJD8toVAA3s/om3LA4C3Qu+bOrXWO0wcgQ7NavgEODoGxImWve7y7p3Nu//a05vcDPoBykrN/tocAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAHdElNRQfnARcQHQzdx0RLAAAgAElEQVR42uzdd5wdVf3G8c/cXrbc7ZtstiQhBUIgBAidgFJDEWkKCthANAhiQ8UfRbGhYqGjKEhREARBigKCCAhI6BBKSNmW7fduuW3uzJzfH7sJoSchZTf7vF+vGMzubWfmzpxn5pzztYwxBhERERERkU3ApyYQEREREREFEBERERERUQARERERERFRABEREREREQUQERERERERBRAREREREVEAERERERERBRAREREREREFEBERERERGTsCagIRkbHNGOgdyKshNtWJ02+RKAqpIUREFEBERMYnx/U452/L2K0hrsbYyAquIZlx+PphU9QYIiIKICIi41ddcZAT9qlXQ2xk2bzLFfc1qyFERD4EzQERERFZSwajRhARUQAREREREREFEBERERER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" + } + }, + "cell_type": "markdown", + "id": "7fe5e474", + "metadata": {}, + "source": [ + "" + ] + }, + { + "cell_type": "markdown", + "id": "679d13fd", + "metadata": {}, + "source": [ + "Wir bauen nun erst eine simple objektorientierte Struktur auf. Wir erzeugen dazu Klassen für die jeweiligen Ebenen:\n", + "* Bibliothek\n", + "* Buch\n", + "* Zeitschrift\n", + "* Ausgabe\n", + "* Artikel\n", + "\n", + "Jede dieser Klassen hat als Attribute die Werte, die in der Darstellung angegeben sind. Ist das Attribut im Plural, z.B. Bücher in Bibliothek, wird es als Liste von Buch-Objekten gespeichert. Die ````__init__()````- Methoden stellen jeweils alle diese Werte ein. Wir verzichten aus Übersichtlichkeit hier auf jegliche Datenkapselung (get- und set- Methoden), die normalerweise im OOP-Kontext viel eingesetzt wird. Hier greifen wir stattdessen direkt auf die Attribute zu und nutzen die Klassen nur zur Strukturierung." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5b8f3668", + "metadata": {}, + "outputs": [], + "source": [ + "class Bibliothek:\n", + " def __init__(self, buecher, zeitschriften):\n", + " self.buecher = buecher\n", + " self.zeitschriften = zeitschriften\n", + "\n", + "class Buch:\n", + " def __init__(self, titel, autor, jahr):\n", + " self.titel = titel\n", + " self.autor = autor\n", + " self.jahr = jahr\n", + "\n", + "class Zeitschrift:\n", + " def __init__(self, titel, verlag, ausgaben):\n", + " self.titel = titel\n", + " self.verlag = verlag\n", + " self.ausgaben = ausgaben\n", + " \n", + "class Ausgabe:\n", + " def __init__(self, nr, artikel):\n", + " self.nr = nr\n", + " self.artikel = artikel\n", + " \n", + "class Artikel:\n", + " def __init__(self, titel, autor):\n", + " self.titel = titel\n", + " self.autor = autor" + ] + }, + { + "cell_type": "markdown", + "id": "265e27ca", + "metadata": {}, + "source": [ + "Nun sind die Klassen angelegt. Im nächsten Codeblock werden Objekte erstellt, die genau die Daten aus der Abbildung repräsentieren. Da wir beim Anlegen eines Objekts am besten schon die ganze Liste an Unterobjekten übergeben, legen wir die Objekte sozusagen \"von unten nach oben\" an. Wir beginnen also mit den Artikeln, damit wir sie direkt beim Erstellen einer Ausgabe einsetzen können usw. Natürlich könnte man die Listen aber auch nachträglich mit ````.append()```` erweitern. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "67cb0b96", + "metadata": {}, + "outputs": [], + "source": [ + "# Artikel\n", + "art_1 = Artikel(\"Editorial\", \"A. Top\")\n", + "art_2 = Artikel(\"Untersuchung von xy\", \"C. Schlau\")\n", + "\n", + "# Ausgaben\n", + "ausg_1 = Ausgabe(\"1/2023\",[art_1,art_2])\n", + "ausg_2 = Ausgabe(\"2/2023\",[])\n", + "\n", + "# Zeitschrift\n", + "zeitschr_1 = Zeitschrift(\"Die beste Zeitschrift\", \"TOPVerlag\", [ausg_1, ausg_2])\n", + "\n", + "# Buecher\n", + "buch_1 = Buch(\"Das erste Buch\", \"M. Muster\", 2013)\n", + "buch_2 = Buch(\"Ein Beispielbuch\", \"E. Beispiel\", 2022)\n", + "\n", + "#Bibliothek\n", + "OO_bib = Bibliothek([buch_1, buch_2], [zeitschr_1,])" + ] + }, + { + "cell_type": "markdown", + "id": "035fc658", + "metadata": {}, + "source": [ + "Nun sind die Daten objektorientiert im Programm innerhalb des Bibliothek-Objekts \"OO_bib\" vorhanden. Wir können uns beispielhaft Daten ausgeben lassen, wie z.B. die Nummer der ersten Ausgabe der ersten Zeitschrift:" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "2313f17e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1/2023\n" + ] + } + ], + "source": [ + "print(OO_bib.zeitschriften[0].ausgaben[0].nr)" + ] + }, + { + "cell_type": "markdown", + "id": "0bd2636d", + "metadata": {}, + "source": [ + "#### <font color='blue'>**Speichern als json**</font>\n", + "\n", + "Nun werden wir eine Funktion erstellen, die ein Bibliothek-Objekt bekommt und die Bibliothek zu einem json-string konvertiert. Wir benötigen also verschachtelte Listen und Dictionaries mit den Attribut-Namen und Werten. Das erreichen wir, indem wir alle Listen mit Objekten durchgehen und die Informationen in ein Dictionary schreiben. Dieses Dictionary fügen wir dann einer Liste hinzu. Die Liste kommt dann wiederum als Wert in das Dictionary des übergeordneten Objekts. Zum Beispiel gehen wir also die Liste mit Büchern durch und erstellen für jedes Buch ein Dictionary mit Titel, Autor und Jahr entsprechend der Objektattribute. Dieses Dictionary fügen wir einer Liste mit Büchern hinzu. Wenn die Liste mit den Daten aller Bücher gefüllt ist, wird sie in dem Dictionary der Bibliothek unter dem key \"Bücher\" als Wert eingespeichert. Im Code sieht das so aus:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1a48d9e2", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "\n", + "def bibliothek_to_json(bib):\n", + " # vorbereiten der noetigen Listen\n", + " buecher_list = []\n", + " zeitschriften_list = []\n", + " \n", + " #Fuellen der Buecherliste. \n", + " for buch in bib.buecher:\n", + " buecher_list.append({\"Titel\": buch.titel, \"Autor\": buch.autor, \"Jahr\": buch.jahr})\n", + " \n", + " #Fuellen der zeitschriftenliste\n", + " for zeitschrift in bib.zeitschriften:\n", + " \n", + " # Analog dazu Ausgaben und Artikellisten. Wir setzen wieder \"rueckwaerts\" ein:\n", + " \n", + " ausgaben_list = []\n", + " for ausgabe in zeitschrift.ausgaben:\n", + " \n", + " artikel_list = []\n", + " for ein_artikel in ausgabe.artikel:\n", + " artikel_list.append({\"Titel\": ein_artikel.titel, \"Autor\": ein_artikel.autor})\n", + " \n", + " # In diesem Dictionary verwenden wir .copy fuer die Liste, da fuer die naechste ausgabe die Liste wieer geleert wird\n", + " ausgaben_list.append({\"Nr\": ausgabe.nr, \"Artikel\":artikel_list.copy()})\n", + " \n", + " zeitschriften_list.append({\"Titel\": zeitschrift.titel, \"Verlag\": zeitschrift.verlag, \"Ausgaben\": ausgaben_list.copy()})\n", + " \n", + " return json.dumps({\"Buecher\": buecher_list, \"Zeitschriften\": zeitschriften_list}, indent = 4)" + ] + }, + { + "cell_type": "markdown", + "id": "47b06bc3", + "metadata": {}, + "source": [ + "Test der Funktion:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "9d396c48", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\n", + " \"Buecher\": [\n", + " {\n", + " \"Titel\": \"Das erste Buch\",\n", + " \"Autor\": \"M. Muster\",\n", + " \"Jahr\": 2013\n", + " },\n", + " {\n", + " \"Titel\": \"Ein Beispielbuch\",\n", + " \"Autor\": \"E. Beispiel\",\n", + " \"Jahr\": 2022\n", + " }\n", + " ],\n", + " \"Zeitschriften\": [\n", + " {\n", + " \"Titel\": \"Die beste Zeitschrift\",\n", + " \"Verlag\": \"TOPVerlag\",\n", + " \"Ausgaben\": [\n", + " {\n", + " \"Nr\": \"1/2023\",\n", + " \"Artikel\": [\n", + " {\n", + " \"Titel\": \"Editorial\",\n", + " \"Autor\": \"A. Top\"\n", + " },\n", + " {\n", + " \"Titel\": \"Untersuchung von xy\",\n", + " \"Autor\": \"C. Schlau\"\n", + " }\n", + " ]\n", + " },\n", + " {\n", + " \"Nr\": \"2/2023\",\n", + " \"Artikel\": []\n", + " }\n", + " ]\n", + " }\n", + " ]\n", + "}\n" + ] + } + ], + "source": [ + "print(bibliothek_to_json(OO_bib))" + ] + }, + { + "cell_type": "markdown", + "id": "7cfb14b3", + "metadata": {}, + "source": [ + "#### <font color='blue'>**Speichern als xml**</font>\n", + "\n", + "Nun werden wir die gleiche Funktionalität mit xml Bereitstellen. Die entsprechende Funktion wird sehr ähnlich aufgebaut. Wir erzeugen mit dem xml.ElementTree Knoten für jedes Attribut. Wenn das Attribut eine Liste ist, erstellen wir Unterknoten für jedes der Listenobjekte. Da wir mehr Knoten als bei json anlegen und benennen, sowie eine extra Zeile für das Definieren des Werts benötigen, wirkt der Code womöglich zunächst sehr lang und unübersichtlich. Das Prinzip ist allerdings relativ simpel (da wir nicht rückwärts einsetzen müssen, evtl. sogar simpler als bei json) und nicht groß anders als bei dem Beispiel mit den Karts aus dem Grundlagennotebook. Wir haben nur etwas mehr Ebenen, in denen wir die Elemente durchgehen müssen. Zum Schluss wird eine Methode verwendet, mit der man auch in Python Versionen unter 3.9 eine ansehnlich formatierte Ausgabe enthält. Da es ab Python 3.9 nicht nötig ist, wird diese nicht detailliert erklärt." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "3f26cd8c", + "metadata": {}, + "outputs": [], + "source": [ + "import xml.etree.ElementTree as ET\n", + "\n", + "# folgender import nur fuer das Einruecken in Python <3.9 noetig\n", + "from xml.dom import minidom\n", + "\n", + "def bibliothek_to_xml(bib):\n", + " \n", + " root = ET.Element(\"Bibliothek\")\n", + " \n", + " buecher_node = ET.SubElement(root, \"Buecher\")\n", + " zeitschriften_node = ET.SubElement(root, \"Zeitschriften\")\n", + " \n", + " for buch in bib.buecher:\n", + " buch_node = ET.SubElement(buecher_node, \"Buch\")\n", + " \n", + " buch_titel = ET.SubElement(buch_node, \"Titel\")\n", + " buch_titel.text = buch.titel\n", + " buch_autor = ET.SubElement(buch_node, \"Autor\")\n", + " buch_autor.text = buch.autor\n", + " buch_jahr = ET.SubElement(buch_node, \"Jahr\")\n", + " buch_jahr.text = str(buch.jahr)\n", + " \n", + " for zeitschrift in bib.zeitschriften:\n", + " zeitschrift_node = ET.SubElement(zeitschriften_node, \"Zeitschrift\")\n", + " \n", + " zeitschrift_titel = ET.SubElement(zeitschrift_node, \"Titel\")\n", + " zeitschrift_titel.text = zeitschrift.titel\n", + " zeitschrift_verlag = ET.SubElement(zeitschrift_node, \"Verlag\")\n", + " zeitschrift_verlag.text = zeitschrift.verlag\n", + " \n", + " zeitschrift_ausgaben_node = ET.SubElement(zeitschrift_node, \"Ausgaben\") \n", + " \n", + " for ausgabe in zeitschrift.ausgaben:\n", + " ausgabe_node = ET.SubElement(zeitschrift_ausgaben_node, \"Ausgabe\")\n", + " \n", + " ausgabe_nr = ET.SubElement(ausgabe_node, \"Nr\")\n", + " ausgabe_nr.text = ausgabe.nr\n", + " \n", + " ausgabe_alle_artikel_node = ET.SubElement(ausgabe_node, \"AlleArtikel\")\n", + " \n", + " for ein_artikel in ausgabe.artikel:\n", + " artikel_node = ET.SubElement(ausgabe_alle_artikel_node, \"Artikel\")\n", + " \n", + " artikel_titel = ET.SubElement(artikel_node, \"Titel\")\n", + " artikel_titel.text = ein_artikel.titel\n", + " artikel_autor = ET.SubElement(artikel_node, \"Autor\")\n", + " artikel_autor.text = ein_artikel.autor\n", + " \n", + " orig_string = ET.tostring(root, 'utf-8')\n", + " # orig_string koennte bereits als Ergebnis mit return ausgegeben werden. Der folgende Befehl macht es ansehnlicher\n", + " \n", + " pretty_string = minidom.parseString(orig_string).toprettyxml(indent=\" \")\n", + " \n", + " return pretty_string" + ] + }, + { + "cell_type": "markdown", + "id": "1c22717c", + "metadata": {}, + "source": [ + "Auch dieses testen wir:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "9c435ff2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "<?xml version=\"1.0\" ?>\n", + "<Bibliothek>\n", + " <Buecher>\n", + " <Buch>\n", + " <Titel>Das erste Buch</Titel>\n", + " <Autor>M. Muster</Autor>\n", + " <Jahr>2013</Jahr>\n", + " </Buch>\n", + " <Buch>\n", + " <Titel>Ein Beispielbuch</Titel>\n", + " <Autor>E. Beispiel</Autor>\n", + " <Jahr>2022</Jahr>\n", + " </Buch>\n", + " </Buecher>\n", + " <Zeitschriften>\n", + " <Zeitschrift>\n", + " <Titel>Die beste Zeitschrift</Titel>\n", + " <Verlag>TOPVerlag</Verlag>\n", + " <Ausgaben>\n", + " <Ausgabe>\n", + " <Nr>1/2023</Nr>\n", + " <AlleArtikel>\n", + " <Artikel>\n", + " <Titel>Editorial</Titel>\n", + " <Autor>A. Top</Autor>\n", + " </Artikel>\n", + " <Artikel>\n", + " <Titel>Untersuchung von xy</Titel>\n", + " <Autor>C. Schlau</Autor>\n", + " </Artikel>\n", + " </AlleArtikel>\n", + " </Ausgabe>\n", + " <Ausgabe>\n", + " <Nr>2/2023</Nr>\n", + " <AlleArtikel/>\n", + " </Ausgabe>\n", + " </Ausgaben>\n", + " </Zeitschrift>\n", + " </Zeitschriften>\n", + "</Bibliothek>\n", + "\n" + ] + } + ], + "source": [ + "print(bibliothek_to_xml(OO_bib))" + ] + }, + { + "cell_type": "markdown", + "id": "93f15900", + "metadata": {}, + "source": [ + "Die erste Zeile wird durch das umformatieren hinzugefügt. Ein Tag mit <? tag ?> wird als Kommentar interpretiert. Typischerweise gibt die erste Zeile in xml-Dateien mittels Kommentar Hinweise auf die Version." + ] + }, + { + "cell_type": "markdown", + "id": "5d641f11", + "metadata": {}, + "source": [ + "#### <font color='blue'>**Einlesen**</font>\n", + "\n", + "Wir können nun noch Funktionen schreiben, die die Objekte anhand der erstellten Zeichenketten anlegt und ein Bibliothek Objekt ausgibt. Vom Prinzip müssen wir wieder mit verschachtelten Schleifen durch alle Ebenen gehen und die Objekte anlegen. In diesem Notebook sind verkürzte Beispiele enthalten, die nur die Bücher anlegen. Wenn du üben möchtest, erweitere diese Funktionen so, dass auch alle Zeitschriften mit ihren Ausgaben und Artikeln ausgelesen werden. Das Prinzip ist wie bei den Büchern, es gibt nur mehr Ebenen." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "3359e50a", + "metadata": {}, + "outputs": [], + "source": [ + "def buecher_from_json_to_bibliothek(json_string):\n", + " bib_dict = json.loads(json_string)\n", + " # Die Daten sind nun in bib_dict\n", + " \n", + " # Liste fuer die Buch-Objekte anlegen\n", + " buecher_list = []\n", + " \n", + " # Alle Dictionaries aus der Liste mit buechern im bib_dict durchgehen\n", + " for buch in bib_dict[\"Buecher\"]:\n", + " # Anlegen und zur Liste hinzufuegen eines Objekts fuer das entsprechende Buch mit den Attributen, die dem dictionary entnommen werden \n", + " buch_objekt = Buch(buch[\"Titel\"], buch[\"Autor\"], int(buch[\"Jahr\"]))\n", + " buecher_list.append(buch_objekt)\n", + " \n", + " # Hier koennte man noch die Zeitschriften einlesen\n", + " \n", + " # Erstellung eines Bibliothek-Objekts, wir uebergeben dabei die gefuellte Buecherliste und eine leere Zeitschriftenliste. Das Objekt wird direkt von der Funktion zurueckgegeben.\n", + " return Bibliothek(buecher_list, [])\n", + " \n", + " \n", + "def buecher_from_xml_to_bibliothek(xml_string):\n", + " root = ET.fromstring(xml_string)\n", + " # Die Daten sind nun in root\n", + " \n", + " # Liste fuer die Buch-Objekte anlegen\n", + " buecher_list = []\n", + " \n", + " # Alle Subknoten (\"Buch\") des Subknotens \"Buecher\" des Baums durchgehen:\n", + " for buch in root.find(\"Buecher\").findall(\"Buch\"):\n", + " # Anlegen und zur Liste hinzufuegen eines Objekts fuer das entsprechende Buch mit den Attributen, die den Subknoten entnommen werden \n", + " buch_objekt = Buch(buch.find(\"Titel\").text, buch.find(\"Autor\").text, int(buch.find(\"Jahr\").text))\n", + " buecher_list.append(buch_objekt)\n", + " \n", + " # Hier koennte man noch die Zeitschriften einlesen\n", + " \n", + " # Erstellung eines Bibliothek-Objekts, wir uebergeben dabei die gefuellte Buecherliste und eine leere Zeitschriftenliste. Das Objekt wird direkt von der Funktion zurueckgegeben.\n", + " return Bibliothek(buecher_list, [])" + ] + }, + { + "cell_type": "markdown", + "id": "50943020", + "metadata": {}, + "source": [ + "Und noch der Test am Beispiel der Jahreszahlen der beiden Bücher:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "b80b127b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2013\n", + "2022\n" + ] + } + ], + "source": [ + "json_string_bib = bibliothek_to_json(OO_bib)\n", + "bib_from_json = buecher_from_json_to_bibliothek(json_string_bib)\n", + "\n", + "xml_string_bib = bibliothek_to_xml(OO_bib)\n", + "bib_from_xml = buecher_from_xml_to_bibliothek(xml_string_bib)\n", + "\n", + "print(bib_from_json.buecher[0].jahr)\n", + "print(bib_from_xml.buecher[1].jahr)" + ] + } + ], + "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 +}