From 3458ce2f8178e1d696b0bdbe8df4754017647301 Mon Sep 17 00:00:00 2001
From: Volker von Holt <v.von-holt@ostfalia.de>
Date: Mon, 10 Jun 2024 13:51:11 +0000
Subject: [PATCH] Upload New File

---
 docs/source/exercises/exercise_03.rst | 436 ++++++++++++++++++++++++++
 1 file changed, 436 insertions(+)
 create mode 100644 docs/source/exercises/exercise_03.rst

diff --git a/docs/source/exercises/exercise_03.rst b/docs/source/exercises/exercise_03.rst
new file mode 100644
index 0000000..5eafbc0
--- /dev/null
+++ b/docs/source/exercises/exercise_03.rst
@@ -0,0 +1,436 @@
+3. Übung
+========
+
+Beispiel 3.1
+------------
+
+Im Rahmen dieses Beispiels wollen wir im Wesentlichen die Liste als neuen Datentypen sowie die *for*-Schleife als neues Programmierkonstrukt kennenlernen.
+
+Zeichenketten
+~~~~~~~~~~~~~
+
+In den ersten beiden Übungen haben wir schon einige der elementaren Datentypen, die Python - wie jede andere Programmiersprache auch - besitzt. Darunter waren mit *integer* und *float* zwei Datentypen, mit denen wir numerische Werte repräsentieren können. Wenn wir 
+
+.. code-block:: python
+
+  a = 42
+  f = 3.1415
+
+codieren, so speichern wir **EINEN** numerischen Wert vom Typ *integer* unter dem Namen *a* ab und **EINEN** numerischen Wert vom Typ *float* unter dem Namen *f* ab. Wenn wir hingegen 
+
+.. code-block:: python
+
+  s = 'Ich bin ein String'
+
+in unser Programm schreiben, dann legen wir **EINE** ZeichenKETTE unter dem Namen *s* an, die aber aus mehreren einzelnen Zeichen aufgebaut sind, die jedes für sich auch einen eigenen Wert darstellen, der in einer getrennten Variablen gespeichert sein könnte:
+
+.. code-block:: python
+
+  l11 = 'I'
+  l12 = 'c'
+  l13 = 'h'
+  s3 = 'auch'
+  space = ' '
+
+  s = l11 + l12 + l13 + space + 'bin' + space + s3 + ' '+ 'ein String'
+
+Wir können Strings aber nicht nur aus einzelnen Zeichen und aus anderen Strings zusammensetzen, sondern wir können umgekehrt Strings auch wieder in einzelne Buchstaben oder in Teilstrings zerlegen. Und, wir können auch auf die einzelnen Zeichen eines Strings zugreifen.
+
+.. code-block:: python
+
+  s = 'Ich bin ein sieben Worte langer String'
+
+  """
+  In String fängt die Positionsindizierung bei 0 an. Die Indizes eines String mit der Länge n 
+  sind also 0...n-1.
+  (Und dies ist ein mehrzeiliger Kommentar, den man in dreifache " setzt.)
+  """
+  print(s[0])       # 1. Zeichen des Strings s an Position 0
+  print(s[1])       # 2. Zeichen des Strings s an Position 1
+  print(s[2])       # 3. Zeichen des Strings s an Position 2
+  print(s[4:7])     # Zeichen des Strings s an den Positionen 4...6
+  print(s[-1])      # 1. Zeichen des Strings vom Ende her, also an Position n-1
+  print(s[0:10:2])  # jedes 2. Zeichen von s zwischen den Positionen 0...9
+  print(s[::2])     # jedes 2. Zeichen von s von 0...n-1
+  print(s[::-1])    # jedes Zeichen von s von n-1...0
+
+  """
+  Die allgemeine Syntax lautet also:
+  [Anfang : Ende+1 : Schrittweite und Schrittrichtung]
+  Defaultwerte:
+  [0 : n : +1]  (n = Länge des Strings)
+  """
+    
+.. admonition:: Merke
+
+  Da Zeichen in der Form von ZeichenKETTEN in der Datenverarbeitung (neben Zahlenwerten) eine besondere Bedeutung haben, gibt es eine Vielzahl besonderer Operationen und Funktionen, die wir auf diese anwenden können. (Das Modul *string* enthält viele dieser Funktionen, von denen wir später noch einige kennenlernen werden.) 
+
+Listen
+~~~~~~
+
+Listen sind ein sog. zusammengesetzter Datentyp, der eine gewisse Ähnlichkeit zu den o.a. Strings hat. Unter der Haube gibt es dann aber doch eine ganze Reihe Unterschiede. In einer Liste können wir zunächst - ähnlich den Zeichenketten - mehrere Datenwerte abspeichern, auf die wir dann mit dem Indexoperator *[ ]* zugreifen können:
+
+.. code-block:: python
+
+  eine_liste = [42, 9, 17+4, 99]
+
+  print(eine_liste[0])                # 1. Element der Liste
+  print(eine_liste[2])                # 3. Element der Liste
+  print(eine_liste[-1])               # 1. Element der Liste von hinten
+  print(eine_liste[2:4])              # 3.-4. Element
+  print(eine_liste[::-1])             # alle Elemente in umgekehrter Reihenfolge
+  print(eine_liste[0]+eine_liste[1])  # Rechnen können wir damit natürlich auch
+
+  # die Anzahl der Elemente einer Liste bestimmen wir mit der Funktion *len()*
+  print('Länge der Liste: ', len(eine_liste))
+
+  # wir schauen uns die Datentypen der Listenelemente an und ...
+  i_n = 0
+  while i_n < len(eine_liste):
+    print(type(eine_liste[i_n]))
+    i_n += 1
+
+  # ... die Liste selber hat auch einen Datentyp
+  print('\n', type(eine_liste))
+
+.. admonition:: Merke
+
+  Die Methode aus einer größeren Datenmenge wie einer Liste nur eine Teilmenge herauszunehmen in der Form *liste[2:8:3]*, in diesem Fall also jedes dritte Element im Indexbereich 2..7, nennt sich offiziell **Slicing**. (Dieses Verfahren findet auch in MATLAB Verwendung.)
+
+Natürlich können wir auch Listen mit anderen Datentypen anlegen:
+
+.. code-block:: python
+
+  f = 4.4
+  b = True
+  liste_f = [1.1, 2.2, 3.3, f]
+  liste_b = [True, False, False, b]
+
+  print(liste_f[0])                # erstes Element von liste_f
+  print(liste_b[-1])               # letztes Element von liste_b
+
+
+Wir können aber auch Listen anlegen, in denen die Elemente alle unterschiedliche Datentypen besitzen:
+
+.. code-block:: python
+
+  q_liste = [42, 3.1415, True, 'Python', [1, 2, 3]]
+
+  # wir schauen uns die Datentypen und -werte der Listenelemente an
+  i_n = 0
+  while i_n < len(q_liste):
+    print('Datentyp: ', type(q_liste[i_n]), ' Datenwert= ', q_liste[i_n])
+    i_n += 1
+  
+  # wenn wir einem Listenelement einen neuen Wert eines anderen Datentyps 
+  # zuweisen, ändert auch das Listenelement seinen Datentyp
+  q_liste[0] = q_liste[0] + q_liste[1]
+  print('Datentyp: ', type(q_liste[0]), ' Datenwert= ', q_liste[0])
+  
+.. admonition:: Hinweis für Programmiererfahrene
+
+  Bei Kenntnis anderer Programmiersprachen \"fühlen" sich diese gemischten Listen i.d.R. ungewohnt an, weil man gewohnt ist, dass in listenartigen Datenstrukturen alle Elemente gleichen Datentyps sind und man damit "rechnen" kann. Wir könnten zwar auch mit den Standard-Listen aus Python mathematische Rechnungen durchführen (die wir selber implementieren müssten), tatsächlich aber gibt es dafür in Python ein spezielles Modul mit Namen *NumPy* (für \"Numerical Python"), das neben Datentypen für Vektor- und Matrizenrechnungen viele weitere mathematische Funktionen bereitstellt. (Die Funktionalität von *NumPy* ist stark an MATLAB angelehnt.)
+
+Bisher haben wir unsere Listen immer auf einen Schlag angelegt und dann auf die Elemente zugegriffen. Was machen wir aber, wenn wir im Programmverlauf eine Liste erweitern oder verkleinern wollen? 
+
+.. code-block:: python
+  :linenos:
+
+  list_1 = [1,2,3]
+  print(list_1)
+
+  # mit der Funktion append() können wir EIN neues Element hinten anfügen
+  list_1.append(4)
+  print(list_1)
+
+  # mit der Funktion insert() fügen wir ein Element an einer Position ein
+  list_1.insert(2, 5) # wir fügen das Element 5 an Index 2 (also an 3. Stelle) ein
+  print(list_1)
+
+  # mit der Funktion remove() können wir ein Element aus der Liste entfernen
+  list_1.remove(3)  # wir entfernen das erste Element mit dem Wert 3 aus der Liste
+  print(list_1)
+
+  # wenn bei remove() der Wert nicht in der Liste enthalten ist sieht es so aus:
+  list_1.remove(7)  # wir entfernen das erste Element mit dem Wert 7 
+  print(list_1)
+
+  # Neben den hier aufgeführten Funktionen, gibt es viele weitere, die auf 
+  # Listen arbeiten.
+
+.. admonition:: Notation
+
+  Bei Verwendung der Funktionen *print()* oder *len()* war der Aufruf bisher immer in der Form: 
+  
+  .. code-block:: 
+
+    # rückgabewert und argumente sind optional    
+    rückgabewert = funktionsname(argument_1, argument_2, argument_3,...)
+
+  Im letzten Codeausschnitt sah die Notation für den Funktionsaufruf aber anders aus:
+
+  .. code-block:: 
+    
+    # rückgabewert und argumente sind optional    
+    rückgabewert = variablenname.funktionsname(argument_2, argument_3,...)
+
+  Diese Aufrufform entspricht der sog. objektorientierten Notation, bei der die Funktionen (oft auch \"Methoden" genannt) auf Datenobjekten/Variablen arbeiten. Für jeden Datentyp werden dabei die für ihn sinnvollen Funktionen implementiert und wir können diese Funktionen dann für Datenelemente des entsprechenden Typs nach o.a. Notation aufrufen. Inhaltlich entspricht die o.a. zweite Form dem (nicht-objektorientierten) Funktionsaufruf:
+
+  .. code-block:: 
+
+    # rückgabewert und argumente sind optional    
+    rückgabewert = funktionsname(variablenname, argument_2, argument_3,...)
+
+  Das Objekt bzw. die Variable mit der wir die Funktion objektorientiert aufrufen hat also die Rolle des ersten Arguments inne.
+
+  Auch wir selber werden später lernen, wie wir eigene Datentypen (\"class") entwickeln und eigene Funktionen für diese sog. Klassen schreiben können.
+
+Um zu vermeiden, dass eine *exception* auftritt, wenn der o.a. Code in den Zeilen 17/18 ausgeführt wird, weil das zu entfernende Element gar nicht in der Liste enthalten ist, können wir dies auch vorher abtesten:
+
+.. code-block:: python
+
+  # ... vorheriger Code
+
+  # diese Zeilen ersetzen die Zeilen 17/18
+  if 7 in list_1:
+    list_1.remove(7)  # wir entfernen das erste Element mit dem Wert 7 
+    print(list_1)
+
+*for*-Schleifen
+~~~~~~~~~~~~~~~
+
+Nachdem wir nun Listen als Datentyp kennengelernt haben, haben wir auch genug Vorwissen um eine weitere sehr nützliche Schleifen-Form neben der *while*-Schleife kennenzulernen.
+
+.. code-block:: python
+
+  ... Anweisungen vor der 'for'-Schleife
+
+  # am Ende der for-Anweisung muss wieder ein ':' stehen
+  for i in Liste:
+    # i nimmt nacheinander alle Werte in der Liste an
+    # dieser eingerückte Progammabschnitt wird solange ausgeführt, wie Werte 
+    # in der Liste sind
+    ... tue dies
+    ... und noch mehr
+
+  # wenn die Einrückung zu Ende ist, geht der Progammablauf nach dem Ende der Schleife hier weiter
+  ... Anweisungen nach der 'for'-Schleife
+
+Wir schauen uns zunächst ein paar Beispiele an:
+
+.. code-block:: python
+
+  #------------------------
+  print('Liste fest vorgegeben:')
+  for i in [0,1,2,3,4,5,6]:
+    print(i)
+  #------------------------
+  print('Liste als Variable:')
+  list_1 = [0,1,2,3,4,5,6]
+
+  print('Schleife über alle Elemente der Liste:')
+  for i in list_1:
+    print(i)
+
+  print('Schleife bis zum 3.Element der Liste:')
+  for i in list_1[:3]:
+    print(i)
+
+  print('Schleife über jedes 2.Element in der Liste:')
+  for i in list_1[::2]:
+    print(i)
+
+  print('Schleife über jedes 2.Element in der Liste von hinten:')
+  for i in list_1[::-2]:
+    print(i)
+  #------------------------
+
+So nützlich die *for*-Schleife uns erscheint, so aufwendig wäre es, wenn wir für große Schleifen händisch eine entsprechend große Liste anlegen müssten. Glücklicherweise gibt es auch hier eine Alternative in Python, die uns das Leben leichter macht:
+
+.. code-block:: python
+
+  #------------------------
+  print('Schleife von 0..99 unter Nutzung von range()')
+  for i in range(0,100):
+    print(i)
+  #------------------------
+  # Natürlich können wir mit range() auch zunächst eine Liste 
+  # erzeugen und dann die Schleife über die Liste laufen lassen
+  list_2 = range(0,100,10)
+  print('Schleife über alle Elemente von list_2')
+  for i in list_2:
+    print(i)
+
+  # Allgemeine Form:
+  # range(Startwert, Endwert+1, Schrittweite)
+  #------------------------
+
+
+Aufgabe 3.1
+-----------
+
+Wir greifen die Berechnung der Sinus-Funktion wieder auf, wollen dieses Mal aber keinen einzelnen Wert berechnen. Vielmehr wollen wir die Funktionswerte für ein ganzes Intervall berechnen und uns im nächsten Schritt ausgeben lassen.
+
+.. admonition:: To Do
+
+  Es soll im ersten Schritt ein Programm erstellt werden, dass den Sinuswert für einen Reihe von Stützpunkten berechnet und in Form einer Liste abspeichert:
+  
+  #. das Werteintervall soll :math:`0...2\pi` beigetragen
+  
+  #. es sollen :math:`n=100` Stützpunkte berechnet werden
+  
+  #. sowohl das Argument :math:`x` wie das Ergebnis :math:`sin(x)` sollen in jeweils einer Liste abgelegt werden
+  
+  #. die Berechnung soll mithilfe einer *for*-Schleife erfolgen
+
+  #. nach Berechnung aller Werte sollen die Wertepaare :math:`(x, sin(x))`  zeilenweise auf der Konsole ausgegeben werden
+
+Lösungshinweise:
+
+* leere Listen werden mit *= []* erzeugt
+
+* anhand der Anzahl der Stützpunkte und der Größe des Werteintervalls lässt sich eine Schrittweite berechnen, um die das Argument von Berechnung zu Berechnung wachsen muss
+
+* es empfiehlt sich, sowohl das Werteintervall wie die Anzahl der Stützpunkte nicht \"hart" zu codieren, sondern in Variablen abzulegen
+
+.. Aufgabe 3.1.2
+  ~~~~~~~~~~~~~
+
+.. Unser Programm aus Aufgabe 3.1.1 soll nun etwas flexibler werden indem wir  
+   einige Parameter von den Nutzern abfragen.
+
+..  .. admonition:: To Do
+
+..    Das Programm aus Aufgabe 3.1.1 soll um folgende Merkmale ergänzt werden:
+
+..   #. Abfrage des Namens für die Ausgabedatei
+
+..    #. Abfrage der Anzahl an Stützpunkten für die Sinus-Funktion
+
+
+
+Beispiel 3.2
+------------
+
+Dateiausgabe
+~~~~~~~~~~~~
+
+Während die Konsole für die Ein- und Ausgabe \"einfach da ist", müssen Dateien explizit für die Arbeit mit ihnen geöffnet und geschlossen werden:
+
+.. code-block:: python
+  :linenos:
+
+  # Datei öffnen
+  fd = open('datei_1.txt','w')    # open(filename, mode)
+  print('Dateiname: ', fd.name)
+
+  # mit write() können wir einen STRING in die Datei schreiben
+  fd.write('Eine sinnfreier Text')
+  # ... und noch einen
+  fd.write('Eine sinnfreier Text')
+  # ... und einer geht noch
+  fd.write('Eine sinnfreier Text')
+
+  # Datei schließen
+  fd.close()
+
+In dem vorstehenden Programm nutzen wir die \"normale" Funktion *open()* um die Datei zu öffnen. Bei erfolgreichem Öffnen (wenn etwas schiefgeht wird eine *exception* ausgelöst) enthält die hier als *fd* bezeichnete Variable ein Objekt mit dem wir Dateifunktionen (in objektorientierter Notation) ausführen können. Mit *write(string)* schreiben wir dann den gleichen String dreimal nacheinander in die Datei und am Ende schließen wir diese wieder.
+
+Wenn wir nun die Datei im Editor öffnen und sie uns anschauen, sehen wir, dass alle drei Strings in eine Zeile geschrieben wurde. Ein Zeilenumbruch hat also nicht stattgefunden. Anders als die uns von der Konsole bekannte *print()*-Funktion gibt *write()* nicht automatisch am Ende einen Zeilenvorschub aus, vor allem aber verarbeitet *write()* in diesem Fall wirklich nur Zeichenketten, wie wir in diesem Fall sehen:
+
+.. code-block:: python
+
+  # Datei öffnen
+  fd = open('datei_1.txt','w')
+  print('Dateiname: ', fd.name)
+
+  # Versuch mit write() einen Integerwert in die Datei zu schreiben
+  fd.write(42)
+  # ... hierhin kommen wir schon gar nicht mehr
+
+  # Datei schließen
+  fd.close()
+
+Wir erinnern uns, dass wir beim Einlesen von der Konsole schon einmal den umgekehrten Fall hatten: *input()* lieferte uns einen String, den wir mit *float(input())* in einen Float-Wert umgewandelt haben - sofern der eingegebene String in eine Zahl umwandelbar war. Analog gibt es auch eine Funktion mit der wir aus einem Zahlenwert einen String machen können: *str(Zahlenwert)*:
+
+.. code-block:: python
+
+  # Datei öffnen
+  fd = open('datei_1.txt','w')
+  print('Dateiname: ', fd.name)
+
+  # Versuch mit write() einen Integerwert in die Datei zu schreiben
+  fd.write(str(42))
+  # ... hierhin kommen wir schon gar nicht mehr
+
+  # Datei schließen
+  fd.close()
+
+Wir können aber der Funktion *write()* als Argument auch einen z.B. mittels \'*+*' erzeugten String aus mehreren Bestandteilen übergeben. Und insbesondere können wir im String auch sog. Formatanweisungen übergeben: \'\\n' löst einen Zeilenumbruch aus, \'\\t' entspricht einem Tabulatorb (es gibt noch weitere Formatanweisungen für Strings):
+
+.. code-block:: python
+
+  # Datei öffnen
+  fd = open('datei_1.txt','w')
+  print('Dateiname: ', fd.name)
+
+  # eine Zeile mit einem Zahlenwert
+  fd.write(str(42)+'\n')
+  # eine Zeile mit einem komponierten String
+  fd.write(str(3.1415)+'\t'+'gleich kommt einen Zeilenumbruch\n')
+
+  # Datei schließen
+  fd.close()
+
+Damit haben wir nun genug Wissen, um zumindest etwas in eine Datei schreiben zu können. Den umgekehrten Weg, das Einlesen von Daten aus einer Datei, werden wir uns etwas später anschauen. 
+
+.. admonition:: Merke
+  
+  Bei Dateien unterscheidet man primär zwischen zwei unterschiedlichen Bearbeitungsformen: Textdateien und Binärdateien. Textdateien sind grundsätzlich für uns Menschen lesbar, während Binärdateien codierte Informationen enthalten und nur mit den entsprechenden Codier-/Decodierverfahren geschrieben und gelesen werden können (z.B. Office-Dateien im *\*.docx* oder *\*xlsx* Format). Die Funktionen zur Dateibearbeitung in Python sind für beide Formate die selben, wir müssen Python aber mitteilen in welchem Format die Datei sein soll. Dazu gibt es im Aufruf der Funktion *open(filename, mode)* den Parameter *mode*, der u.a. folgende Werte annehmen kann:
+
+  * \'*wt*' (oder \'*w*'): Textdatei zum Schreiben öffnen
+  
+  * \'*rt*' (oder \'*r*'): Textdatei zum Lesen öffnen 
+
+  * \'*wb*': Binärdatei zum Schreiben öffnen
+  
+  * \'*rb*': Binärdatei zum Lesen öffnen
+
+Aufgabe 3.2
+-----------
+
+In Fortsetzung der Aufgabe 3.1. wollen wir unsere neu gewonnenen Erkenntnisse über den Umgang mit Dateien gleich in die Tat umsetzen.
+
+Aufgabe 3.2.1
+~~~~~~~~~~~~~
+
+.. admonition:: To Do
+
+  Das in Aufgabe 3.1 entstandene Programm ist so zu modifizieren, dass:
+
+    #. die Ausgabe der Listen für *x* und *sin_x* in eine Textdatei geschrieben wird - jedes Wertepaar *(x, sin_x)* in einer separaten Zeile
+
+    #. der Dateiname soll von den Nutzern erfragt werden
+
+    #. nach dem Schreiben der Datei eine Meldung folgender Form auf der Konsole ausgegeben wird: \"*n* Datensätze in der Datei *filename* gespeichert" (*n* und *filename* sind durch die aktuellen Werte zu ersetzen) 
+
+    #. die Ausgabe der Listen auf der Konsole soll im Gegenzug entfallen
+
+Nach der Aufgabe kontrollieren wir das Ergebnis unserer Dateiausgabe - wenn alles in Ordnung ist, sollte unsere Datei über 100 Zeilen mit 2 Spalten verfügen.
+
+.. Lösungshinweise:
+
+.. Ist :code:`dies` jetzt ein Inline-Code?
+
+Aufgabe 3.2.2
+~~~~~~~~~~~~~
+
+Unser Programm aus Aufgabe 3.1.1 soll nun etwas flexibler werden indem wir einige Parameter von den Nutzern abfragen.
+
+.. admonition:: To Do
+
+  Das Programm aus Aufgabe 3.1.1 soll um folgende Merkmale ergänzt werden:
+
+  #. Abfrage des Namens für die Ausgabedatei
+
+  #. Abfrage der Anzahl an Stützpunkten für die Sinus-Funktion
-- 
GitLab