Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • p.anaguano/informatik2022
  • a.engelke/informatik2022
  • p.schleinzer/informatik2022
  • n.rosenkranz/informatik2022
  • w.weber/informatik2022
  • xuhao.zhang/informatik2022
  • h.el-menuawy/informatik2022
  • saifalla.ibrahim/informatik2022
  • d.krause/informatik2022
  • p.schilling/informatik2022
  • j.tolke/informatik2022
  • f.luckau/informatik2022
  • danish.ahmad/informatik2022
  • emir.sagdani/informatik2022
  • d.griedel/informatik2022
  • j.mahnke/informatik2022
  • l.poehler/informatik2022
  • christoph.wrede/informatik2022
  • y.kummert/informatik2022
  • alexander.reisch/informatik2022
  • t.dickel/informatik2022
  • ni.petersen/informatik2022
  • markus.werner/informatik2022
  • s.mouammar/informatik2022
  • j.jahns/informatik2022
  • m.figueroa-castillo/informatik2022
  • b.hannan/informatik2022
  • v.lapschiess/informatik2022
  • j.hegner/informatik2022
  • g.paraschiv/informatik2022
  • e.abkaimov/informatik2022
  • l.krogmann/informatik2022
  • d.mizyed/informatik2022
  • h.almasri/informatik2022
  • a.mickan/informatik2022
  • f.shikh-alshabab/informatik2022
  • j.feldbausch/informatik2022
  • l.abdel-kader/informatik2022
  • jan.seibt/informatik2022
  • e.goekmen/informatik2022
  • nathanael.schenk/informatik2022
  • r.reksius/informatik2022
  • edmont.schulze/informatik2022
  • a.singh/informatik2022
  • p.christensen/informatik2022
  • m.woidt/informatik2022
46 results
Show changes
Showing
with 3475 additions and 0 deletions
Semester_2/Einheit_11/Pics/Sprachen.png

284 KiB

<?xml version="1.0" encoding="windows-1252" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" contentStyleType="text/css" height="463px" preserveAspectRatio="none" style="width:354px;height:463px;background:#FFFFFF;" version="1.1" viewBox="0 0 354 463" width="354px" zoomAndPan="magnify"><defs/><g><!--class Moebel--><g id="elem_Moebel"><rect codeLine="1" fill="#F1F1F1" height="227.2656" id="Moebel" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="272" x="41" y="7"/><ellipse cx="146.25" cy="23" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1.0;"/><path d="M148.5938,18.6719 C147.6563,18.2344 147.0625,18.0938 146.1875,18.0938 C143.5625,18.0938 141.5625,20.1719 141.5625,22.8906 L141.5625,24.0156 C141.5625,26.5938 143.6719,28.4844 146.5625,28.4844 C147.7813,28.4844 148.9375,28.1875 149.6875,27.6406 C150.2656,27.2344 150.5938,26.7813 150.5938,26.3906 C150.5938,25.9375 150.2031,25.5469 149.7344,25.5469 C149.5156,25.5469 149.3125,25.625 149.125,25.8125 C148.6719,26.2969 148.6719,26.2969 148.4844,26.3906 C148.0625,26.6563 147.375,26.7813 146.6094,26.7813 C144.5625,26.7813 143.2656,25.6875 143.2656,23.9844 L143.2656,22.8906 C143.2656,21.1094 144.5156,19.7969 146.25,19.7969 C146.8281,19.7969 147.4375,19.9531 147.9063,20.2031 C148.3906,20.4844 148.5625,20.7031 148.6563,21.1094 C148.7188,21.5156 148.75,21.6406 148.8906,21.7656 C149.0313,21.9063 149.2656,22.0156 149.4844,22.0156 C149.75,22.0156 150.0156,21.875 150.1875,21.6563 C150.2969,21.5 150.3281,21.3125 150.3281,20.8906 L150.3281,19.4688 C150.3281,19.0313 150.3125,18.9063 150.2188,18.75 C150.0625,18.4844 149.7813,18.3438 149.4844,18.3438 C149.1875,18.3438 148.9844,18.4375 148.7656,18.75 L148.5938,18.6719 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="53" x="166.75" y="27.8467">Moebel</text><line style="stroke:#181818;stroke-width:0.5;" x1="42" x2="312" y1="39" y2="39"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="87" x="47" y="55.9951">laenge:float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84" x="47" y="72.292">hoehe:float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="84" x="47" y="88.5889">breite:float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="86" x="47" y="104.8857">farbe:string</text><line style="stroke:#181818;stroke-width:0.5;" x1="42" x2="312" y1="112.1875" y2="112.1875"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="82" x="47" y="129.1826">Konstruktor</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="178" x="129" y="129.1826">(float, float, float, string)</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="129" x="47" y="145.4795">get_laenge():float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="126" x="47" y="161.7764">get_hoehe():float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="126" x="47" y="178.0732">get_breite():float</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="128" x="47" y="194.3701">get_farbe():string</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="156" x="47" y="210.667">set_farbe(string):void</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="73" x="47" y="226.9639">info():void</text></g><!--class Schrank--><g id="elem_Schrank"><rect codeLine="2" fill="#F1F1F1" height="162.0781" id="Schrank" rx="2.5" ry="2.5" style="stroke:#181818;stroke-width:0.5;" width="340" x="7" y="294"/><ellipse cx="143.75" cy="310" fill="#ADD1B2" rx="11" ry="11" style="stroke:#181818;stroke-width:1.0;"/><path d="M146.0938,305.6719 C145.1563,305.2344 144.5625,305.0938 143.6875,305.0938 C141.0625,305.0938 139.0625,307.1719 139.0625,309.8906 L139.0625,311.0156 C139.0625,313.5938 141.1719,315.4844 144.0625,315.4844 C145.2813,315.4844 146.4375,315.1875 147.1875,314.6406 C147.7656,314.2344 148.0938,313.7813 148.0938,313.3906 C148.0938,312.9375 147.7031,312.5469 147.2344,312.5469 C147.0156,312.5469 146.8125,312.625 146.625,312.8125 C146.1719,313.2969 146.1719,313.2969 145.9844,313.3906 C145.5625,313.6563 144.875,313.7813 144.1094,313.7813 C142.0625,313.7813 140.7656,312.6875 140.7656,310.9844 L140.7656,309.8906 C140.7656,308.1094 142.0156,306.7969 143.75,306.7969 C144.3281,306.7969 144.9375,306.9531 145.4063,307.2031 C145.8906,307.4844 146.0625,307.7031 146.1563,308.1094 C146.2188,308.5156 146.25,308.6406 146.3906,308.7656 C146.5313,308.9063 146.7656,309.0156 146.9844,309.0156 C147.25,309.0156 147.5156,308.875 147.6875,308.6563 C147.7969,308.5 147.8281,308.3125 147.8281,307.8906 L147.8281,306.4688 C147.8281,306.0313 147.8125,305.9063 147.7188,305.75 C147.5625,305.4844 147.2813,305.3438 146.9844,305.3438 C146.6875,305.3438 146.4844,305.4375 146.2656,305.75 L146.0938,305.6719 Z " fill="#000000"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="58" x="164.25" y="314.8467">Schrank</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="346" y1="326" y2="326"/><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="89" x="13" y="342.9951">spiegel:bool</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="89" x="13" y="359.292">n_tueren:int</text><line style="stroke:#181818;stroke-width:0.5;" x1="8" x2="346" y1="366.5938" y2="366.5938"/><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="82" x="13" y="383.5889">Konstruktor</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="246" x="95" y="383.5889">(float, float, float, string, bool, int)</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="131" x="13" y="399.8857">get_spiegel():bool</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="131" x="13" y="416.1826">get_n_tueren():int</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="73" x="13" y="432.4795">info():void</text><text fill="#000000" font-family="sans-serif" font-size="14" font-style="italic" lengthAdjust="spacing" textLength="93" x="13" y="448.7764">"+"-Operator</text><text fill="#000000" font-family="sans-serif" font-size="14" lengthAdjust="spacing" textLength="131" x="106" y="448.7764">(Schrank):Schrank</text></g><!--reverse link Moebel to Schrank--><g id="link_Moebel_Schrank"><path codeLine="4" d="M177,254.447 C177,267.862 177,281.176 177,293.78 " fill="none" id="Moebel-backto-Schrank" style="stroke:#181818;stroke-width:1.0;"/><polygon fill="none" points="170,254.089,177,234.089,184,254.088,170,254.089" style="stroke:#181818;stroke-width:1.0;"/></g><!--SRC=[bP1B3eCW54Ndhe8uqbJZd7G7JTD15s3GFfL8U0rYHrryGMZ1pwGZu9myxeLMi6aYLuG6HD8kXtixA2R7977q_2xBm3mab0W6iWVQ2MGwq05XsC560TSxsZ7L09squh8FjAekA0sSHusggh9bg23hnGtbOQe7NRj2MRxDiMwfTQnSkMEtL3HgMtBwET-4lf0_WkUomyp3nF-T7Njow47G1b54MDPw1WMISgaZ_CTF5CHUMPXSdKSts2VuJDDcashbDzdOTNRyWBXHUahBsnCKSvMyogdVV000]--></g></svg>
\ No newline at end of file
%% Cell type:markdown id:18a8ddd0 tags:
## <font color='blue'> Übung 8: C++ </font>
#### <font color='blue'> Problemstellung: Übersetzung einer Python Klassenstruktur nach C++ </font>
Folgende Klassenstruktur und Hauptfunktion soll nach C++ nachprogrammiert werden.
Die Klasse `Moebel` hat die Attribute `laenge`, `hoehe`, `breite` und `farbe`, die über den Konstruktor gesetzt werden. Alle diese Attribute haben eine `get`-Methode, `farbe` hat zudem eine `set`-Methode (Hintergrund: Die Farbe kann bei einem Möbelstück leicht geändert werden, für die Maße wäre ein erheblicher Umbau erforderlich). Außerdem hat die Klasse eine Methode `info`, die eine Textausgabe erzeugt und die Daten über das Möbelstück auf dem Bildschirm ausgibt. Zum Beispiel im Format: "Das Möbelstück ist 100 cm lang, 80 cm hoch, 40 cm breit und hat die Farbe schwarz."
Davon abgeleitet ist die Klasse `Schrank`. Diese hat zusätzlich ein Attribut `spiegel`, das über einen Wahrheitswert enthält, ob ein Spiegel vorhanden ist. Außerdem hat es `n_tueren`, das die Anzahl der Türen speichert. Beide haben eine get, aber keine set-Methode. Die `info`-Methode ist entsprechend erweitert, um auch die Informationen über die beiden zusätzlichen Attribute zu enthalten.
Der Additionsoperator wird für Schränke definiert und dabei als nebeneinanderstellen interpretiert. Dabei wird die Länge addiert und bei Höhe und Breite der höhere Wert genommen. Wenn die Farbe gleich ist, wird diese Farbe übernommen, ansonsten "mehrfarbig". Die Anzahl der Türen wird addiert und, ob der neue Schrank einen Spiegel hat, ist davon abhängig, ob einer der beiden Schränke einen Spiegel hat.
In der Hauptfunktion wird diese Funktionalität getestet.
Die Klassenstruktur ist im folgenden UML-Diagramm gezeigt:
%% Cell type:markdown id:aa979a2f tags:
![](./Pics/UML_Moebel_Schrank.svg)
%% Cell type:markdown id:8dfa507a tags:
Moebel:
%% Cell type:code id:6278b740 tags:
``` python
class Moebel:
def __init__(self, laenge, hoehe, breite, farbe):
self.laenge = laenge
self.hoehe = hoehe
self.breite = breite
self.farbe = farbe
def get_laenge(self):
return self.laenge
def get_hoehe(self):
return self.hoehe
def get_breite(self):
return self.breite
def get_farbe(self):
return self.farbe
def set_farbe(self,farbe):
self.farbe = farbe
def info(self):
print(f'Das Möbelstück ist {self.laenge} cm lang, {self.hoehe} cm hoch, {self.breite} cm breit und hat die Farbe {self.farbe}.')
```
%% Cell type:markdown id:49ce2521 tags:
Schrank:
%% Cell type:code id:8b33d53b tags:
``` python
class Schrank(Moebel):
def __init__(self, laenge, hoehe, breite, farbe, spiegel, n_tueren):
super().__init__(laenge, hoehe, breite, farbe)
self.spiegel = spiegel
self.n_tueren = n_tueren
def get_spiegel(self):
return self.spiegel
def get_n_tueren(self):
return self.n_tueren
def info(self):
if self.spiegel:
print(f'Der Schrank ist {self.laenge} cm lang, {self.hoehe} cm hoch, {self.breite} cm breit, {self.farbe}, hat {self.n_tueren} Türen und Spiegel.')
else:
print(f'Der Schrank ist {self.laenge} cm lang, {self.hoehe} cm hoch, {self.breite} cm breit, {self.farbe}, hat {self.n_tueren} Türen und keine Spiegel.')
def __add__(self, s2):
# Variablen, die eine if Struktur erfordern:
h = self.hoehe
b = self.breite
f = self.farbe
if self.hoehe < s2.hoehe:
h = s2.hoehe
if self.breite > s2.breite:
b = s2.breite
if self.farbe != s2.farbe:
f = "mehrfarbig"
# Restliche variablen direkt in-place berechnet
return Schrank(self.laenge + s2.laenge, h, b, f, self.spiegel or s2.spiegel, self.n_tueren + s2.n_tueren)
```
%% Cell type:markdown id:5e1e35bf tags:
Testfunktion:
%% Cell type:code id:ebb28f33 tags:
``` python
print("Test: Klasse Moebel: ")
m1 = Moebel(100, 80, 40, "blau")
m1.info()
print(f"Hoehe von m1: {m1.get_hoehe()} cm.")
m1.set_farbe("schwarz")
m1.info()
print("\n\nTest: Klasse Schrank:")
s1 = Schrank(100, 180, 60, "weiss", True, 2)
s1.info()
s2 = Schrank(60, 200, 60, "weiss", False, 1)
s2.info()
(s1+s2).info()
s2.set_farbe("schwarz")
(s1+s2).info()
```
%% Output
Test: Klasse Moebel:
Das Möbelstück ist 100 cm lang, 80 cm hoch, 40 cm breit und hat die Farbe blau.
Hoehe von m1: 80 cm.
Das Möbelstück ist 100 cm lang, 80 cm hoch, 40 cm breit und hat die Farbe schwarz.
Test: Klasse Schrank:
Der Schrank ist 100 cm lang, 180 cm hoch, 60 cm breit, weiss, hat 2 Türen und Spiegel.
Der Schrank ist 60 cm lang, 200 cm hoch, 60 cm breit, weiss, hat 1 Türen und keine Spiegel.
Der Schrank ist 160 cm lang, 200 cm hoch, 60 cm breit, weiss, hat 3 Türen und Spiegel.
Der Schrank ist 160 cm lang, 200 cm hoch, 60 cm breit, mehrfarbig, hat 3 Türen und Spiegel.
source diff could not be displayed: it is too large. Options to address this: view the blob.
Semester_2/Einheit_12/Pics/.ipynb_checkpoints/Training-checkpoint.png

82.4 KiB

Semester_2/Einheit_12/Pics/AufbauNeuron.png

33.1 KiB

Semester_2/Einheit_12/Pics/Bluete.png

355 KiB

Semester_2/Einheit_12/Pics/Entscheidungsbaum.png

35.2 KiB

Semester_2/Einheit_12/Pics/Konfusionsmatrix.png

54.9 KiB

Semester_2/Einheit_12/Pics/Methoden.png

20.2 KiB

Semester_2/Einheit_12/Pics/Netzwerk.png

64.7 KiB

Semester_2/Einheit_12/Pics/Neuron.png

123 KiB

Semester_2/Einheit_12/Pics/QRCode.png

679 B

Semester_2/Einheit_12/Pics/Training.png

82.4 KiB

Semester_2/Einheit_12/Pics/decision_tree.png

9.76 KiB

sepal length,sepal width,petal length,petal width,species
5.1,3.5,1.4,0.2,Iris-setosa
4.9,3,1.4,0.2,Iris-setosa
4.7,3.2,1.3,0.2,Iris-setosa
4.6,3.1,1.5,0.2,Iris-setosa
5,3.6,1.4,0.2,Iris-setosa
5.4,3.9,1.7,0.4,Iris-setosa
4.6,3.4,1.4,0.3,Iris-setosa
5,3.4,1.5,0.2,Iris-setosa
4.4,2.9,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5.4,3.7,1.5,0.2,Iris-setosa
4.8,3.4,1.6,0.2,Iris-setosa
4.8,3,1.4,0.1,Iris-setosa
4.3,3,1.1,0.1,Iris-setosa
5.8,4,1.2,0.2,Iris-setosa
5.7,4.4,1.5,0.4,Iris-setosa
5.4,3.9,1.3,0.4,Iris-setosa
5.1,3.5,1.4,0.3,Iris-setosa
5.7,3.8,1.7,0.3,Iris-setosa
5.1,3.8,1.5,0.3,Iris-setosa
5.4,3.4,1.7,0.2,Iris-setosa
5.1,3.7,1.5,0.4,Iris-setosa
4.6,3.6,1,0.2,Iris-setosa
5.1,3.3,1.7,0.5,Iris-setosa
4.8,3.4,1.9,0.2,Iris-setosa
5,3,1.6,0.2,Iris-setosa
5,3.4,1.6,0.4,Iris-setosa
5.2,3.5,1.5,0.2,Iris-setosa
5.2,3.4,1.4,0.2,Iris-setosa
4.7,3.2,1.6,0.2,Iris-setosa
4.8,3.1,1.6,0.2,Iris-setosa
5.4,3.4,1.5,0.4,Iris-setosa
5.2,4.1,1.5,0.1,Iris-setosa
5.5,4.2,1.4,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
5,3.2,1.2,0.2,Iris-setosa
5.5,3.5,1.3,0.2,Iris-setosa
4.9,3.1,1.5,0.1,Iris-setosa
4.4,3,1.3,0.2,Iris-setosa
5.1,3.4,1.5,0.2,Iris-setosa
5,3.5,1.3,0.3,Iris-setosa
4.5,2.3,1.3,0.3,Iris-setosa
4.4,3.2,1.3,0.2,Iris-setosa
5,3.5,1.6,0.6,Iris-setosa
5.1,3.8,1.9,0.4,Iris-setosa
4.8,3,1.4,0.3,Iris-setosa
5.1,3.8,1.6,0.2,Iris-setosa
4.6,3.2,1.4,0.2,Iris-setosa
5.3,3.7,1.5,0.2,Iris-setosa
5,3.3,1.4,0.2,Iris-setosa
7,3.2,4.7,1.4,Iris-versicolor
6.4,3.2,4.5,1.5,Iris-versicolor
6.9,3.1,4.9,1.5,Iris-versicolor
5.5,2.3,4,1.3,Iris-versicolor
6.5,2.8,4.6,1.5,Iris-versicolor
5.7,2.8,4.5,1.3,Iris-versicolor
6.3,3.3,4.7,1.6,Iris-versicolor
4.9,2.4,3.3,1,Iris-versicolor
6.6,2.9,4.6,1.3,Iris-versicolor
5.2,2.7,3.9,1.4,Iris-versicolor
5,2,3.5,1,Iris-versicolor
5.9,3,4.2,1.5,Iris-versicolor
6,2.2,4,1,Iris-versicolor
6.1,2.9,4.7,1.4,Iris-versicolor
5.6,2.9,3.6,1.3,Iris-versicolor
6.7,3.1,4.4,1.4,Iris-versicolor
5.6,3,4.5,1.5,Iris-versicolor
5.8,2.7,4.1,1,Iris-versicolor
6.2,2.2,4.5,1.5,Iris-versicolor
5.6,2.5,3.9,1.1,Iris-versicolor
5.9,3.2,4.8,1.8,Iris-versicolor
6.1,2.8,4,1.3,Iris-versicolor
6.3,2.5,4.9,1.5,Iris-versicolor
6.1,2.8,4.7,1.2,Iris-versicolor
6.4,2.9,4.3,1.3,Iris-versicolor
6.6,3,4.4,1.4,Iris-versicolor
6.8,2.8,4.8,1.4,Iris-versicolor
6.7,3,5,1.7,Iris-versicolor
6,2.9,4.5,1.5,Iris-versicolor
5.7,2.6,3.5,1,Iris-versicolor
5.5,2.4,3.8,1.1,Iris-versicolor
5.5,2.4,3.7,1,Iris-versicolor
5.8,2.7,3.9,1.2,Iris-versicolor
6,2.7,5.1,1.6,Iris-versicolor
5.4,3,4.5,1.5,Iris-versicolor
6,3.4,4.5,1.6,Iris-versicolor
6.7,3.1,4.7,1.5,Iris-versicolor
6.3,2.3,4.4,1.3,Iris-versicolor
5.6,3,4.1,1.3,Iris-versicolor
5.5,2.5,4,1.3,Iris-versicolor
5.5,2.6,4.4,1.2,Iris-versicolor
6.1,3,4.6,1.4,Iris-versicolor
5.8,2.6,4,1.2,Iris-versicolor
5,2.3,3.3,1,Iris-versicolor
5.6,2.7,4.2,1.3,Iris-versicolor
5.7,3,4.2,1.2,Iris-versicolor
5.7,2.9,4.2,1.3,Iris-versicolor
6.2,2.9,4.3,1.3,Iris-versicolor
5.1,2.5,3,1.1,Iris-versicolor
5.7,2.8,4.1,1.3,Iris-versicolor
6.3,3.3,6,2.5,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
7.1,3,5.9,2.1,Iris-virginica
6.3,2.9,5.6,1.8,Iris-virginica
6.5,3,5.8,2.2,Iris-virginica
7.6,3,6.6,2.1,Iris-virginica
4.9,2.5,4.5,1.7,Iris-virginica
7.3,2.9,6.3,1.8,Iris-virginica
6.7,2.5,5.8,1.8,Iris-virginica
7.2,3.6,6.1,2.5,Iris-virginica
6.5,3.2,5.1,2,Iris-virginica
6.4,2.7,5.3,1.9,Iris-virginica
6.8,3,5.5,2.1,Iris-virginica
5.7,2.5,5,2,Iris-virginica
5.8,2.8,5.1,2.4,Iris-virginica
6.4,3.2,5.3,2.3,Iris-virginica
6.5,3,5.5,1.8,Iris-virginica
7.7,3.8,6.7,2.2,Iris-virginica
7.7,2.6,6.9,2.3,Iris-virginica
6,2.2,5,1.5,Iris-virginica
6.9,3.2,5.7,2.3,Iris-virginica
5.6,2.8,4.9,2,Iris-virginica
7.7,2.8,6.7,2,Iris-virginica
6.3,2.7,4.9,1.8,Iris-virginica
6.7,3.3,5.7,2.1,Iris-virginica
7.2,3.2,6,1.8,Iris-virginica
6.2,2.8,4.8,1.8,Iris-virginica
6.1,3,4.9,1.8,Iris-virginica
6.4,2.8,5.6,2.1,Iris-virginica
7.2,3,5.8,1.6,Iris-virginica
7.4,2.8,6.1,1.9,Iris-virginica
7.9,3.8,6.4,2,Iris-virginica
6.4,2.8,5.6,2.2,Iris-virginica
6.3,2.8,5.1,1.5,Iris-virginica
6.1,2.6,5.6,1.4,Iris-virginica
7.7,3,6.1,2.3,Iris-virginica
6.3,3.4,5.6,2.4,Iris-virginica
6.4,3.1,5.5,1.8,Iris-virginica
6,3,4.8,1.8,Iris-virginica
6.9,3.1,5.4,2.1,Iris-virginica
6.7,3.1,5.6,2.4,Iris-virginica
6.9,3.1,5.1,2.3,Iris-virginica
5.8,2.7,5.1,1.9,Iris-virginica
6.8,3.2,5.9,2.3,Iris-virginica
6.7,3.3,5.7,2.5,Iris-virginica
6.7,3,5.2,2.3,Iris-virginica
6.3,2.5,5,1.9,Iris-virginica
6.5,3,5.2,2,Iris-virginica
6.2,3.4,5.4,2.3,Iris-virginica
5.9,3,5.1,1.8,Iris-virginica
\ No newline at end of file
%% Cell type:markdown id:f0b13807-b3b2-4f55-9581-9f8d4ffaa3b5 tags:
### <font color='blue'>**Einleitendes Thema**</font>
Sehr oft ist es notwendig, erzeugte Daten nicht nur temporär im Speicher zu haben, sondern permanent auf der Festplatte abzuspeichern und auch weiterversenden zu können. Das ermöglicht die Nutzung von Daten in verschiedenen Programmen (z.B. Berechnung und Auswertung, nicht auf Python beschränkt), von verschiedenen Personen oder der langfristigen Sicherung, damit nicht alle Berechnungen neu durchgeführt werden müssen, wenn nach einiger Zeit ein neuer Aspekt oder in einer anderen Darstellungsform ausgewertet werden soll.
%% Cell type:markdown id:78480fdf-603c-48e5-b671-5b634c94fbaa tags:
### <font color='blue'>**Kompaktes Fallbeispiel**</font>
%% Cell type:code id:c54061cb tags:
``` python
"""
In diesem Beispiel wird eine Datenreihe in einer CSV (comma-separated-value) Datei abgespeichert.
Diese kann mit anderen Programmen geoeffnet (und auch bearbeitet) werden.
Anschliessend wird die Datei eingelesen und das eingelesene Feld ausgegeben.
"""
# Hilfreiche Bibliothek fuer die arbeit mit csv
import csv
# Beispiel-Datenreihe
quadratzahlen = [[0,0], [1,1], [2,4], [3,9], [4,16], [5,25], [6,36], [7,49], [8,64], [9,81], [10,100]]
#Schreiben der Datei
with open('quadratzahlen.csv','w', newline='') as f:
writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC, delimiter = ';') # Instanziieren eines Objekts, das die Schreib-Methoden enthaelt
writer.writerow(['Zahl','Quadratzahl']) # Überschriften, damit die Datei noch nachvollzogen werden kann
writer.writerows(quadratzahlen) # Schreiben der Daten selbst
"""
Tipp:
Oeffne die Datei in einem Tabellenkalukationsprogramm (z.B. Excel, Open Office Calc oder Mac Numbers.)
Es kann sein, dass das Komma auf deutschen Systemen als Dezimalzeichen erkannt wird, deswegen verwenden wir hier ";" als Trennzeichen (Standard bei Excel).
Es gibt aber auch Moeglichkeiten, wenn "," verwendet wird.
In Excel ist es zum Beispiel am einfachsten, die CSV als Datenimport einzulesen, dabei kann das Trennzeichen gewaehlt werden
Man sollte eine Tabelle mit den beiden Spalten erhalten.
Oder öffne die Datei in einem Texteditor, dann siehst du, wie die Daten im csv Format abgelegt sind
"""
rows = [] # Hier lesen wir gleich die Daten ein
#Lesen der Datei
with open("quadratzahlen.csv",'r', newline='') as f:
reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC, delimiter = ';') # Instanziieren eines Objekts, das die Lese-Methoden enthaelt
for row in reader:
rows.append(row) # Jede Zeile wird an row angehaengt
imported_quadratzahlen = rows # Dies ist nur zum Verdeutlichen, dass wir die Zahlen aus der Datei importiert haben
print(imported_quadratzahlen) # Ausgabe der Zahlen
```
%% Output
[['Zahl', 'Quadratzahl'], [0.0, 0.0], [1.0, 1.0], [2.0, 4.0], [3.0, 9.0], [4.0, 16.0], [5.0, 25.0], [6.0, 36.0], [7.0, 49.0], [8.0, 64.0], [9.0, 81.0], [10.0, 100.0]]
%% Cell type:markdown id:9837909c-0cc2-43bd-9692-bb149a7a87ba tags:
# <font color='blue'>**Übersicht - "Dateien"**</font>
Es gibt sehr viele verschiedene Datenformate. Wir werden in diesem Notebook zunächst das allgemeine Schreiben und Lesen von Dateien in Python behandeln, und anschließend auf ein paar gängige Dateiformate und dazu passende Module in Python eingehen. Allerdings kann dieses Notebook nur einen groben Überblick geben, da es unzählige Möglichkeiten zur Arbeit mit Dateien und den entsprechenden Modulen gibt. Das Grundlagennotebook gibt einen Einstiegsüberblick und beinhaltet nützliche Funktionen.
%% Cell type:markdown id:3b34e520-f385-4530-b2c7-4b6df641d72b tags:
### <font color='blue'>**Lernziele des Notebooks**</font>
* ASCII Dateien
* Allgemeine Textdateien
* typische Formate
* csv
* json
* xml
* Binärdateien mit Pickle
%% Cell type:markdown id:f6012101-a726-4880-8bbc-ccfee49d9ceb tags:
# <font color='blue'>**"ASCII-Dateien"**</font>
ASCII-Dateien sind Dateien, in denen ausschließlich lesbare Zeichen (Text mit Zahlen und Sonderzeichen) enthalten ist. ASCII selbst ist der Standard zur Zuordnung von Zeichen (also Buchstaben, Sonderzeichen, etc.) zu den jeweiligen Binärzahlen im Speicher. Solche Dateien kann man mit Texteditoren öffnen und lesen.
Im Gegensatz dazu stehen Binärdateien, in denen kein lesbarer Text enthalten ist. Solche Dateien können nur (sinnvoll) von Programmen geöffnet werden, die wissen, wie die Binärdatei aufgebaut ist (wo Zahlen stehen, wo Texte stehen, was diese Bedeuten usw.). Öffnet man Binärdateien in einem Texteditor, sieht man als Mensch nur wirre Zeichen.
Bei ASCII-Dateien ist klar definiert, dass alle darin enthaltenen Binärzahlen als Zeichen ausgewertet und angezeigt werden. Deshalb werden sie auch "human-readable" also "von Menschen lesbar" bezeichnet. Dass man sie mit Texteditoren öffnen und lesen kann, bedeutet nicht, dass ASCII-Dateien nur für Texteditoren gut sind. Man könnte zum Beispiel ein Bild in einer ASCII-Datei speichern, indem man in der Datei zunächst die Bildhöhe und Bildbreite in Pixeln angibt, und eine Liste anschließt, in der die Helligkeitswerte für jeden Farbkanal (rot, grün, blau) für jeden Pixel aufgelistet sind. Eine solche Datei könnte man in einem Texteditor öffnen und die vielen Zahlen ansehen, oder in einem (kompatiblen) Programm zur Bildbetrachtung öffnen, das weiß, wie es diese Informationen als Bild darstellen kann, und als Bild ansehen (für Interessierte: https://de.wikipedia.org/wiki/Portable_Anymap).
%% Cell type:markdown id:97a85106-b815-4d8b-bc87-ecd93aae5312 tags:
### **<font color='blue'>Grundlagen</font>**
Python bringt standardmäßig bereits Werkzeuge zum Schreiben und Lesen von ASCII Dateien mit. Eine Datei wird mit dem Befehl ````open(dateiname)```` geöffnet (falls sie im aktuellen Verzeichnis nicht existiert, wird sie dort angelegt). Diese hat neben dem bereits oben aufgeführten Dateinamen der zu öffnenden Datei einige optionale Parameter, von denen der erste, ````mode```` der wichtigste ist. Mit diesem wird festgelegt ob gelesen oder geschrieben werden soll.
|mode|Bedeutung|
|----|:----|
|"r"| lesen |
|"w"| schreiben (überschreibt bisherige Datei mit dem gleichen Namen) |
|"a"| schreiben (fügt den Inhalt an bisherige Datei mit dem gleichen Namen an)|
|"x"| schreiben (nur, falls bisher keine Datei mit dem gleichen Namen gibt, sonst Fehler)|
|"b"| (angehängt) im Binärformat |
Der Befehl ````open()```` gibt ein Objekt zurück, mit dem die Datei bearbeitet werden kann. Eine geöffnete Datei wird im Betriebssystem blockiert, sodass kein Anderes Programm gleichzeitig darauf zugreifen kann. Es könnten sonst korrupte Dateien entstehen. Daher müssen Dateien nach dem Öffnen und Bearbeiten geschlossen werden. In Python gibt es dafür eine praktische Methode: Das ````with````. Der Befehl lautet voll ````with open(dateiname, mode) as f:```` und öffnet einen Block, an dessen Ende die Datei automatisch geschlossen wird. f ist dabei der Name des Objekts (er könnte beliebig anders sein. Ebenfalls typisch ist ````fid```` (abk. file identifier)). Mit diesem Objekt können nun zum Beispiel mit ````write(string)```` analog zu print Zeichenketten in die Datei (anstatt auf den Bildschirm) geschrieben werden. Mit ````readlines()```` kann eine Liste mit allen Zeilen ausgelesen werden. Wie die entsprechenden Daten formatiert, oder verarbeitet werden ist vollständig frei.
%% Cell type:code id:9161b653 tags:
``` python
# Erstellen einer Datei und schreiben von Text
with open("eine_erste_datei.txt","w") as f:
f.write("Die erste Zeile.\n")
f.write("In der dritten Zeile werden nun drei Zahlen stehen.\n")
f.write("1 2 3")
# Die Datei sollte im Ordner zu finden sein und geoeffnet werden koennen
# Lesen der Datei
with open("eine_erste_datei.txt","r") as f:
zeilen = f.readlines()
# Nun koennen wir die Daten beliebig weiterverwenden
# 1. Einfach auf dem Bildschirm ausgeben):
for zeile in zeilen:
print(zeile)
# Alternative, in der nicht jeder Zeilenumbruch doppelt ausgeführt wird (Zeilenumbruch in Datei + neuer print-befehl)
# for zeile in zeilen:
# print(zeile, end = "")
# 2. Die Zahlen der 3. Zeile zusammenzaehlen. Hierbei muessen wir ein paar Funktionen nutzen, um die Zeichenkette "1 2 3"
# zu 3 getrennten Zahlen umzuwandeln. Das ist zunächst die Methode "split()", die eine Zeichenkette aufteilt [...]
zahlen_zeichen = zeilen[2].split()
print(zahlen_zeichen) # Kontrollausgabe
# [...] und die Funktion "int()", die ein Zahlenzeichen in eine Ganzzahl umwandelt. Wir bilden direkt in einer Schleife die Summe.
summe = 0
for zahl_zeichen in zahlen_zeichen:
summe += int(zahl_zeichen)
print(f"Die Summe der 3 Zahlen: {summe}")
```
%% Output
Die erste Zeile.
In der dritten Zeile werden nun drei Zahlen stehen.
1 2 3
['1', '2', '3']
Die Summe der 3 Zahlen: 6
%% Cell type:markdown id:c2847319 tags:
Die gezeigte Methode ````split()```` in einer Zeichenkette teilt diese standardmäßig an den Leerzeichen in eine Liste von Zeichenketten auf. Hat man andere Trennzeichen, so kann man das Trennzeichen (bzw. die Trennzeichenkette) als Parameter übergeben:
%% Cell type:code id:ecaec008 tags:
``` python
text_typischer_trennung = "Das ist eine typische Trennung bei Zahlenwerten:;1;2.5;4;5.5"
print(text_typischer_trennung.split(";"))
#Das soll nur die allgemeine Moeglichkeit demonstrieren
text_untypischer_trennung = "DasTRENNUNGistTRENNUNGeineTRENNUNGuntypischeTRENNUNGTrennung"
print(text_untypischer_trennung.split("TRENNUNG"))
```
%% Output
['Das ist eine typische Trennung bei Zahlenwerten:', '1', '2.5', '4', '5.5']
['Das', 'ist', 'eine', 'untypische', 'Trennung']
%% Cell type:markdown id:a11a3c0e tags:
Mit diesen Grundlagen kann man für seine Programme bereits nahezu alles abspeichern und auch wieder einlesen, sofern man sich überlegt, wie die Datei strukturiert wird.
%% Cell type:markdown id:d6dd17cb tags:
### **<font color='blue'>Typische Datenformate</font>**
Es bietet sich an, nicht für jeden Anlass ein komplett neues Prinzip der Dateistruktur ( = Format) auszudenken. Viele Dinge sind für viele Datensätze gleich. So gibt es standardisierte Datenformate für verschiedene Zwecke. Ein großer Vorteil, diese zu verwenden, liegt darin, dass in Python (sowie anderen Sprachen und Programmen) Module zu Verfügung stehen, die diese Dateien unterstützen. In Python können wir so einfacher solche Daten Schreiben und Auslesen und müssen nicht jedes Zeichen einer potentiell ausgedachten Datenstruktur manuell auswerten. Das gleiche gilt für andere Sprachen. In anderen Programmen, kann das Format ggf. auch eingelesen werden. Zum Beispiel haben Tabellenkalkulationsprogramme wie Excel die Möglichkeit CSV-Dateien zu laden.
Wir wollen nun 3 Formate ansehen. csv, json und xml. Alle diese Formate sind ASCII-Dateien und weit verbreitet. Wir werden zunächst die Dateieformate zeigen und danach ein bisschen in die passenden Python Pakete sehen. Theoretisch könnte man auch jedes dieser Formate manuell schreiben (programmieren). In diesem Notebook geht es nicht darum, alle Details dazu zu erfassen, sondern hauptsächlich, diese Formate mal gesehen zu haben. Sehr wahrscheinlich wird man dem ein oder anderen Format davon später in Studium, Beruf oder Freizeit noch mal begegnen.
Hier zunächst ein grober Überblick über die Eigenschaften:
* csv (comma separated value)
* geeignet für Datenreihen in Tabellenform
* Sehr Speicherplatz schonend
* json (JavaScript Object Notation)
* geeignet für jegliche hierarchische Daten
* trotz "JavaScript" im Namen für alle Sprachen geeignet
* Speicherplatz schonend
* erfährt zunehmend weite Verbreitung, ist in vielen neuen Programmen/Systemen der Standard
* xml (extensible markup language)
* geeignet für jegliche hierarchische Daten
* benötigt mehr Speicherplatz als json
* weit verbreitet, ist in vielen älteren Programmen/Systemen der Standard
### **<font color='blue'>Beispiele</font>**
#### **<font color='blue'>Beispiel: Tabelle</font>**
Wir wollen uns die Datenformate nun anhand zweier (programm-unabhänger) Beispieldatensätze ansehen. Das erste Beispiel sind Datenreihen für Ergebnisse eines Kart-Rennens:
%% Cell type:markdown id:238f45d7 tags:
![Karting.PNG](attachment:Karting.PNG)
%% Cell type:markdown id:ab2d96dc tags:
#### **csv**
Csv steht für "Comma separated value", also "durch Kommata getrennte Werte". Dieser Name ist etwas streng, man müsste eher "durch Trennzeichen getrennte Werte" dazu sagen. Oft wird nämlich kein Komma, sondern ein Semikolon genutzt. Typischerweise wird so eine Datei in Spalten und Zeilen aufgeteilt, wobei die Spalten durch das gewählte Trennzeichen und die Zeilen durch einen Zeilenumbruch getrennt werden. Die Kartergebnisse sähen z.B. folgendermaßen aus.
```
"Kart","Runde 1","Runde 2"
1,56.5,55.7
2,55.2,55.1
3,57.1,56.3
```
Hier ist als Trennzeichen das "," gewählt, und Texte sind in Anführungszeichen gestellt. Das wäre nicht zwingend nötig, erleichtert aber die automatisierte Auswertung, da klar ist, was als Zahl ausgewertet werden soll und was nicht. Aufgrund der minimalistischen Schreibweise, in der fast nur die Werte ohne Erklärung stehen, ist das Format sehr speicherplatzschonend. CSV Daten können von den gängigen Tabellenverarbeitungsprogrammen gelesen und geschrieben werden.
%% Cell type:markdown id:b67df868 tags:
#### **json**
Json steht für "JavaScript Object Notation". Trotz Javascript im Namen kann das Format in jeder Sprache genutzt werden. Das Format ist fast identisch mit (verschachtelten) Dictionaries und Listen in Python. Es gibt immer einen Schlüssel (key) und den dazugehörigen Wert hinter einem Doppelpunkt. Dieser Wert kann auch eine Liste von Werten, oder von Dictionaries sein.
Kleie Unterschiede zur Python Darstellung sind z.B. das kleine ````true```` oder ````false```` (in Python groß: ````True````,````False````) oder ````null```` statt Pythons ````none````. Das äußerste Element kann entweder eine Liste oder ein Dictionary sein.
Die Kartergebnisse sähen z.B. folgendermaßen aus.
```
[
{
"Kart": 1,
"Runde 1": 56.5,
"Runde 2": 55.7
},
{
"Kart": 2,
"Runde 1": 55.2,
"Runde 2": 55.1
},
{
"Kart": 3,
"Runde 1": 57.1,
"Runde 2": 56.3
}
]
```
Die Einrückungen sind optional und dienen der Lesbarkeit. Es braucht etwas mehr Speicherplatz als csv, da jeder Key wiederholt wird. Diese Liste von Ergebnissen könnten wir zum Beispiel direkt in Python einlesen und so verwenden, als hätten wir sie in Python angelelegt. JSON wird zunehmend auf breiter Basis eingesetzt.
%% Cell type:markdown id:a0dc8519 tags:
#### **xml**
Xml steht für "Extensible Markup Language". Das Format wurde als Menschen- und Maschinenlesbares Format für hierarchische Daten entwickelt. Das Prinzip basiert auf dem Öffnen und Schließen von *tags*, die gewissermaßen die Knoten in einer Baumstruktur darstellen. Geöffnet wird mit ````<tag_name>````, geschlossen wird mit ````</tag_name>````. Die Daten stehen als freier Text zwischen den tags. Die Kartergebnisse sähen z.B. folgendermaßen aus:
```
<Ergebnisse>
<Kart>
<Nr>1</Nr>
<Runde 1>56.5</Runde 1>
<Runde 2>55.7</Runde 2>
</Kart>
<Nr>2</Nr>
<Runde 1>55.2</Runde 1>
<Runde 2>55.1</Runde 2>
<Kart>
</Kart>
<Kart>
<Nr>3</Nr>
<Runde 1>57.1</Runde 1>
<Runde 2>56.3</Runde 2>
</Kart>
</Ergebnisse>
```
Auch hier sind die Einrückungen optional und dienen der Übersicht. XML wurde vor json entwickelt und ist der Standard in vielen bewährten Programmen und Anwendungen geworden. Der Speicherplatzverbrauch ist etwas höher als bei json, da hier nun jeder Tag sowohl geöffnet, als auch geschlossen wird, was Text erzeugt.
%% Cell type:markdown id:04a4474e tags:
#### **<font color='blue'>Beispiel: Hierarchische Datenstruktur</font>**
Das zweite Beispiel ist ein etwas komplexerer hierarchischer Datansatz einer Bibliothek, die Bücher und Zeitschriften verwaltet, wobei Zeitschriften nochmal in Ausgaben und Artikel aufgeteilt werden. Im folgenden Bild ist der vollständige Datensatz als Baum aufgezeichnet:
%% Cell type:markdown id:4f4c2176 tags:
![Hierarchisch_klein.PNG](attachment:Hierarchisch_klein.PNG)
%% Cell type:markdown id:425796d3 tags:
#### **csv**
Csv ist nicht für hierarchische Datenstrukturen entwickelt worden. Die Daten müssen alle irgendiwe in einer Tabelle angeordnet werden. Im folgenden Beispiel wird die CSV Datei so geschrieben, dass in jeder Zeile entweder ein Buch oder ein Artikel steht, und in den Spalten alle möglichen Attribute (wobei zum Beispiel zwischen B_Titel, ZS_titel und Art_Titel unterschieden werden muss). Zutreffende Felder werden ausgefüllt, nicht zutreffende nicht. Auf die optionalen Anführungszeichen wird verzichtet, da ohnehin fast alles als Text ausgewertet werden soll. Zwar lassen sich die Daten so relativ kompakt aufschreiben, aber es ersichtlich, dass csv nicht sonderlich gut für die Repräsentation solcher hierarchischer (baumartiger) Strukturen geeignet ist.
```
B_Titel,B_Autor,B_Jahr,ZS_titel,ZS_Verlag,ZS_Ausg_Nr,Art_Titel,Art_Autor
Das erste Buch,M. Muster,2013,,,,,
Ein Beispielbuch,E.Beispiel,2022,,,,,
,,,Die beste Zeitschrift,TOPVerlag,1/2023,Editorial,A. Top
,,,Die beste Zeitschrift,TOPVerlag,1/2023,Untersuchung von xy,C. Schlau
,,,Die beste Zeitschrift,TOPVerlag,2/2023,,
```
Die Baumstruktur geht nicht optisch daraus hervor, und wir nennen die Zeitschrift mehrfach. Man könnte dies auch sicher über eine Indizierung lösen, aber der Kern dieser Darstellung ist, dass CSV nicht das beste Format für solche Strukturen ist und man sich gut überlegen muss, wie man die Tabelle am besten gestaltet. CSV glänzt eher im Speichern von vielen und großen gleichberechtigten Zahlenreihen, wie zum Beispiel als Ergebnis einer zeitlich abhängigen Bewegungssimulation: Zeit, Kraft_x, Kraft_y, Kraft_z, Moment_x, Moment_y, Moment_z, Ort_x, Ort_y, Ort_z [etc.]
%% Cell type:markdown id:a3d2a6ed tags:
#### **json**
In json wird nicht viel anders gemacht, als beim Kartbeispiel, abgesehen davon, dass es einige Verschachtelungen mehr gibt:
```
{
"Buecher": [
{
"Titel": "Das erste Buch",
"Autor": "M. Muster",
"Jahr": 2013
},
{
"Titel": "Ein Beispielbuch",
"Autor": "E Beispiel",
"Jahr": 2012
}
],
"Zeitschriften": [
{
"Titel": "Die beste Zeitschrift",
"Verlag": "TOPVerlag",
"Ausgaben": [
{
"Nr": "1/2023",
"Artikel": [
{
"Titel": "Editorial",
"Autor": "A. Top"
},
{
"Titel": "Untersuchung von xy",
"Autor": "C. Schlau"
}
]
},
{
"Nr": "2/2023",
"Artikel": []
}
]
}
]
}
```
Die Struktur der Daten wird wesentlich einfacher ersichtlich. Nach wie vor könnte dieses Dictionary direkt in Python importiert und verwendet werden.
%% Cell type:markdown id:2c43d5db tags:
#### **xml**
In XML sind nun einige Knoten mehr als beim Kart-Beispiel enthalten, aber auch hier bleibt das Prinzip gleich.
```
<Bibliothek>
<Buecher>
<Buch>
<Titel>Das erste Buch</Titel>
<Autor>M. Muster</Autor>
<Jahr>2013</Jahr>
</Buch>
<Buch>
<Titel>Ein Beispielbuch</Titel>
<Autor>E. Beispiel</Autor>
<Jahr>2022</Jahr>
</Buch>
</Buecher>
<Zeitschriften>
<Zeitschrift>
<Titel>Die beste Zeitschrift</Titel>
<Verlag>TOPVerlag</Verlag>
<Ausgaben>
<Ausgabe>
<Nr>1/2023</Nr>
<AlleArtikel>
<Artikel>
<Titel>Editorial</Titel>
<Autor>A. Top</Autor>
</Artikel>
<AlleArtikel>
<Titel>Untersuchung von xy</Titel>
<Autor>C. Schlau</Autor>
</AlleArtikel>
</AlleArtikel>
</Ausgabe>
<Ausgabe>
<Nr>2/2023</Nr>
<Artikel/>
</Ausgabe>
</Ausgaben>
</Zeitschrift>
</Zeitschriften>
</Bibliothek>
```
Im Vergleich zu json muss man mehr lesen (und die Datei ist daher größer), aber auch bei xml sind die Daten deutlich selbsterklärender, als im csv-Beispiel.
%% Cell type:markdown id:08431e54 tags:
### **<font color='blue'>Welches Datenformat?</font>**
Welches Datenformat verwendet wird, hängt also immer vom Anwendungsfall und auch von der bisher bestehenden Architektur ab (wenn man keine Anwendung von Grund auf entwickelt). Grundsätzlich ist CSV sehr gut für Tabellen geeignet, während json und xml für hierarchische Daten besser geeignet sind. json ist etwas platzsparender und daher schneller und ist für neue Anwendungen eine gute Wahl. Es wird auch stark zunehmend, insbesondere für Webanwendungen, genutzt. XML ist etwas weniger platzsparend als json, aber in vielen bestehenden Anwendungen und Architekturen der etablierte standard. XML bietet noch einige Features, die wir im Rahmen dieses Notebooks nicht besprechen können (Attribute, Schemata), und wird als flexibler bezeichent.
%% Cell type:markdown id:5932056c tags:
### **<font color='blue'>Verwendung in Python</font>**
Für alle diese Formate gibt es (gleichnamige) Module in Python, die die Arbeit mit diesen Formaten erleichtern. So muss man nicht manuell einprogrammieren, wie die Formatierung gemacht werden soll.
#### **csv**
Das Paket ````csv```` enthält Klassen für das Lesen und Schreiben von csv Dateien. Dabei kann eine Formatierung (z.B. Trennzeichen für Spalten (````delimiter = ''````) und Anführungszeichen bei Texten (````quoting = csv.QUOTE_NONNUMERIC````) etc.) beim Anlegen konfiguriert werden. Mit diesen Objekten lassen sich dann Felder von Feldern direkt schreiben oder auslesen. Die für Zahlenreihen wichtigsten Optionen sind im folgenden Beispiel (Kart) gezeigt und ausführlich kommentiert:
%% Cell type:code id:c0f40ef4 tags:
``` python
# Zunaechst bereiten wir die Daten vor
# Daten anlegen
kart_1 = [56.5,55.7]
kart_2 = [55.2,55.1]
kart_3 = [57.1,56.3]
# Wir wollen die Kartnummer in der Tabelle haben und legen ein Feld mit den Feldern für jedes Kart an
alle_karts = [[1]+kart_1, [2]+kart_2, [3]+kart_3]
# Nun CSV
import csv
# Beim oeffnen geben wir bereits .csv an, das hat allerdings noch keine besondere Bedeutung. Wir koennen immer noch in die Datei schreiben was wir wollen.
# newline = '' ist fuer das csv modul noetig, da es selbst Steuerzeichen fuer den Zeilenumbruch setzt
with open('karts.csv','w',newline='') as f:
# Nun erstellen wir den csv writer und uebergeben diesem das Datei-Objekt f, sowie hier den optionalen Parameter "quoting" fuer das Setzen von anfuehrungszeichen bei Text
writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC)
# Nun koennen wir mit writerow() eine Liste in eine Zeile schreiben. Wir machen dies fuer die Ueberschriften
writer.writerow(["Kart","Runde 1", "Runde 2"])
# Mit writerows() koennen wir eine Liste von Listen uebergeben. Damit wird fuer jede innere Liste writerow() aufgerufen.
# Unsere Rundendaten sind solche Listen von Listen. Somit koennen wir mit dem einen Befehl den Rest der Daten schreiben.
writer.writerows(alle_karts)
# Zum Lesen Erstellen wir zuerst eine Liste für die inneren Listen
rows = []
#Das Oeffnen zum Lesen funktioniert analog zum Schreiben (Modus "r"). Wir erstellen analog zum csv-writer objekt ein csv-reader objekt
with open('karts.csv','r',newline='') as f:
reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
# nun koennen wir "reader" verwenden wie eine Liste der Zeilen (die wir allerdings nur ein einziges mal von vorne nach hinten durchlaufen koennen)
# Wir iterieren also ueber "reader" und speichern alle Zeilen in "rows"
for line in reader:
rows.append(line)
# Nun ist in rows der gesamte Datensatz gespeichert. Wir koennen ihn verwenden, wie wir moechten. Z.B. ausgeben:
print(f"Direkte Ausgabe: {rows}")
#Oder eine formatiertere Ausgabe:
print("formatierte Ausgabe")
for line in rows:
for element in line:
print(f"{element} \t", end ="") # \t ist ein Steuerzeichen für "tab", end="" verhindert einen Zeilenumbruch
print("") # Dieses print erzeugt den Zeilenumbruch fuer das naechste Kart
```
%% Output
Direkte Ausgabe: [['Kart', 'Runde 1', 'Runde 2'], [1.0, 56.5, 55.7], [2.0, 55.2, 55.1], [3.0, 57.1, 56.3]]
formatierte Ausgabe
Kart Runde 1 Runde 2
1.0 56.5 55.7
2.0 55.2 55.1
3.0 57.1 56.3
%% Cell type:markdown id:602a06df tags:
#### **json**
Das Paket ````json```` enthält Funktionen für das Konvertieren (````json.dump()````) von Python-Dictionaries in json-Strings und umgekehrt (````json.loads()````). Diese Strings können mit den Attribut ````indent = 4```` zur besseren Übersicht eingerückt werden. Die Strings können direkt eine Datei geschrieben werden und nach dem Auslesen in ein Dictionary konvertiert werden. Das folgende Beispiel zeigt wieder die Karts:
%% Cell type:code id:c0ea1ff7 tags:
``` python
# Die Rohdaten sind oben bereits bei CSV in "alle_karts" angelegt, wir erstellen eine passende Liste mit Dictionaries:
karts_dict_list = []
for kart_daten in alle_karts:
karts_dict_list.append({"Kart":kart_daten[0], "Runde 1":kart_daten[1], "Runde 2":kart_daten[2]})
# In diesem Dictionary sind nun die Daten gespeichert. Wir importieren json und erstellen den passenden string mit Einrueckung
import json
json_string = json.dumps(karts_dict_list, indent = 4)
#Kontrollausgabe
print(json_string)
#Dies string koennen wir mit den oben vorgestellten tools in eine Datei schreiben:
with open("karts.json","w") as f:
f.write(json_string)
#Beim Lesen haengen wir jede Zeile an eine Zeichenkette an, damit wir keine Liste von Zeichenketten, sondern nur eine einzige Zeichenkette erhalten
with open("karts.json","r") as f:
json_from_file = ""
for line in f.readlines():
json_from_file+=line
#Kontrollausgabe
print(json_from_file)
#Um eine Liste aus dem String zu erhalten nutzen wir json.loads()
imported_list = json.loads(json_from_file)
#Kontrollausgaben
print(imported_list[0])
print(imported_list[0]["Runde 1"])
```
%% Output
[
{
"Kart": 1,
"Runde 1": 56.5,
"Runde 2": 55.7
},
{
"Kart": 2,
"Runde 1": 55.2,
"Runde 2": 55.1
},
{
"Kart": 3,
"Runde 1": 57.1,
"Runde 2": 56.3
}
]
[
{
"Kart": 1,
"Runde 1": 56.5,
"Runde 2": 55.7
},
{
"Kart": 2,
"Runde 1": 55.2,
"Runde 2": 55.1
},
{
"Kart": 3,
"Runde 1": 57.1,
"Runde 2": 56.3
}
]
{'Kart': 1, 'Runde 1': 56.5, 'Runde 2': 55.7}
56.5
%% Cell type:markdown id:04e13d48 tags:
#### **xml**
Das Paket ````xml```` enthält sehr viele Funktionen für das Arbeiten mit xml-Daten und den entsprechenden Baumstrukturen. Wir behandeln hier nur die grundlegendsten.
***Baum anlegen***
Um eine Baumstruktur einzulesen oder zu schreiben wird das Unterpaket ````xml.etree.ElementTree```` als ````ET```` verwendet. Jeder Baum hat einen ersten Wurzelknoten. Dieser wird mit ````ET.Element(tag)```` angelegt. Diese Funktion legt einen Knoten an und gibt ein Objekt aus, das den von diesem Knoten ausgehenden Baum enthält. Unterknoten können mit ````ET.SubElement(elternknoten,tag)```` angelegt werden, wobei zuerst das Objekt des Elternknotens und anschließend der Tag (Name) übergeben werden. An diesen Knoten kann man weitere Unterknoten anschließen, oder einen Wert speichern. Um einen Wert zu speichern, weißt man dem Attribut ````.text```` des Objekts den gewünschten Wert zu. Für eine gültige xml muss der Wert als Zeichenkette eingespeichert werden. Im folgenden Beispiel wird der Baum für die Kartergebnisse angelegt:
%% Cell type:code id:5e92c505 tags:
``` python
import xml.etree.ElementTree as ET
# manuell angelegt (um die Struktur klar zu machen, in der Praxis unpraktikabel)
ergebnisse = ET.Element("Ergebnisse")
kart_1 = ET.SubElement(ergebnisse, "Kart")
kart_2 = ET.SubElement(ergebnisse, "Kart")
kart_3 = ET.SubElement(ergebnisse, "Kart")
kart_1_nr = ET.SubElement(kart_1, "Nr")
kart_1_nr.text = "1"
kart_1_R1 = ET.SubElement(kart_1, "Runde_1")
kart_1_R1.text = "56.5"
kart_1_R2 = ET.SubElement(kart_1, "Runde_2")
kart_1_R2.text = "55.7"
kart_2_nr = ET.SubElement(kart_2, "Nr")
kart_2_nr.text = "2"
kart_2_R1 = ET.SubElement(kart_2, "Runde_1")
kart_2_R1.text = "55.2"
kart_2_R2 = ET.SubElement(kart_2, "Runde_2")
kart_2_R2.text = "55.1"
kart_3_nr = ET.SubElement(kart_3, "Nr")
kart_3_nr.text = "3"
kart_3_R1 = ET.SubElement(kart_3, "Runde_1")
kart_3_R1.text = "57.1"
kart_3_R2 = ET.SubElement(kart_3, "Runde_2")
kart_3_R2.text = "56.3"
# In Schleife angelegt (praktikabel, da es bei exakt diesem Code (8 Zeilen) bleibt, auch wenn es 10 Karts und 20 Runden sind)
# Der manuelle Code fuer 3 Karts und 2 Runden hat 22 Zeilen. Bei 10 Karts und 20 Runden haette er 441 Zeilen.
# Das Feld "alle karts" mit den Unterfeldern fuer Nummer und Rundenzeiten steht noch zur Verfuegung
ergebnisse_neu = ET.Element("Ergebnisse")
for kart_daten in alle_karts:
current_kart = ET.SubElement(ergebnisse_neu, "Kart")
nr = ET.SubElement(current_kart, "Nr")
nr.text = str(kart_daten[0])
for runde_nummer in range(1,len(kart_daten)):
runde = ET.SubElement(current_kart, f"Runde_{runde_nummer}")
runde.text = str(kart_daten[runde_nummer])
```
%% Cell type:markdown id:d4fdd3cf tags:
Nun ist der Baum angelegt.
***Baum in Datei schreiben und aus Datei laden***
Wir können ihn mit ````ET.tostring(baum, encoding="unicode")```` in eine Zeichenkette mit der xml Syntax überführen. Der Parameter ````encoding ="unicode"```` sorgt für einen ASCII-String. Für die optisch hilfreichen Einrückungen steht ab Python Version 3.9 die Funktion ````ET.indent(baum, space=" ", level=0)```` zur Verfügung, die vor Erstellen des Strings aufgerufen werden muss. Diesen String können wir ganz konventionell in eine Datei schreiben.
%% Cell type:code id:5a6c1745 tags:
``` python
# Folgende Zeile nur ab Python 3.9
# ET.indent(ergebnisse, space=" ", level=0)
xml_string = ET.tostring(ergebnisse_neu, encoding="unicode")
print(xml_string)
with open("karts.xml","w") as f:
f.write(xml_string)
```
%% Output
<Ergebnisse><Kart><Nr>1</Nr><Runde_1>56.5</Runde_1><Runde_2>55.7</Runde_2></Kart><Kart><Nr>2</Nr><Runde_1>55.2</Runde_1><Runde_2>55.1</Runde_2></Kart><Kart><Nr>3</Nr><Runde_1>57.1</Runde_1><Runde_2>56.3</Runde_2></Kart></Ergebnisse>
%% Cell type:markdown id:4af0ceb8 tags:
Zum Einlesen steht entweder die Funktion ````ET.fromstring(string)```` zur Verfügung, die den String erhält und ein Knotenobjekt für den Wurzelknoten ausgibt. Mit diesem kann auf den ganzen Baum zugegriffen werden, wie gleich gezeigt wird.
Es gibt zusätzlich noch eine Methode, mit der man direkt eine xml-Datei einlesen kann. Man muss dann also nicht mehr manuell die Datei Öffnen und des String auslesen. Diese Methode ist "ET.parse(dateiname)". Dies importiert den Baum. Um dann den Wurzelknoten als Objekt zu erhalten ruft man ````.getroot()```` in dem erhaltenen Objekt auf.
%% Cell type:code id:0ec639e5 tags:
``` python
#Variante 1 (allerdings hier mit dem noch im Speicher befindlichen xml_string, koennte aber auch wie bei json aus der Datei eingelesen werden)
ergebnisse = ET.fromstring(xml_string)
#Variante 2 (nun wirklich aus der Datei)
baum_importiert = ET.parse("karts.xml")
ergebnisse = baum_importiert.getroot()
```
%% Cell type:markdown id:6adb0fb8 tags:
***Zugriff auf die Daten im Baum***
Die Daten in einem Baum auslesen können wir folgendermaßen: Jedes Knoten-Objekt hat ein Attribut ````.tag````, in dem der Tag gespeichert ist. Im Attribut ````.text```` sind die Werte abgelegt. Das Objekt selbst ist sozusagen eine Liste mit seinen Unterknoten. Auf diese können wir mit dem Indexoperator zugreifen (Die Reihenfolge entspricht dabei dem Anlegen, bzw. dem eingelesenen xml file. Ein Knoten hat noch folgende nützliche Methoden: ````.findall(tag)```` gibt alle Unterknoten mit dem entsprechenden Tag zurück. ````.find(tag)```` gibt nur den ersten Unterknoten mit dem angegebenen Tag aus. ````.inter()```` durchläuft den ganzen Baum unter dem Element. Im Folgenden sind ein paar Beispiele gezeigt (Der Code wird in der Ausgabe immer mit gezeigt, man kann also die Ausgabe alleine ansehen):
%% Cell type:code id:a5962a27 tags:
``` python
print(f"ergebnisse.tag: {ergebnisse.tag}\n") #Zugriff auf Tag
print(f"ergebnisse[:]: {ergebnisse[:]}\n") #Zugriff auf Liste mit Unterelementen
print(f"ergebnisse[0].tag: {ergebnisse[0].tag}\n") #Zugriff auf erstes Unterlement und dessen Tag
print(f"ergebnisse[0][:]: {ergebnisse[0][:]}\n") #Zugriff auf Liste mit Unterelementen des ersten Unterelements
print(f"ergebnisse[0].find('Nr'): {ergebnisse[0].find('Nr')}\n") #Zugriff auf das Unterlement mit Tag "Nr" des ersten Unterelements
print(f"ergebnisse[0].find('Nr').text: {ergebnisse[0].find('Nr').text}\n") # Zugriff auf den Wert des eben gefundenen Unterelements
print(f"ergebnisse.findall('Kart'): {ergebnisse.findall('Kart')}\n") # Liste aller Unterelemente mit dem tag "Kart"
```
%% Output
ergebnisse.tag: Ergebnisse
ergebnisse[:]: [<Element 'Kart' at 0x00000272448247C0>, <Element 'Kart' at 0x0000027244824950>, <Element 'Kart' at 0x0000027244824E00>]
ergebnisse[0].tag: Kart
ergebnisse[0][:]: [<Element 'Nr' at 0x0000027244824770>, <Element 'Runde_1' at 0x00000272448244A0>, <Element 'Runde_2' at 0x0000027244824900>]
ergebnisse[0].find('Nr'): <Element 'Nr' at 0x0000027244824770>
ergebnisse[0].find('Nr').text: 1
ergebnisse.findall('Kart'): [<Element 'Kart' at 0x00000272448247C0>, <Element 'Kart' at 0x0000027244824950>, <Element 'Kart' at 0x0000027244824E00>]
%% Cell type:markdown id:6e3880da tags:
Diese Beispiele wirken etwas theoretisch (obwohl man teils geziehlt auf solche einzelnen Elemente zugreift). Im Folgenden ein paar praxisnähere Beispiele für Zugriffe auf den Baum. Wir beginnen mit einer Schleife über alle gefundenen Karts und geben die Nr. und Rundenzeit aus.
%% Cell type:code id:f35960a3 tags:
``` python
for kart in ergebnisse.findall('Kart'):
print(f"Kart: {kart.find('Nr').text}\nRunde 1: {kart.find('Runde_1').text}\nRunde 2: {kart.find('Runde_2').text}\n")
```
%% Output
Kart: 1
Runde 1: 56.5
Runde 2: 55.7
Kart: 2
Runde 1: 55.2
Runde 2: 55.1
Kart: 3
Runde 1: 57.1
Runde 2: 56.3
%% Cell type:markdown id:0132bac0 tags:
Ähnlich können wir auch die ````iter()```` Methode nutzen. Um zu verstehen, was bei der Methode passiert, geben wir zunächst nur alle Tags aus.
%% Cell type:code id:a973ba6a tags:
``` python
for element in ergebnisse.iter():
print(element.tag)
```
%% Output
Ergebnisse
Kart
Nr
Runde_1
Runde_2
Kart
Nr
Runde_1
Runde_2
Kart
Nr
Runde_1
Runde_2
%% Cell type:markdown id:6c0aebd6 tags:
Wir sehen also, dass der gesamte Baum von oben bis unten durchlaufen wurde. Geben wir nun auch alle Werte aus:
%% Cell type:code id:80503571 tags:
``` python
for element in ergebnisse.iter():
print(element.tag, element.text)
```
%% Output
Ergebnisse None
Kart None
Nr 1
Runde_1 56.5
Runde_2 55.7
Kart None
Nr 2
Runde_1 55.2
Runde_2 55.1
Kart None
Nr 3
Runde_1 57.1
Runde_2 56.3
%% Cell type:markdown id:75a170a7 tags:
Jetzt sind bereits alle Daten dargestellt. Allerdings könnte man die Formatierung verbessern und das ````None```` weglassen, wenn das Element keinen Text hat:
%% Cell type:code id:72253ee5 tags:
``` python
for element in ergebnisse.iter():
if element.text is None:
print(f"{element.tag}:")
else:
print(f" {element.tag}: {element.text}")
```
%% Output
Ergebnisse:
Kart:
Nr: 1
Runde_1: 56.5
Runde_2: 55.7
Kart:
Nr: 2
Runde_1: 55.2
Runde_2: 55.1
Kart:
Nr: 3
Runde_1: 57.1
Runde_2: 56.3
%% Cell type:markdown id:696ab624 tags:
# <font color='blue'>**"Binärdateien mit Pickle"**</font>
Als Alternative zu den ASCII-Dateien gibt es Binärdateien. Diese sind nicht menschenlesbar, und so sind diese nur für bestehende Programme nutzbar, oder wenn eine Dokumentation genau aufschlüsselt, wie die Datei strukturiert ist (Indebesondere wo welcher Datentyp verwendet werden muss, um die Binärzahl zu entschlüsseln).
In Python gibt es mit ````pickle```` ein nützliches Paket zum permanenten Speichern von aktuellen Objekten in der Session. Die Verwendung ist sehr einfach, aber man muss ein bisschen etwas beachten:
**Das Importieren von Objekten in eine neue Session ist nur dann erfolgreich, wenn eine dazu passende Klassendefinition verfügbar ist, und die Python Version die selbe ist. Ein Weglassen oder Ändern der Klassendefinition, oder ein Wechsel der Python Version kann also zur Folge haben, dass die gespeicherten Daten nicht erfolgreich eingelesen werden können.**
Um ein Objekt auf der Festplatte zu speichern, öffnet man konventionell eine Datei zum Schreiben (Dateiendung typischerweise .pkl) im ````mode = "wb"```` (Schreiben - binär) und beschreibt sie mit der Funktion ````pickle.dump(objekt, dateiobjekt)````.
So können wir zum Beipiel unsere Liste "alle_karts" (Objekt der Standard-Klasse Liste) mit pickle speichern:
%% Cell type:code id:3d1ced1a tags:
``` python
import pickle
with open("karts.pkl","wb") as f:
pickle.dump(alle_karts, f)
```
%% Cell type:markdown id:0bae701b tags:
Wenn man die entstandene Datei versucht, im Editor zu öffnen, kann man nur einen wilden Mix aus Zeichen und Hexadezimalzahlen erkennen, da der Editor die aneinander gereiten 0 und 1 des Binärformats nicht richtig interpretiert. Das gleiche passiert, wenn wir es im Modus "r" (ohne "b") öffnen:
%% Cell type:code id:196f7834 tags:
``` python
with open("karts.pkl","r") as f:
print(f.readlines())
```
%% Output
['€\x04•M\x00\x00\x00\x00\x00\x00\x00]”(]”(K\x01G@L@\x00\x00\x00\x00\x00G@KÙ™™™™še]”(K\x02G@K™™™™™šG@KŒÌÌÌÌÍe]”(K\x03G@LŒÌÌÌÌÍG@L&fffffee.']
%% Cell type:markdown id:bf281b0b tags:
Auch mit "rb" wird die Situation nicht viel besser. Nun wird zwar jeder Wert (byte) als Hexadezimalzahl dargestellt, aber wir haben trotzdem noch keine Information, wie die Werte interpretiert werden müssen.
%% Cell type:code id:994e1611 tags:
``` python
with open("karts.pkl","rb") as f:
print(f.readlines())
```
%% Output
[b'\x80\x04\x95M\x00\x00\x00\x00\x00\x00\x00]\x94(]\x94(K\x01G@L@\x00\x00\x00\x00\x00G@K\xd9\x99\x99\x99\x99\x9ae]\x94(K\x02G@K\x99\x99\x99\x99\x99\x9aG@K\x8c\xcc\xcc\xcc\xcc\xcde]\x94(K\x03G@L\x8c\xcc\xcc\xcc\xcc\xcdG@L&fffffee.']
%% Cell type:markdown id:eb0b8d31 tags:
Die uns fehlenden Informationen hat das pickle Modul. Deshalb können wir problemlos mit ````pickle.load(dateiobjekt)```` die gespeicherte Liste einlesen, nachdem wir die Datei im Modus ````"rb"```` geöffnet haben:
%% Cell type:code id:4f8ba3fc tags:
``` python
with open("karts.pkl","rb") as f:
list_from_pickle = pickle.load(f)
print(list_from_pickle)
```
%% Output
[[1, 56.5, 55.7], [2, 55.2, 55.1], [3, 57.1, 56.3]]
%% Cell type:markdown id:d5b35733 tags:
Eigene definierte Klassen und Objekte können genau so gespeichert werden. Da im pickle Format intern auf die Klasse nur verwiesen wird (also nicht die Klassendefinition gespeichert wird), muss beim Import die entsprechende Klassendefinition gegeben sein (z.B. durch Import als Modul: Speichern wir z.B. einen np.array mit pickle, muss zunächst auch numpy importiert werden, bevor die pickle Datei erfolgreich importiert wird).
%% Cell type:markdown id:e10b6cba tags:
# <font color='blue'>**Übung 10 - Erweiterung der Fallmodellierung um weitere Raumdimensionen und Abspeichern und Laden von Ergebnisdaten - SciPy, csv**
%% Cell type:markdown id:ece841a5 tags:
In dieser Übung möchten wir zunächst noch eine Erweiterung an der Modellierung des Falls durch die Luft vornehmen. Wir haben bisher nur eine Richtung betrachtet. Ein typischer Fallschirmsprung findet aus einem Flugzeug statt, das sich über die Erdoberfläche bewegt. Es könnte daher interessant sein, nicht nur die Höhe zu betrachten, sondern auch andere Raumrichtungen. Wir wollen uns dabei nur auf die Erweiterung um eine Raumrichtung konzentrieren, und vernachlässigen daher in dieser Übung die Komplexität durch Öffnen des Fallschirms aus der letzten Übung.
Nachdem wir gesehen haben, wie man Simulationen erzeugt, wollen wir uns ansehen, wir man die Ergebnisse oder auch Objekte (wie das Fachwerk) in Dateien speichern und aus Dateien lesen kann. Das ermöglicht langfristiges Abspeichern, das Verwenden der Daten in anderen Programmen, und das Weitergeben an andere Personen. Dabei wollen wir uns ein paar gängige Formate ansehen, die für verschiedene Zwecke verschieden geeignet sind.
### <font color='blue'>**Vorkenntnisse - Notebooks**
* Übung 1
* Übung 2
* Übung 4
* Übung 5
* Übung 9
* Grundlagen Python
* Grundlagen Matplotlib
* Grundlagen Numpy
* Grundlagen SciPy
### <font color='blue'>**Notebooks, die helfen können**
* Grundlagen Dateien
* darin nur die Abschnitte zu allgemeinen ASCII-Dateien und CSV für diese Übung notwendig. Json, xml und pickle können für diese Übung noch übersprungen werden. Achte darauf, dass im Notebook zuerst alle Formate allgemein vorgestellt werden und erst später die Umsetzung in Python. Es kommt also auch im hinteren Teil noch etwas zu csv dran. Ds ist aber durch die Überschriften deutlich erkennba
### <font color='blue'>**Lernziele**
* Erweiterung einer Simulation um weitere Variablen
* Lesen und Schreiben von Daten im csv Format - Datenreihen
%% Cell type:markdown id:eca5ef34 tags:
## <font color='blue'>**Problemstellung 1: 2D Fall (ohne Fallschirmöffnung)**
<font color='blue'>**Aufgabenstellung**
Die Berechnung mit *SciPy* aus Übung 9 soll nun nicht mehr eine Raumdimension haben, sondern Höhe (z) und Strecke (x), sodass beispielsweise ein Absprung aus einem Flugzeug simuliert werden kann. Um sich aufs wesentliche zu konzentrieren, kann in dieser Aufgabe ein Verändern der Parameter (also ein Öffnen des Schirms) vernachlässigt werden. Außerdem darf davon ausgegeangen werden, dass der Körper immer der Bewegunsrichtung nach ausgerichtet ist (das heißt A und C_W sind unabhängig von der Richtung). Zuletzt sollen die ermittelten Ortskoordinaten während des Falls in einem Diagramm dargestellt werden.
<font color='blue'>**Vorüberlegung**
Wir haben nun nicht mehr je eine Komponente für die Geschwindigkeit und den Ort, sondern beide Größen haben nun zwei Komponenten. Man könnte sich vorstellen, das wir statt Skalaren nun Vektoren in y schreiben könnten. Allerdings kann ````scipy.integrate.solve_ivp()```` nur eindimensionale Vektoren verarbeiten. Das ist aber kein Problem. Wir können einfach unsere Geschwindigkeitskomponenten und Koordinaten hintereinander in y schreiben (ohne sie zu einzelnen Vektoren zusammenzufassen). Wir definieren also y als [vx, vz, x, z].
Das bedeutet, wir müssen ein neues y_0 mit 4 Einträgen initialisieren und die DGL-Funktion so umschreiben, dass sie nun für jede der 4 Komponenten von y eine Änderungsrate zurückgibt. Zurückgeben müssen wir also den Vektor [ax, az, vx, vz].
Für diese Rückgabe sind vx und vz sehr leicht zu bestimmen, denn sie entsprechen den ersten beiden Einträgen von y.
Die Beschleunigung ist ein bisschen komplizierter:
Die Beschleunigung in einer Raumrichtung ist nach $F=ma$ die Summe aller Kräfte in diese Raumrichtung geteilt durch die Masse.
Die x-Richtung erfährt als Kraft nur einen Anteil des Luftwiderstands.
Die z-Richtung setzt sich aus einem Anteil des Luftwiderstands und der Erdgravitation zusammen.
Eine kleine Schwierigkeit stellt die Berechnung des Anteils des Luftwiderstands dar. Dieser hängt nämlich durch das Quadrat nichtlinear mit dem Geschwindigkeitsvektor zusammen. Das heißt, wir können nicht einfach die bisher benutzte Formel auf die beiden Geschwindigkeitskomponenten getrennt voneinander anwenden. Die Formel gibt uns nämlich eigentlich den Betrag der Luftwiderstandskraft entgegen der Bewegungsrichtung in Abhängigkeit des Betrags der Geschwindigkeit zum Quadrat an. Wenn wir die einzelnen Komponenten quadrieren, kommt aber ein anderer Vektor heraus, als der Vektor, der in die ursprüngliche Richtung zeigt und dessen Betrag dem Quadrat des ursprünglichen Betrags entspricht.
Das lässt sich leichter an einem Beispiel zeigen (auch im Bild grafisch dargestellt): Betrachten wir den Vektor (3,4) mit dem Betrag 5 (Pythagoras). Quadrieren wir die Einträge erhalten wir (9,16) mit dem Betrag 18.35, der auch in eine andere Richtung zeigt. Wir suchen aber für das Quadrat dieses Vektors eigentlich den Vektor mit dem quadrierten Betrag (5²=25), der immer noch in die gleiche Richtung wie (3,4) zeigt. Diesen erhalten wir durch Skalierung des Vektors mit seinem Betrag (4,3)*|(4,3)|= (4,3) * 5 =(15,20).
Um sicherzustellen, dass wir diese Berechnung später für die Berechnung der Widerstandskraft richtig verwenden, testen wir zunächst den Code mit den Zahlen aus dem Beispiel von eben.
%% Cell type:markdown id:dcb9f50b tags:
![Vektorquadrat.PNG](attachment:Vektorquadrat.PNG)
%% Cell type:markdown id:6602f0d2 tags:
**Hinweis** Dieses Notebook ist wieder als eine Art Lückentext mit ````'?'```` gestaltet, mit dem du üben kannst. Die Erklärungstexte und vorhandenen Codebausteine mit Kommentaren sollen Hilfestellung geben. Wenn du keinen Ansatz hast, versuche, in den Grundlagennotebooks oder alten Übungen etwas zu finden, ansonsten verwende gerne das Notebook mit Lösung (\_LSG) für Hilfestellungen.
%% Cell type:code id:eb4179de tags:
``` python
import scipy.integrate
import numpy as np
import matplotlib.pyplot as plt
```
%% Cell type:code id:8018b4f2 tags:
``` python
y = [4,3,0,0]
#Komponenten aus y fuer v
yx = '?'
yz = '?'
vektor_1 = np.array('?')
betrag_1 = np.linalg.norm('?')
# Achte beim quadrieren auf das oben besprochene. Quadrieren eines np.array mit **2 quadriert Komponentenweise
vektor_quadriert = '?'
#Kontrollausgabe
print(f"Vektor: {vektor_1}")
print(f"Betrag des Vektors: {betrag_1}")
print(f"Betrag zum Quadrat: {betrag_1**2}")
print(f"Vektor mit dem Betrag {betrag_1**2} durch Skalierung des ursprünglichen Vektors mit seinem (Betrag * Vektor): {vektor_quadriert}")
print(f"Probe: Betrag des erhaltenen Vektors: {np.linalg.norm(vektor_quadriert)}")
```
%% Cell type:markdown id:dd71b98b tags:
Dieser Code liefert uns die erwarteten Ergebnisse. Wir können also nun $\vec{v}\cdot|v|$ nutzen, um die korrekten Komponenten von $v^2$ in der Luftwiderstandskraft zu verwenden. Ein praktischer Nebeneffekt ist, dass nun die Vorzeichen erhalten bleiben. Das war beim skalaren quadrieren nicht der Fall und wir mussten die Richtung der Bewegung explizit berücksichtigen. Hier müssen wir nur daran denken, dass die Kraft nicht in Bewegungsrichtung, sondern entgegen zeigt, also ein negatives Vorzeichen benötigt.
%% Cell type:markdown id:a7cca46b tags:
<font color='blue'>**Umsetzung**
Alle Änderungen im Vergleich zur Übung 9 sind bereits in der Vorüberlegung behandelt worden. Analog zu Übung 9 legen wir nun also zuerst die Konstanten, Anfangswerte (4 Komponenten) und Zeitintervall fest und schreiben dann die DGL-Funktion, die die Änderungsraten von y ausgibt. Dabei beginnen wir mit vx und vz, und nutzen diese Werte, um den Geschwindigkeitsvektor analog zum eben getesteten Code zu bilden und den Betrag zu berechnen. Wir berechnen den Vektor der Widerstandskraft und verwenden deren Komponenten in den beiden Kräftebilanzen. In der z-Komponente kommt noch die Gewichtskraft hinzu und die Kräfte werden durch die Masse m geteilt, um die Beschleunigung a zu erhalten. Zuletzt rufen wir ````scipy.integrate.solve_ivp()```` und übergeben wie in Übung 9 die vorbereiteten Parameter.
%% Cell type:code id:fa5e2b56 tags:
``` python
#Konstanten (Werte wie in UE9)
'?'
A = 0.05 #Mehr Widerstand als in UE9, damit man im x,z Plot etwas davon sieht.
#Anfangswerte und Zeitintervall (z.B. Werte Geschindigkeit horizontal: 70, vertikal: 10. Position: Strecke 0, Hoehe 500. Intervall 20s)
vx_0, vz_0 = '?'
x_0, z_0 = '?'
y_0 = '?'
t_span = '?'
#In der Funktion wird nun die Gleichung für jede entsprechende Komponente aufgeschrieben.
def func_2D('?'):
# Komponenten von v
vx ='?'
vz ='?'
# Vektor und Betrag v
vektor_v = '?'
betrag_v = '?'
# Vektor der Widerstandskraft
F_W = '?'*vektor_v*'?'
# Berechnen der Komponenten der Beschleunigung
ax = -F_W['?']/m
az = '?'
return '?'
# Aufruf der Lösungsmethode (maximale Schrittweite 0.25)
sol_2d = '?'
```
%% Cell type:markdown id:227968fc tags:
Die Ergebnisse stellen wir als x,z-Plot dar. Da es sich hierbei um zwei räumliche Koordinaten handelt, ist es sinnvoll, die beiden Achsenskalierungen gleich zu wählen. Das geht mit dem Befehl ````plt.gca().set_aspect('equal')````. Dabei stellt man fest, dass der Plot sehr klein wird. Wir verwenden also ````plt.figure(figsize=(3,8))````, um eine gewisse Größe beizubehalten. Die darin eingetragenen Werte sind dabei am leichtesten per Ausprobieren zu bestimmen, bis der Plot gut aussieht. Teste gerne ein wenig herum. Die Ergebisse von x und z werden wie bekannt als Linie dargestellt. Um ein Gefühl für die Entwicklung der Geschwindigkeit und ihrer Komponenten in den Plot zu integrieren stellen wir noch jedes 8. Wertepaar (auch diese Zahl ist durch probieren, was gut aussieht entschieden worden) per ````plt.scatter()```` als Punkte dar. Einmal auf der Bewegungslinie, und einmal auf Achse. Je größer der Abstand, desto höher die Geschwindigkeit
%% Cell type:code id:20a9a25e tags:
``` python
#Der Plot ist etwas detaillierter erstellt, als in vorherigen Übungen. Dies dient der besseren Visualisierung
plt.'?' #Zur Steuerung der Bildgröße, die Werte hier sind per trial and error ermittelt.
plt.'?' #Normale Plotlinie
plt.'?' #Gleiche Achsenskalierung. Da beide Achsen räumliche Distanzen sind, ist das sinnvoll.
#Die Scatter-Punkte geben eine grobe Vorstellung von der Geschwindigkeit (jeder 8. Wert). Grob, da die Zeitschrittweite nicht konstant ist.
#Punkte werden einmal auf der Linie dargestellt, und einmal nach Komponente getrennt (dazu wird eine der Komponenten mit 0 multipliziert)
plt.scatter('?'['?']['?'][::8], '?'[::8])
offset_x = 290
offset_z = -110
plt.scatter('?''[::8]'*0+offset_x, '?')
plt.scatter('?', '?'*0+'?')
#Achsenbeschriftung und anzeigen
'?'
```
%% Cell type:markdown id:efa8da00 tags:
Wir sehen nun also, welche Flugbahn bei diesem Absprung entsteht, dass in diesem Fall die Geschwindigkeit etwa konstant bleibt, allerdings mit der Zeit die horizontale Komponente nahezu vollständig in die vertikale Komponente übertragen wird.
Teste beliebige andere Fälle. Dabei kann es sehr gut sein, dass du die Einstellungen für den Plot entsprechend anpassen musst. Insbesondere das Intervall der Punkte beim Scatter und die Offsets, die bei den Komponentendarstellungen der Punkte die Position der "Linie" festlegen.
%% Cell type:markdown id:93649679 tags:
## <font color='blue'>**Problemstellung 2: Ergebnisdaten zum Archivieren und Austauschen abspeichern - csv**
Das Abspeichern von (Roh-)Daten ist in vielen Fällen wichtig. So kann man später noch auf die Daten zugreifen, ohne die Rechnung zu wiederholen, was sich insbesondere bei langen Laufzeiten lohnt. Man kann die Daten mit verschiedenen Skripten erstellen und verarbeiten, oder die Daten mit anderen teilen.
#### <font color='blue'>**Aufgabenstellung**
Die Datenreihen t, vx, vz, x und z aus der Problemstellung 1 sollen in einer Datei abgespeichert werden. Außerdem sollen die Daten aus der Datei wieder eingelesen werden.
#### <font color='blue'>**Vorüberlegung**
In Python können wir ASCII-basierte Dateien schreiben, wie wir möchten (das funktioniert nach Öfnnen einer zu schreibenden Datei mit ````write()```` analog zu ````print()````).
Wir möchten die Datenreihen für t, vx, vz, x und z in einer Art Tabelle abspeichern, in der jede Zeile einem Zeitschritt und jede Spalte einer der 5 Größen entspricht.
Ein typisches Dateiformat für solche in Tabellen darstellbaren Daten ist csv (Comma-Separated-Value). Dafür gibt es ein Package ````csv````, das uns die Arbeit etwas erleichtert, da wir uns nicht selbst um die Formatierung innerhalb der Datei kümmern müssen, wie es bei dem normalen Datei schreiben der Fall wäre.
%% Cell type:markdown id:7ad1cbf8 tags:
#### <font color='blue'>**Umsetzung**
Der CSV Writer hat eine Methode ````wrtiterow()````, die ein Feld erhält und eine Zeile in die Datei schreibt, in der jeder Feldeintrag enthalten ist (getrennt mit dem festgelegten Trennzeichen, standard ","). Wenn wir jetzt also die Einträge des Dictionaries an den Writer übergeben, wird eine der Datenreihen in eine Zeile geschrieben. Grundsätzlich wäre das auch möglich, aber wir hatten oben definiert, dass die Datenreihen in den Spalten stehen sollen. Das entspricht eher dem intuitiven Verständnis einer Wertetabelle. Wir müssen daher dafür sorgen, dass wir dem Writer für jeden Zeitschritt ein Feld mit den fünf Einträgen [t, vx, vz, x, z] übergeben. Es gibt die Methode ````writerows()````, die ein Feld von Feldern erhält und die in einem Zug alle in dem äußeren Feld enthaltenen inneren Felder in aufeinander folgende Zeilen schreibt. Ein solches Feld der Form [[t0,vx0,...],[t1,vx1,...],[...]] können wir erstellen, indem wir zunächst die fünf Datenreihen zusammen in ein np.array schreiben [t, vx, vz, ...]->[[t0,t1,t2,...],[vx0,vx1,vx2,...],...] und dieses dann transponieren. Das transponierte Array können wir dann vollständig der Methode ````writerows()```` übergeben. Wir verwenden beim Anlegen des Writer-Objects den Parameter ````quoting = csv.QUOTE_NONNUMERIC````. Dieser sorgt dafür, dass nicht-Zahlenwerte (also text) in Anführungszeichen gesetzt werden. Das ermöglicht später beim Wiedereinlesen ein automatisches Erkennen der Zahlenwerte.
%% Cell type:code id:c5804a13 tags:
``` python
import '?'
# Anlegen eines Datenfeldes, das wir dann transponieren und dem csv-writer uebergeben koennen
data = np.array([sol_2d["t"], '?', '?', '?', '?'])
#Ausgabe zum Prüfen der Felder
print(data[:,:4])
print("\n")
print(data.T[:4])
# Datei zum Schreiben oeffnen (Siehe Grundlagen NB. Dateiname ist beliebig waehlbar zb. "simulation_2D.csv")
with open('?') as '?':
writer = csv.writer('?') # Writer Objekt (beachte das Statement zu "quoting" im Text oben)
writer.'?'(['t','?']) # Überschriften, damit die Datei noch nachvollzogen werden kann
writer.'?'('?') # Schreiben des Datenfeldes
```
%% Cell type:markdown id:7a434228 tags:
Die geschriebene Datei sieht folgendermaßen aus (Wir verwenden hier nicht die csv-spezifischen Lesmethoden, da wir hier die Daten nicht verarbeiten, sondern nur 1:1 ausgeben möchten. Allerdings entstehen beim Ausgeben hier zusaätzliche Leerzeilen, diese stehen so nicht in der Datei). Du kannst auch die Datei ganz normal auf deinem Computer finden und öffnen, wie jede andere Textdatei auch.
%% Cell type:code id:556f7e3a tags:
``` python
print("Inhalt der Datei:\n\n")
#Datei zum Lesen Oeffnen
with open('?') as f:
lines = f.'?'[:5] #Lesen und Speichern der ersten 5 Zeilen
#Alle Zeilen ausgeben. end ="" in print verhindert einen Zeilenumbruch beim naechsten print
for line in lines:
print('?', end="")
print("[...]")
```
%% Cell type:markdown id:5f03cd53 tags:
Wir könnten die Datei auch ohne das Paket ````csv```` schreiben. Dann müssten wir allerdings manuell für das korrekte Format sorgen. Das könnte zum Beispiel so aussehen (wir nutzen hier das bereits vorbereitete Datenfeld):
%% Cell type:code id:34bc8829 tags:
``` python
with open("simulation_2D_manual.csv", "w") as f:
f.write('"t","vx", "?" \n') #Überschriften, alle Trennzeichen, Anführungszeichen und Zeilenumbrüche manuell
#Schleife über alle Zeilen im Datenfeld
for line in '?':
f.write(f"{line[0]},{line[1]},'?'") #Schreiben der einzelnen Spalten der Zeile. Trennzeichen und Zeilenumbruch manuell
```
%% Cell type:markdown id:0e573125 tags:
Als nächstes sollen die Daten wieder eingelesen werden. Hier ist es zwar das selbe Notebook, aber man könnte diese Datei in jedem beliebigen anderen Notebook oder Programm einlesen, ohne die Rechnung wiederholen zu müssen.
Zum Einlesen nutzen wir wieder das csv Paket. Mit dem Reader werden alle Zeilen nacheinander ausgelesen. Dazu iterieren wir über alle Elemente des readers (reader ist allerdings kein Feld. Einfach gesagt, kann man es nur genau einmal und nur von vorne bis hinten durchlaufen). Jedes Element ist ein Feld mit dem Inhalt der Zeile (getrennt durch die Trennzeichen). Dank der Option ````quoting=csv.QUOTE_NONNUMERIC```` werden alle Zahlen, die nicht mit Anführungszeichen versehen sind direkt als Zahl eingelesen (standardmäßig würden sie als Zeichen eingelesen). Wir hängen jede Zeile an ein von uns angelegtes Feld (hier ````rows````)an. So haben wir nach der Schleife ein Feld mit weiteren Feldern der Wertepaare (t, vx, vz, x, z) für jeden Zeitschritt.
Dieses möchten wir nun wieder in ein Dictionary schreiben, damit wir sie genau so behandeln können, wie die Lösungen aus der Berechnung. Dazu führen wir den Umformschritt wie vor dem Abspeichern durch, um in den inneren Felder nicht die Wertepaare eines Zeitschritts, sondern alle Zeitschritte einer Größe zu bekommen. Wir legen also ein Numpy array aus der Liste von Listen an (dabei Überspringen wir die erste Zeile, die ja die Überschriften enthält) und transponieren das Array. Anschließend legen wir ein Dictionary an und tragen die einzelnen Arrays aus dem großen Array ein. Dann haben wir ein Dictionary, das genau so einsetzbar ist, wie die Dictionaries, die aus der Werte gekommen sind.
%% Cell type:code id:10673537 tags:
``` python
rows = '?'
with open('?') as f:
reader = csv.reader('?')
for row in reader:
rows.append('?')
# Alle Zeilen ab der zweiten als np.array formatieren und transponieren, um aus [[x1,y1],[x2,y2]] [[x1,x2],[y1,y2]] zu machen
data_import = np.array('?').'?'
#Sortieren der jeweiligen Listen in das Dictionary
sol_imported={}
sol_imported["t"] = data_import['?']
sol_imported["y"] = '?' # In y wird also wie auch oben die Matrix (v,h) gespeichert.
```
%% Cell type:markdown id:7a2c5185 tags:
Nun ist die Lösung importiert. Wir testen das, indem wir den oben gezeigten Plot nun noch einmal mit der importierten Lösung erstellen.
%% Cell type:code id:e870d961 tags:
``` python
#Diese importierte Lösung kann nun genau so dargestellt werden, wie die originale Lösung. (Zur Erstellung des Plots, siehe oben)
'?'
```
%% Cell type:markdown id:a3461331 tags:
Auch hier möchte ich noch einmal zeigen, wie das ohne das CSV Paket aussehen kann (wir müssen uns also wieder manuell um die Formatierung kümmern):
%% Cell type:code id:3ea9d27a tags:
``` python
# Wir legen wieder ein Feld "rows" mit den Zeilen an. In dieses fügen wir die Zahlendaten jeder Zeile als Liste ein
rows = '?'
with open('?') as f:
content = '?'['?'] #wir überspringen die erste Zeile und speichern den Rest in content ab
#iteration über alle Zeilen, die nun immer als ein String eingelesen sind
for '?' in '?':
line_splitted = '?'.'?' #teilt den string an den "," in eine Liste aus den Teilstrings auf (siehe Grundlagennotebook Dateien)
current_row = []
for element in line_splitted:
# Anhangen des Werts des Elements als float an die Liste der Zeile
current_row.'?'('?'('?'))
'?' # Anhangen der aktuellen Zeile an die Liste mit allen Zeilen
#Weiteren koennen wir rows genau wie oben bei der Verwendung von csv weiterverarbeiten
data_import = '?'
# Zuletzt legen wir das Dictionary an und konvertieren die Listen zu np.arrays
sol_man_imported='?'
sol_man_imported["t"] = '?'
sol_man_imported["y"] = '?'
#zum Test ein paar Werte ausgeben
print("y: ",sol_man_imported["t"][:10], "\nvx: ", sol_man_imported["y"][0][:10])
```
%% Cell type:markdown id:ca033fad tags:
## <font color='blue'> **Schlussbemerkung**
Wir haben gesehen, dass wir mit relativ einfachen Mitteln die SciPy Lösungsmethode für DGL auf weitere Lösungsvariablen, wie z.B. mehrere Raumdimensionen erweitern können. Die eigentliche Schwierigkeit in Problemstellung 1 war das Übertragen des Quadrats der Geschwindigkeit für den Luftwiderstand als Vektor.
Dann haben wir eine Methode besprochen, mit der tabellarische Daten wie die Ergebnisse von Simulationen im Zeitbereich kompakt und speicherschonend und für andere Programme importierbar auf der Festplatte gespeichert werden können.
## <font color='blue'> **Ausblick**
Nicht alle Daten lassen sich so gut als Tabelle darstellen, wie die Ergebnisse dieser Übung. In der nächsten Übung werden wir (auch zur Wiederholung) eine kleine objektorientierte Datenstruktur aufbauen und Funktionen entwickeln, mit denen wir diese strukturierten Daten in geeigneten Dateien auf der Festplatte speichern können.
## <font color='blue'> **Aufgaben zum selbst probieren**
* Erweitere die 2D-Fallsimulation auf 3D.
* Erweitere die 2D/3D Fallsimulation mit der Öffnung eines Fallschirms (wie in Übung 9)
* Schreibe ein Skript, dass die Ergebnisdaten in einem csv-artigen ASCII Format abspeichert. Die Zeit soll mit drei Nachkommastellen, alle anderen Variablen nur mit je zwei Nachkommastellen eingetragen werden. Die Trennung zwischen den Werten sollen ausschließlich Leerzeichen (mindestens 3) sein. Schreibe ebenfalls ein Skript, das diese Daten einließt. Du kannst dazu das csv-Paket nutzen, oder das Format von Hand definieren.
* (Zusatz) Füge einen höhenabhängigen Wind hinzu (z.B. 0 m/s am Boden 25m/s in 1000m Höhe, linear interpoliert). Beachte, dass der Luftwiderstand von der Geschwindigkeit *gegenüber der Luft* abhängig ist. Der Ort ist weiterhin von der absoluten Geschwindigkeit abhängig. Überlege dir also, wie du auf den entsprechenden Geschwindigkeitsvektor für die Ermittlung des Widerstands und dessen Richtung kommst.
Füge deiner Kopie des Notebooks beliebig viele Codezellen hinzu, um die Aufgaben zu lösen.
%% Cell type:markdown id:e10b6cba tags:
# <font color='blue'>**Übung 10 - Erweiterung der Fallmodellierung um weitere Raumdimensionen und Abspeichern und Laden von Ergebnisdaten - SciPy, csv**
%% Cell type:markdown id:ece841a5 tags:
In dieser Übung möchten wir zunächst noch eine Erweiterung an der Modellierung des Falls durch die Luft vornehmen. Wir haben bisher nur eine Richtung betrachtet. Ein typischer Fallschirmsprung findet aus einem Flugzeug statt, das sich über die Erdoberfläche bewegt. Es könnte daher interessant sein, nicht nur die Höhe zu betrachten, sondern auch andere Raumrichtungen. Wir wollen uns dabei nur auf die Erweiterung um eine Raumrichtung konzentrieren, und vernachlässigen daher in dieser Übung die Komplexität durch Öffnen des Fallschirms aus der letzten Übung.
Nachdem wir gesehen haben, wie man Simulationen erzeugt, wollen wir uns ansehen, wir man die Ergebnisse oder auch Objekte (wie das Fachwerk) in Dateien speichern und aus Dateien lesen kann. Das ermöglicht langfristiges Abspeichern, das Verwenden der Daten in anderen Programmen, und das Weitergeben an andere Personen. Dabei wollen wir uns ein paar gängige Formate ansehen, die für verschiedene Zwecke verschieden geeignet sind.
### <font color='blue'>**Vorkenntnisse - Notebooks**
* Übung 1
* Übung 2
* Übung 4
* Übung 5
* Übung 9
* Grundlagen Python
* Grundlagen Matplotlib
* Grundlagen Numpy
* Grundlagen SciPy
### <font color='blue'>**Notebooks, die helfen können**
* Grundlagen Dateien
* darin nur die Abschnitte zu allgemeinen ASCII-Dateien und CSV für diese Übung notwendig. Json, xml und pickle können für diese Übung noch übersprungen werden. Achte darauf, dass im Notebook zuerst alle Formate allgemein vorgestellt werden und erst später die Umsetzung in Python. Es kommt also auch im hinteren Teil noch etwas zu csv dran. Ds ist aber durch die Überschriften deutlich erkennba
### <font color='blue'>**Lernziele**
* Erweiterung einer Simulation um weitere Variablen
* Lesen und Schreiben von Daten im csv Format - Datenreihen
%% Cell type:markdown id:eca5ef34 tags:
## <font color='blue'>**Problemstellung 1: 2D Fall (ohne Fallschirmöffnung)**
<font color='blue'>**Aufgabenstellung**
Die Berechnung mit *SciPy* aus Übung 9 soll nun nicht mehr eine Raumdimension haben, sondern Höhe (z) und Strecke (x), sodass beispielsweise ein Absprung aus einem Flugzeug simuliert werden kann. Um sich aufs wesentliche zu konzentrieren, kann in dieser Aufgabe ein Verändern der Parameter (also ein Öffnen des Schirms) vernachlässigt werden. Außerdem darf davon ausgegeangen werden, dass der Körper immer der Bewegunsrichtung nach ausgerichtet ist (das heißt A und C_W sind unabhängig von der Richtung). Zuletzt sollen die ermittelten Ortskoordinaten während des Falls in einem Diagramm dargestellt werden.
<font color='blue'>**Vorüberlegung**
Wir haben nun nicht mehr je eine Komponente für die Geschwindigkeit und den Ort, sondern beide Größen haben nun zwei Komponenten. Man könnte sich vorstellen, das wir statt Skalaren nun Vektoren in y schreiben könnten. Allerdings kann ````scipy.integrate.solve_ivp()```` nur eindimensionale Vektoren verarbeiten. Das ist aber kein Problem. Wir können einfach unsere Geschwindigkeitskomponenten und Koordinaten hintereinander in y schreiben (ohne sie zu einzelnen Vektoren zusammenzufassen). Wir definieren also y als [vx, vz, x, z].
Das bedeutet, wir müssen ein neues y_0 mit 4 Einträgen initialisieren und die DGL-Funktion so umschreiben, dass sie nun für jede der 4 Komponenten von y eine Änderungsrate zurückgibt. Zurückgeben müssen wir also den Vektor [ax, az, vx, vz].
Für diese Rückgabe sind vx und vz sehr leicht zu bestimmen, denn sie entsprechen den ersten beiden Einträgen von y.
Die Beschleunigung ist ein bisschen komplizierter:
Die Beschleunigung in einer Raumrichtung ist nach $F=ma$ die Summe aller Kräfte in diese Raumrichtung geteilt durch die Masse.
Die x-Richtung erfährt als Kraft nur einen Anteil des Luftwiderstands.
Die z-Richtung setzt sich aus einem Anteil des Luftwiderstands und der Erdgravitation zusammen.
Eine kleine Schwierigkeit stellt die Berechnung des Anteils des Luftwiderstands dar. Dieser hängt nämlich durch das Quadrat nichtlinear mit dem Geschwindigkeitsvektor zusammen. Das heißt, wir können nicht einfach die bisher benutzte Formel auf die beiden Geschwindigkeitskomponenten getrennt voneinander anwenden. Die Formel gibt uns nämlich eigentlich den Betrag der Luftwiderstandskraft entgegen der Bewegungsrichtung in Abhängigkeit des Betrags der Geschwindigkeit zum Quadrat an. Wenn wir die einzelnen Komponenten quadrieren, kommt aber ein anderer Vektor heraus, als der Vektor, der in die ursprüngliche Richtung zeigt und dessen Betrag dem Quadrat des ursprünglichen Betrags entspricht.
Das lässt sich leichter an einem Beispiel zeigen (auch im Bild grafisch dargestellt): Betrachten wir den Vektor (3,4) mit dem Betrag 5 (Pythagoras). Quadrieren wir die Einträge erhalten wir (9,16) mit dem Betrag 18.35, der auch in eine andere Richtung zeigt. Wir suchen aber für das Quadrat dieses Vektors eigentlich den Vektor mit dem quadrierten Betrag (5²=25), der immer noch in die gleiche Richtung wie (3,4) zeigt. Diesen erhalten wir durch Skalierung des Vektors mit seinem Betrag (4,3)*|(4,3)|= (4,3) * 5 =(15,20).
Um sicherzustellen, dass wir diese Berechnung später für die Berechnung der Widerstandskraft richtig verwenden, testen wir zunächst den Code mit den Zahlen aus dem Beispiel von eben.
%% Cell type:markdown id:dcb9f50b tags:
![Vektorquadrat.PNG](attachment:Vektorquadrat.PNG)
%% Cell type:code id:eb4179de tags:
``` python
import scipy.integrate
import numpy as np
import matplotlib.pyplot as plt
```
%% Cell type:code id:8018b4f2 tags:
``` python
y = [4,3,0,0]
yx = y[0]
yz = y[1]
vektor_1 = np.array([yx, yz])
betrag_1 = np.linalg.norm(vektor_1)
vektor_quadriert = vektor_1*betrag_1
#Kontrollausgabe
print(f"Vektor: {vektor_1}")
print(f"Betrag des Vektors: {betrag_1}")
print(f"Betrag zum Quadrat: {betrag_1**2}")
print(f"Vektor mit dem Betrag {betrag_1**2} durch Skalierung des ursprünglichen Vektors mit seinem (Betrag * Vektor): {vektor_quadriert}")
print(f"Probe: Betrag des erhaltenen Vektors: {np.linalg.norm(vektor_quadriert)}")
```
%% Output
Vektor: [4 3]
Betrag des Vektors: 5.0
Betrag zum Quadrat: 25.0
Vektor mit dem Betrag 25.0 durch Skalierung des ursprünglichen Vektors mit seinem (Betrag * Vektor): [20. 15.]
Probe: Betrag des erhaltenen Vektors: 25.0
%% Cell type:markdown id:dd71b98b tags:
Dieser Code liefert uns die erwarteten Ergebnisse. Wir können also nun $\vec{v}\cdot|v|$ nutzen, um die korrekten Komponenten von $v^2$ in der Luftwiderstandskraft zu verwenden. Ein praktischer Nebeneffekt ist, dass nun die Vorzeichen erhalten bleiben. Das war beim skalaren quadrieren nicht der Fall und wir mussten die Richtung der Bewegung explizit berücksichtigen. Hier müssen wir nur daran denken, dass die Kraft nicht in Bewegungsrichtung, sondern entgegen zeigt, also ein negatives Vorzeichen benötigt.
%% Cell type:markdown id:a7cca46b tags:
<font color='blue'>**Umsetzung**
Alle Änderungen im Vergleich zur Übung 9 sind bereits in der Vorüberlegung behandelt worden. Analog zu Übung 9 legen wir nun also zuerst die Konstanten, Anfangswerte (4 Komponenten) und Zeitintervall fest und schreiben dann die DGL-Funktion, die die Änderungsraten von y ausgibt. Dabei beginnen wir mit vx und vz, und nutzen diese Werte, um den Geschwindigkeitsvektor analog zum eben getesteten Code zu bilden und den Betrag zu berechnen. Wir berechnen den Vektor der Widerstandskraft und verwenden deren Komponenten in den beiden Kräftebilanzen. In der z-Komponente kommt noch die Gewichtskraft hinzu und die Kräfte werden durch die Masse m geteilt, um die Beschleunigung a zu erhalten. Zuletzt rufen wir ````scipy.integrate.solve_ivp()```` und übergeben wie in Übung 9 die vorbereiteten Parameter.
%% Cell type:code id:fa5e2b56 tags:
``` python
#Konstanten
g = 9.81
m = 1
rho = 1.225
C_W = 0.18
A = 0.05 #Mehr Widerstand als in UE9, damit man im x,z Plot etwas davon sieht.
#Anfangswerte und Zeitintervall
vx_0, vz_0 = 70, 10
x_0, z_0 = 0, 500
y_0 = [vx_0, vz_0, x_0, z_0]
t_span = [0,20]
#In der Funktion wird nun die Gleichung für jede entsprechende Komponente aufgeschrieben.
def func_2D(t,y,g,m,rho,C_W,A):
# Komponenten von v
vx = y[0]
vz = y[1]
# Vektor und Betrag v
vektor_v = np.array([vx,vz])
betrag_v = np.linalg.norm(vektor_v)
# Vektor der W
F_W = 0.5*rho*vektor_v*betrag_v*C_W*A
ax = -F_W[0] /m
az = (-F_W[1] - m*g)/m
return [ax, az, vx, vz]
sol_2d = scipy.integrate.solve_ivp(func_2D, t_span, y_0, max_step = 0.25, args=(g, m, rho, C_W, A))
```
%% Cell type:markdown id:227968fc tags:
Die Ergebnisse stellen wir als x,z-Plot dar. Da es sich hierbei um zwei räumliche Koordinaten handelt, ist es sinnvoll, die beiden Achsenskalierungen gleich zu wählen. Das geht mit dem Befehl ````plt.gca().set_aspect('equal')````. Dabei stellt man fest, dass der PLot sehr klein wird. Wir verwenden also ````plt.figure(figsize=(3,8))````, um eine gewisse Größe beizubehalten. Die darin eingetragenen Werte sind dabei am leichtesten per Ausprobieren zu bestimmen, bis der Plot gut aussieht. Die Ergebisse von x und z werden wie bekannt als Linie dargestellt. Um ein Gefühl für die Entwicklung der Geschwindigkeit und ihrer Komponenten in den Plot zu integrieren stellen wir noch jedes 8. Wertepaar (auch diese Zahl ist durch probieren, was gut aussieht entschieden worden) per ````plt.scatter()```` als Punkte dar. Einmal auf der Bewegungslinie, und einmal auf Achse. Je größer der Abstand, desto höher die Geschwindigkeit
%% Cell type:code id:20a9a25e tags:
``` python
#Der Plot ist etwas detaillierter erstellt, als in vorherigen Übungen. Dies dient der besseren Visualisierung
plt.figure(figsize=(3,8)) #Zur Steuerung der Bildgröße, die Werte hier sind per trial and error ermittelt.
plt.plot(sol_2d["y"][2], sol_2d["y"][3]) #Normale Plotlinie
plt.gca().set_aspect('equal') #Gleiche Achsenskalierung. Da beide Achsen räumliche Distanzen sind, ist das sinnvoll.
#Die Scatter-Punkte geben eine grobe Vorstellung von der Geschwindigkeit (jeder 8. Wert). Grob, da die Zeitschrittweite nicht konstant ist.
#Punkte werden einmal auf der Linie dargestellt, und einmal nach Komponente getrennt (dazu wird eine der Komponenten mit 0 multipliziert)
plt.scatter(sol_2d["y"][2][::8], sol_2d["y"][3][::8])
offset_x = 290
offset_z = -110
plt.scatter(sol_2d["y"][2][::8]*0+offset_x, sol_2d["y"][3][::8])
plt.scatter(sol_2d["y"][2][::8], sol_2d["y"][3][::8]*0+offset_z)
#Achsenbeschriftung
plt.xlabel("x")
plt.ylabel("z")
plt.show()
```
%% Output
%% Cell type:markdown id:efa8da00 tags:
Wir sehen nun also, welche Flugbahn bei diesem Absprung entsteht, dass in diesem Fall die Geschwindigkeit etwa konstant bleibt, allerdings mit der Zeit die horizontale Komponente nahezu vollständig in die vertikale Komponente übertragen wird.
Teste beliebige andere Fälle. Dabei kann es sehr gut sein, dass du die Einstellungen für den Plot entsprechend anpassen musst. Insbesondere das Intervall der Punkte beim Scatter und die Offsets, die bei den Komponentendarstellungen der Punkte die Position der "Linie" festlegen.
%% Cell type:markdown id:93649679 tags:
## <font color='blue'>**Problemstellung 2: Ergebnisdaten zum Archivieren und Austauschen abspeichern - csv**
Das Abspeichern von (Roh-)Daten ist in vielen Fällen wichtig. So kann man später noch auf die Daten zugreifen, ohne die Rechnung zu wiederholen, was sich insbesondere bei langen Laufzeiten lohnt. Man kann die Daten mit verschiedenen Skripten erstellen und verarbeiten, oder die Daten mit anderen teilen.
#### <font color='blue'>**Aufgabenstellung**
Die Datenreihen t, vx, vz, x und z aus der Problemstellung 1 sollen in einer Datei abgespeichert werden. Außerdem sollen die Daten aus der Datei wieder eingelesen werden.
#### <font color='blue'>**Vorüberlegung**
In Python können wir ASCII-basierte Dateien schreiben, wie wir möchten (das funktioniert nach Öfnnen einer zu schreibenden Datei mit ````write()```` analog zu ````print()````).
Wir möchten die Datenreihen für t, vx, vz, x und z in einer Art Tabelle abspeichern, in der jede Zeile einem Zeitschritt und jede Spalte einer der 5 Größen entspricht.
Ein typisches Dateiformat für solche in Tabellen darstellbaren Daten ist csv (Comma-Separated-Value). Dafür gibt es ein Package ````csv````, das uns die Arbeit etwas erleichtert, da wir uns nicht selbst um die Formatierung innerhalb der Datei kümmern müssen, wie es bei dem normalen Datei schreiben der Fall wäre.
%% Cell type:markdown id:7ad1cbf8 tags:
#### <font color='blue'>**Umsetzung**
Der CSV Writer hat eine Methode ````wrtiterow()````, die ein Feld erhält und eine Zeile in die Datei schreibt, in der jeder Feldeintrag enthalten ist (getrennt mit dem festgelegten Trennzeichen, standard ","). Wenn wir jetzt also die Einträge des Dictionaries an den Writer übergeben, wird eine der Datenreihen in eine Zeile geschrieben. Grundsätzlich wäre das auch möglich, aber wir hatten oben definiert, dass die Datenreihen in den Spalten stehen sollen. Das entspricht eher dem intuitiven Verständnis einer Wertetabelle. Wir müssen daher dafür sorgen, dass wir dem Writer für jeden Zeitschritt ein Feld mit den fünf Einträgen [t, vx, vz, x, z] übergeben. Es gibt die Methode ````writerows()````, die ein Feld von Feldern erhält und die in einem Zug alle in dem äußeren Feld enthaltenen inneren Felder in aufeinander folgende Zeilen schreibt. Ein solches Feld der Form [[t0,vx0,...],[t1,vx1,...],[...]] können wir erstellen, indem wir zunächst die fünf Datenreihen zusammen in ein np.array schreiben [t, vx, vz, ...]->[[t0,t1,t2,...],[vx0,vx1,vx2,...],...] und dieses dann transponieren. Das transponierte Array können wir dann vollständig der Methode ````writerows()```` übergeben. Wir verwenden beim Anlegen des Writer-Objects den Parameter ````csv.QUOTE_NONNUMERIC````. Dieser sorgt dafür, dass nicht-Zahlenwerte (also text) in Anführungszeichen gesetzt werden. Das ermöglicht später beim Wiedereinlesen ein automatisches Erkennen der Zahlenwerte.
%% Cell type:code id:c5804a13 tags:
``` python
import csv
# Anlegen eines Datenfeldes, das wir dann transponieren und dem csv-writer uebergeben koennen
data = np.array([sol_2d["t"], sol_2d["y"][0], sol_2d["y"][1], sol_2d["y"][2], sol_2d["y"][3]])
#Ausgabe zum Prüfen der Felder
print(data[:,:4])
print("\n")
print(data.T[:4])
# Datei zum Schreiben oeffnen
with open("simulation_2D.csv",'w', newline='') as f:
writer = csv.writer(f, quoting=csv.QUOTE_NONNUMERIC) # Writer Objekt
writer.writerow(['t','vx','vz','x','z']) # Überschriften, damit die Datei noch nachvollzogen werden kann
writer.writerows(data.T) # Schreiben des Datenfeldes
```
%% Output
[[0.00000000e+00 2.47426240e-05 2.72168864e-04 2.74643126e-03]
[7.00000000e+01 6.99993249e+01 6.99925745e+01 6.99251444e+01]
[1.00000000e+01 9.99966083e+00 9.99626939e+00 9.96237826e+00]
[0.00000000e+00 1.73197533e-03 1.90508099e-02 1.92147358e-01]
[5.00000000e+02 5.00000247e+02 5.00002721e+02 5.00027413e+02]]
[[0.00000000e+00 7.00000000e+01 1.00000000e+01 0.00000000e+00
5.00000000e+02]
[2.47426240e-05 6.99993249e+01 9.99966083e+00 1.73197533e-03
5.00000247e+02]
[2.72168864e-04 6.99925745e+01 9.99626939e+00 1.90508099e-02
5.00002721e+02]
[2.74643126e-03 6.99251444e+01 9.96237826e+00 1.92147358e-01
5.00027413e+02]]
%% Cell type:markdown id:7a434228 tags:
Die geschriebene Datei sieht folgendermaßen aus (Wir verwenden hier nicht die csv-spezifischen Lesmethoden, da wir hier die Daten nicht verarbeiten, sondern nur 1:1 ausgeben möchten. Allerdings entstehen beim Ausgeben hier zusaätzliche Leerzeilen, diese stehen so nicht in der Datei). Du kannst auch die Datei ganz normal auf deinem Computer finden und öffnen, wie jede andere Textdatei auch.
%% Cell type:code id:556f7e3a tags:
``` python
print("Inhalt der Datei:\n\n")
#Datei zum Lesen Oeffnen
with open("simulation_2D.csv","r") as f:
lines = f.readlines()[:5] #Lesen und Speichern der ersten 5 Zeilen
#Alle Zeilen ausgeben. end ="" in print verhindert einen Zeilenumbruch beim naechsten print
for line in lines:
print(line, end="")
print("[...]")
```
%% Output
Inhalt der Datei:
"t","vx","vz","x","z"
0.0,70.0,10.0,0.0,500.0
2.47426239594269e-05,69.99932489223151,9.999660832062501,0.0017319753251635336,500.0002474220436
0.0002721688635536959,69.99257454870897,9.996269386305274,0.019050809923807782,500.00272118094546
0.0027464312594963854,69.92514444833192,9.96237826465535,0.19214735776814215,500.0274126378807
[...]
%% Cell type:markdown id:5f03cd53 tags:
Wir könnten die Datei auch ohne das Paket ````csv```` schreiben. Dann müssten wir allerdings manuell für das korrekte Format sorgen. Das könnte zum Beispiel so aussehen (wir nutzen hier das bereits vorbereitete Datenfeld):
%% Cell type:code id:34bc8829 tags:
``` python
with open("simulation_2D_manual.csv", "w") as f:
f.write('"t","vx","vz","x","y"\n') #Überschriften, alle Trennzeichen, Anführungszeichen und Zeilenumbrüche manuell
#Schleife über alle Zeilen im Datenfeld
for line in data.T:
f.write(f"{line[0]},{line[1]},{line[2]},{line[3]},{line[4]}\n") #Schreiben der einzelnen Spalten der Zeile. Trennzeichen und Zeilenumbruch manuell
```
%% Cell type:markdown id:0e573125 tags:
Als nächstes sollen die Daten wieder eingelesen werden. Hier ist es zwar das selbe Notebook, aber man könnte diese Datei in jedem beliebigen anderen Notebook oder Programm einlesen, ohne die Rechnung wiederholen zu müssen.
Zum Einlesen nutzen wir wieder das csv Paket. Mit dem Reader werden alle Zeilen nacheinander ausgelesen. Dazu iterieren wir über alle Elemente des readers (reader ist allerdings kein Feld. Einfach gesagt, kann man es nur genau einmal und nur von vorne bis hinten durchlaufen). Jedes Element ist ein Feld mit dem Inhalt der Zeile (getrennt durch die Trennzeichen). Dank der Option ````quoting=csv.QUOTE_NONNUMERIC```` werden alle Zahlen, die nicht mit Anführungszeichen versehen sind direkt als Zahl eingelesen (standardmäßig würden sie als Zeichen eingelesen). Wir hängen jede Zeile an ein von uns angelegtes Feld (hier ````rows````)an. So haben wir nach der Schleife ein Feld mit weiteren Feldern der Wertepaare (t, vx, vz, x, z) für jeden Zeitschritt.
Dieses möchten wir nun wieder in ein Dictionary schreiben, damit wir sie genau so behandeln können, wie die Lösungen aus der Berechnung. Dazu führen wir den Umformschritt wie vor dem Abspeichern durch, um in den inneren Felder nicht die Wertepaare eines Zeitschritts, sondern alle Zeitschritte einer Größe zu bekommen. Wir legen also ein Numpy array aus der Liste von Listen an (dabei Überspringen wir die erste Zeile, die ja die Überschriften enthält) und transponieren das Array. Anschließend legen wir ein Dictionary an und tragen die einzelnen Arrays aus dem großen Array ein. Dann haben wir ein Dictionary, das genau so einsetzbar ist, wie die Dictionaries, die aus der Werte gekommen sind.
%% Cell type:code id:10673537 tags:
``` python
rows = []
with open("simulation_2D.csv",'r', newline='') as f:
reader = csv.reader(f, quoting=csv.QUOTE_NONNUMERIC)
for row in reader:
rows.append(row)
# Alle Zeilen ab der zweiten als np.array formatieren und transponieren, um aus [[x1,y1],[x2,y2]] [[x1,x2],[y1,y2]] zu machen
data_import = np.array(rows[1:]).T
#Sortieren der jeweiligen Listen in das Dictionary
sol_imported={}
sol_imported["t"] = data_import[0]
sol_imported["y"]= data_import[1:] # In y wird also wie auch oben die Matrix (v,h) gespeichert.
```
%% Cell type:markdown id:7a2c5185 tags:
Nun ist die Lösung importiert. Wir testen das, indem wir den oben gezeigten Plot nun noch einmal mit der importierten Lösung erstellen.
%% Cell type:code id:e870d961 tags:
``` python
#Diese importierte Lösung kann nun genau so dargestellt werden, wie die originale Lösung. (Zur Erstellung des Plots, siehe oben)
plt.figure(figsize=(3,8))
plt.plot(sol_imported["y"][2], sol_imported["y"][3])
plt.gca().set_aspect('equal')
plt.scatter(sol_imported["y"][2][::8], sol_imported["y"][3][::8])
offset_x = 290
offset_z = -110
plt.scatter(sol_imported["y"][2][::8]*0+offset_x, sol_imported["y"][3][::8])
plt.scatter(sol_imported["y"][2][::8], sol_imported["y"][3][::8]*0+offset_z)
plt.xlabel("x")
plt.ylabel("z")
plt.show()
```
%% Output
%% Cell type:markdown id:a3461331 tags:
Auch hier möchte ich noch einmal zeigen, wie das ohne das CSV Paket aussehen kann (wir müssen uns also wieder manuell um die Formatierung kümmern):
%% Cell type:code id:3ea9d27a tags:
``` python
# Wir legen wieder ein Feld "rows" mit den Zeilen an. In dieses fügen wir die Zahlendaten jeder Zeile als Liste ein
rows = []
with open("simulation_2D_manual.csv", "r") as f:
content = f.readlines()[1:] #wir überspringen die erste Zeile und speichern den Rest in content ab
#iteration über alle Zeilen, die nun immer als ein String eingelesen sind
for line in content:
line_splitted = line.split(',') #teilt den string an den "," in eine Liste aus den Teilstrings auf
current_row = []
for element in line_splitted:
current_row.append(float(element))
rows.append(current_row)
#Weiteren koennen wir rows genau wie oben bei der Verwendung von csv weiterverarbeiten
data_import = np.array(rows[1:]).T
# Zuletzt legen wir das Dictionary an und konvertieren die Listen zu np.arrays
sol_man_imported={}
sol_man_imported["t"] = data_import[0]
sol_man_imported["y"] = data_import[1:]
#zum Test ein paar Werte ausgeben
print("y: ",sol_man_imported["t"][:10], "\nvx: ", sol_man_imported["y"][0][:10])
```
%% Output
y: [2.47426240e-05 2.72168864e-04 2.74643126e-03 2.74890552e-02
2.74915295e-01 5.24915295e-01 7.74915295e-01 1.02491529e+00
1.27491529e+00 1.52491529e+00]
vx: [69.99932489 69.99257455 69.92514445 69.25809643 63.24034783 58.15451792
53.83605131 50.11730739 46.8746509 44.01485887]
%% Cell type:markdown id:ca033fad tags:
## <font color='blue'> **Schlussbemerkung**
Wir haben gesehen, dass wir mit relativ einfachen Mitteln die SciPy Lösungsmethode für DGL auf weitere Lösungsvariablen, wie z.B. mehrere Raumdimensionen erweitern können. Die eigentliche Schwierigkeit in Problemstellung 1 war das Übertragen des Quadrats der Geschwindigkeit für den Luftwiderstand als Vektor.
Dann haben wir eine Methode besprochen, mit der tabellarische Daten wie die Ergebnisse von Simulationen im Zeitbereich kompakt und speicherschonend und für andere Programme importierbar auf der Festplatte gespeichert werden können.
## <font color='blue'> **Ausblick**
Nicht alle Daten lassen sich so gut als Tabelle darstellen, wie die Ergebnisse dieser Übung. In der nächsten Übung werden wir (auch zur Wiederholung) eine kleine objektorientierte Datenstruktur aufbauen und Funktionen entwickeln, mit denen wir diese strukturierten Daten in geeigneten Dateien auf der Festplatte speichern können.
## <font color='blue'> **Aufgaben zum selbst probieren**
* Erweitere die 2D-Fallsimulation auf 3D.
* Erweitere die 2D/3D Fallsimulation mit der Öffnung eines Fallschirms (wie in Übung 9)
* Schreibe ein Skript, dass die Ergebnisdaten in einem csv-artigen ASCII Format abspeichert. Die Zeit soll mit drei Nachkommastellen, alle anderen Variablen nur mit je zwei Nachkommastellen eingetragen werden. Die Trennung zwischen den Werten sollen ausschließlich Leerzeichen (mindestens 3) sein. Schreibe ebenfalls ein Skript, das diese Daten einließt. Du kannst dazu das csv-Paket nutzen, oder das Format von Hand definieren.
* (Zusatz) Füge einen höhenabhängigen Wind hinzu (z.B. 0 m/s am Boden 25m/s in 1000m Höhe, linear interpoliert). Beachte, dass der Luftwiderstand von der Geschwindigkeit *gegenüber der Luft* abhängig ist. Der Ort ist weiterhin von der absoluten Geschwindigkeit abhängig. Überlege dir also, wie du auf den entsprechenden Geschwindigkeitsvektor für die Ermittlung des Widerstands und dessen Richtung kommst.
Füge deiner Kopie des Notebooks beliebig viele Codezellen hinzu, um die Aufgaben zu lösen.
%% Cell type:markdown id:8c57efff tags:
# <font color='blue'>**Übung 11 - Primfaktorzerlegung und Speichern von objektorientierten Datenstrukturen**
%% Cell type:markdown id:ad48d2ea tags:
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.
### <font color='blue'>**Vorkenntnisse - Notebooks**
* Übung 1
* Übung 2
* Übung 3
* Übung 10
* Grundlagen Python
* Grundlagen Objektorientierte Programmierung
* Grundlagen Dateien
### <font color='blue'>**Notebooks, die helfen können**
* Zusatznotebook "Dateien"
### <font color='blue'>**Lernziele**
* Wiederholung OOP
* Umsetzung mathematischer Abläufe in Algorithmen
* Lesen und Schreiben von hierarchischen Strukturen (OOP) in Dateien
%% Cell type:markdown id:88fd3cb5 tags:
## <font color='blue'>**Problemstellung 1: Primfaktorzerlegung**
<font color='blue'>**Aufgabenstellung**
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.
<font color='blue'>**Vorüberlegung**
*Hintergrund Primfaktorzerlegung*
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.
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}$.
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.
*Berechnung Primfaktorzerlegung*
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:
$100 : 2 = 50$. Neuer Primfaktor 2, neue Zahl 50, Primfaktoren: $2^1$\
$50 : 2 = 25$. Neuer Primfaktor 2, neue Zahl 25, Primfaktoren: $2^2$\
$25 : 2 = $ (keine Ganzzahl), nächsthöhere Primzahl testen\
$25 : 3$ -> (keine Ganzzahl), nächsthöhere Primzahl testen\
$25 : 5 = 5$ Neuer Primfaktor 5, neue Zahl 5, Primfaktoren: $2^2\cdot5^1$\
5 ist selbst Primzahl. Primfaktoren: $2^2\cdot5^2$
*Programm*
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.
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.
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:
````class Primfaktor_Werkzeug````
* Attribute:
* Eine Liste mit lückenlos aufsteigenden Primzahlen bis zur aktuellen Grenze ````prim_liste````
* Ein Dictionary mit allen bisher berechneten Zerlegungen in der Form {Zahl:Liste_mit_Primfaktoren} ````zerlegungen````
* Der Kern ist eine Methode, die:
* die Primfaktorzerlegung für eine gegebene Zahl berechnet, ausgibt, und in das Dictionary einträgt ````zerlegung(zahl)````
* Hilfsmethoden sind:
* Der Konstruktor ````__init__()````
* get-Methoden für die Attribute, um außerhalb der Klasse auf Kopien der Attribute zuzugreifen ````get_prim_liste()````,````get_zerlegungen()````
*Primzahl-Liste*
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.
Unsere neue Klasse ````Primzahl_Liste```` erhält folgende Elemente:
* Attribute:
* Eine Liste mit lückenlos aufsteigenden Primzahlen bis zur aktuellen Grenze ````primzahlen````
* Methoden:
* Die Kernmethode, die die Liste lückenlos bis zu einer gegebenen Zahl erweitert ````liste_erweitern_bis(zahl)````
* Den Konstruktor ````__init__()````
* Eine get-Methode, um außerhalb der Klasse auf eine Kopie der Liste zuzugreifen ````get_primzahlen()````
* 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)
Bevor wir mit der Implementierung beginnen, noch kurz die Überlegung zur Erweiterung der Liste:
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.
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.
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.
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 tags:
<font color='blue'>**Umsetzung**
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.
**Generell sollte Code, bzw. die Namen, so aussagekräftig sein und fein aufgegliedert sein, dass man wenig Kommentare benötigt.**
*Konstruktor*
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]````.
*Liste erweitern bis*
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).
*_ist_primzahl*
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.
*_ist_teilbar*
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```` )
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.
*get_primzahlen*
Die Methode gibt eine Kopie der Liste zurück. Dafür kann ein Slicing über alle Elemente genutzt werden.
%% Cell type:code id:16ecfaad tags:
``` python
'?' '?':
def __init__(self, '?'):
self.primzahlen = '?'
self.'?'
def liste_erweitern_bis'?':
start = '?'
if '?':
for zahl in range('?'):
if '?'('?'):
'?'.'?'.'?'(zahl)
def _ist_primzahl'?':
for '?':
if '?':
return '?' #In welche Ebene muss das andere return?
def _ist_teilbar'?':
return '?'
def get_primzahlen('?'):
'?'
```
%% Cell type:markdown id:a0622bb9 tags:
Wir testen die neue Klasse:
%% Cell type:code id:ff4be127 tags:
``` python
print("Test der Klasse Primzahl_Liste:")
# Erstellt die Primzahlen bis 30 als Liste
primes = '?'
print(primes.'?')
# Erweitert bis 200
primes.'?'
print('?')
```
%% Cell type:markdown id:c0c48eea tags:
Nachdem wir nun eine funktionierende Primzahl_Liste haben, können wir uns dem eigentlichen Ziel der Aufgabe widmen, der Primfaktorzerlegung.
Wir haben die Grundstruktur der Klasse bereits entworfen und überlegen nun die Details der Implementierung.
*Konstruktor*
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.
*zerlegung*
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.
Dann müssen wir sicherstellen, dass die Primzahlliste weit genug reicht. Dazu erweitern wir sie (mit ihrer Methode) bis zur eingegebenen Zahl.
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.
Wir benötigen eine leere Liste, die wir mit Primfaktoren füllen.
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.
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.
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.
*get_zerlegungen*
Diese Methode gibt eine Kopie des Dictionaries zurück. Wir verwenden die .copy() Methode.
*get_prim_liste*
Diese Methode gibt die Primzahlliste zurück. Wir nutzen dazu die entsprechende Methode der Primzahlliste und geben das Ergebnis lediglich weiter.
%% Cell type:code id:f51d4e45 tags:
``` python
class Primfaktor_Werkzeug:
def __init__(self):
self.prim_liste = '?'
self.zerlegungen = '?'
# Alle Schritte sind im Beschreibungstext oben angesprochen
def zerlegung('?'):
if '?':
return 1
self.prim_liste.'?'
save_key = '?'
primfaktoren = '?'
for '?' in '?':
while self.'?'('?', '?'):
'?'
primfaktoren.'?'
self.zerlegungen['?']='?'
return '?'
def _ist_teilbar'?':
'?'
def get_zerlegungen('?'):
'?'
def get_prim_liste('?'):
'?'
```
%% Cell type:markdown id:55a4f204 tags:
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 id:23a78c3f tags:
``` python
# Test der Klasse Primfaktorzerlegung
pf_werkzeug = '?'
print(pf_werkzeug.zerlegung('?'))
print('?')
# Alle Zerlegungen bis einschliesslich 20
for '?' in '?'
pf_werkzeug.'?'
# Ausgabe der gespeicherten Zerlegungen
print('?')
```
%% Cell type:markdown id:7c15b337 tags:
## <font color='blue'>**Problemstellung 2: Speichern in Dateiformaten**
<font color='blue'>**Aufgabenstellung**
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.
* 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.
* Ebenso wird eine Methode benötigt, die ein neues Primfaktor_Werkzeug Objekt erstellt und mit den Daten aus der Zeichenkette füllt.
* Zur einfacheren Übertragung innerhalb Pythons soll eine Speicherung mittels ````pickle```` (Binärdatei) ermöglicht werden.
<font color='blue'>**Vorüberlegung**
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.
Die Daten sind daher nicht sehr geeignet für **csv**, wobei man auch für **csv** Lösungen finden kann.
*(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)*
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.
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.
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.
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.
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 tags:
<font color='blue'>**Umsetzung**
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.
Wir testen es durch vollständige Ausgabe des Json-Strings, der aus dem oben getesteten Objekt erzeugt wird.
%% Cell type:code id:6f11a208 tags:
``` python
import json
def pfw_object_to_json_string('?'):
json_dict = {'?': '?'}
json_dict['?'] = '?'
return json.'?'('?', indent=4)
#Test
print('?'('?'))
```
%% Cell type:markdown id:e954b334 tags:
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.
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 id:cee9f298 tags:
``` python
def json_string_to_pfw_object('?'):
pfw_object = '?'
json_dict = json.'?'('?')
pfw_object.prim_liste = '?'
# 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.
for '?' in json_dict['?']:
pfw_object.'?'['?'('?')] = json_dict['?']['?']
return '?'
#Test
save_string = pfw_object_to_json_string(pf_werkzeug)
imported_pfz = json_string_to_pfw_object(save_string)
print(imported_pfz.zerlegungen[20])
```
%% Cell type:markdown id:bfe4321e tags:
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.
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 id:215c715b tags:
``` python
import pickle
def pfw_object_to_pickle(pfw_object, filename):
with '?':
pickle.'?'('?')
def pfw_object_from_pickle('?'):
'?':
return '?'
#Test:
pfw_object_to_pickle(pf_werkzeug, "Primfaktoren.pkl")
object_from_pickle = pfw_object_from_pickle("Primfaktoren.pkl")
print(object_from_pickle.zerlegungen[20])
```
%% Cell type:markdown id:e6aed72d tags:
## <font color='blue'> **Schlussbemerkung**
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.
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.
## <font color='blue'> **Ausblick**
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.
## <font color='blue'> **Aufgaben zum selbst probieren**
* 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.
* 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)
* 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.
* Baue eine export und import Funktion für xml. Orientiere dich dabei an dem Vorgehen im "Grundlagen Dateien" Notebook, und dem Zusatznotebook "Dateien".
Füge deiner Kopie des Notebooks beliebig viele Codezellen hinzu, um die Aufgaben zu lösen.
%% Cell type:code id:83014351 tags:
``` python
```