satz = 'Ein String ist eine Zeichenkette.'
len(satz)

"""
Explanation: Strings
Sequenzen von Zeichen
Strings sind Zeichenketten, also Sequenzen von Zeichen.

Die Länge der Sequenz ermitteln
Die Zahl der Element in der Sequenz (also die Zahl der Zeichen) kann mit der Funktion len() ermittelt werden:
End of explanation
"""


# Für die folgenden Beispiele ist ein kurzer String übersichtlicher
satz = "Ein String"
satz[0]

"""
Explanation: Adressierung einzelner Elemente
Jedes Element in der Sequenz kann einzeln addressiert werden:

Dabei ist zu beachten, dass das erste Element der Sequenz den Index 0 hat!
End of explanation
"""


satz[0:3]

"""
Explanation: Ausprobieren
Was passiert, wenn ich auf satz[10] zugreife?
Slicing: Herausschneiden eines Substrings
Durch Angabe zweier, durch einen Doppelpunkt getrennter Werte (Index des ersten herauszuschneidenden und des ersten nicht mehr herauszuschneidenden Elements) kann man aus einem String einen Teilstring extrahieren:
End of explanation
"""


satz[:3]

"""
Explanation: Ist der erste Wert 0, kann dieser weggelassen werden:
End of explanation
"""


satz[3:]

"""
Explanation: Wird der zweite Wert weggelassen, ist das gleichbedeutend mit "bis zum Ende des Strings":
End of explanation
"""


satz[len(satz)-1]

"""
Explanation: Stringende
In den meisten Programmiersprachen muss man auf das letzte Elemente eines Strings so zugreifen:
End of explanation
"""


satz[-1]

"""
Explanation: In Python gibt es dazu eine elegante Alternative: Man kann negative Zahlen verwenden, um von hinten her auf einzelne Zeichen zuzugreifen:

Das letzte Zeichen des Strings hat als den Index -1, das vorletzte -2 usw.
End of explanation
"""


output = ('Dein Name ist {} und besteht aus {} Zeichen. '
          'Er beginnt mit {} und endet mit {}. Von hinten gelesen lautet er {}.') 

"""
Explanation: Übung 1
Schreiben wir ein Programm, das
1. Zur Eingabe eines Namens auffordert
1. Diese Eingabe in einer Variable zuweist
1. Folgende Ausgabe produziert:
   "Dein Name ist XXX und besteht aus n Zeichen. Er beginnt mit X und endet mit Y. Von hinten gelesen lautet er XXX.
End of explanation
"""


for char in satz:
    print(char)

"""
Explanation: Die for-Schleife
Beim Programmieren muss häufig ein Anweisung oder eine Reihe von Anweisungen wiederholt werden, beispielsweise für jedes Element einer Sequenz (z.B. für jedes Zeichen eines Strings). Für diesen Zweck ist eine for-Schleife bestens geeignet:
End of explanation
"""


for i in range(1, 11):
    print(i)

"""
Explanation: Dieses Konstrukt (for Element in Sequenz) funktioniert für alle Datentypen, die in der Lage sind, ein Element nach dem anderen zu liefern. Man spricht hier von einem Iterable. Solche Iterables sind in Python zahlreich, so dass auf diese Weise nicht nur durch die Zeichen eines Strings, sondern beispielsweise auch durch die Elemente einer Liste, die Zeilen einer Datei oder einfach nur durch eine Abfolge von Zahlen iteriert werden kann:
End of explanation
"""


for i in range(1, 11):
    for j in range(1, 11):
        print('{} x {} = {}'.format(j, i, i * j))

"""
Explanation: Übung
Ermitteln wir in einer Schleife die Summe aller Zahlen zwischen 1 und 50000
Verschachtelte Schleifen
Man kann zwei (oder mehr - meist nicht empfehlenswert) Schleifen ineinander verschachteln. Dadurch kann man beispielsweise als Elemente aus 2 Sequenzen miteinander kombinieren:
End of explanation
"""


fh = open('data/vornamen/names_short.txt')

"""
Explanation: Mit Dateien arbeiten
Bevor aus einer Datei gelesen oder in eine Datei geschrieben werden kann, muss diese Datei mit der Funktion open() geöffnet werden. open() erwartet mindestens ein Argument: Den Namen (evtl. mit Pfad) zur Datei:
End of explanation
"""


fh = open('data/vornamen/names_short.txt', encoding='utf-8')

"""
Explanation: Falls nötig, kann noch das Encoding der Datei explizit angegeben werden:
End of explanation
"""


fh.close()

"""
Explanation: Wenn wir die Datei nicht mehr brauchen, sollte sie wieder geschlossen werden, damit das Betriebssystem die Ressource wieder freigeben kann.
End of explanation
"""


fh = open('data/vornamen/names_short.txt', encoding='utf-8')
for line in fh:
    print(line)
fh.close()

"""
Explanation: Das Objekt, das die geöffnete Datei repräsentiert, bietet mehrere Möglichkeiten um auf den Inhalt 
zuzugreifen, darunter auch einen Iterator, den wir in einer for-Schleife nutzen können.
End of explanation
"""


with open('data/vornamen/names_short.txt', encoding='utf-8') as fh:
    for line in fh:
        print(line)

"""
Explanation: Eine Datei in einem Context-Manager öffnen
Es ist guter Stil, eine geöffnete Datei auch wieder zu schließen. Wenn aber z.B. das Programm abstürzt, während die Datei geöffnet ist, kann die close()-Methode nicht mehr ausgeführt werden. Um solche Probleme zu vermeiden, empfiehlt sich die Verwendung eines Context-Managers:
End of explanation
"""


with open('data/vornamen/names_short.txt', encoding='utf-8') as fh:
    data = fh.read()
print(data)    

"""
Explanation: Weitere Methoden um aus einer Datei zu lesen
read()
Die read()-Methode liest den gesamten Dateiinhalt als String ein:
End of explanation
"""


with open('data/vornamen/names_short.txt', encoding='utf-8') as fh:
    data = fh.readlines()
print(data)    

"""
Explanation: readlines()
Diese Methode liest jede Zeile der Datei als Element in eine Liste ein:
End of explanation
"""


students = ['Otto', 'Anna', 'Maria', 'Franz']
students

temperatures = [25, 28, 20, 26, 32]
temperatures

"""
Explanation: Übung: wie viele Zeilen hat die Datei names_short.txt?
Listen
Eine Liste ist ein weiterer Sequenztyp. Eine Liste enthält eine Sequenz von Elementen. Der Datentyp eines Elements ist egal, oder anders gesagt: in einer Liste können Elemente mit beliebigen Typen gespeichert werden:
End of explanation
"""


len(students)

"""
Explanation: Da eine Liste so wie ein String ein Sequenztyp ist, funktionieren viele Dinge, die wir bei Strings kennengelernt haben, auch bei Listen. 
Zahl der Listenelement ermitteln
Wir können die Zahl der Elemente einer Liste mit der Funktion len() ermitteln:
End of explanation
"""


students[0]

"""
Explanation: Einzelne Elemente adressieren
Wie bei einem String über den Index auf ein einzelnes Zeichen zugegriffen werden kann, kann bei einer Liste ein bestimmtes Element adressiert werden:
End of explanation
"""


students[1:3]

"""
Explanation: Slicing
Außerdem können Teillisten extrahiert werden:
End of explanation
"""


print(students)
students.append('Otto')
print(students)

"""
Explanation: Listen verändern
Im Unterschied zu Strings sind Listen nachträglich veränderbar. Wir können jederzeit neue Elemente hinzufügen. Die Mehode append(WERT) fügt ein neues Element am Ende der Liste ein:
End of explanation
"""


students.insert(0, 'Berta')
students

"""
Explanation: Wir können aber auch Elemente an beliebiger Position einfügen:
End of explanation
"""


next_student = students.pop()
print(next_student)
print(students)

"""
Explanation: Ebenso können wir Elemente wieder entfernen. Die Methode pop() entfernt das letzte Element der Liste.
End of explanation
"""


first = students.pop(0)
print(first)
students

"""
Explanation: pop() kann aber auch optional mit einem Argument aufgerufen werden: einer Zahl die dem Index des zu entfernenden Objekts entspricht:
End of explanation
"""


print(students)
students[1] = 'Berta'
print(students)

with open('data/vornamen/names_short.txt', encoding='utf-8') as fh:
    lines = fh.readlines()
print(len(lines))

"""
Explanation: Elemente ersetzen
Über den Index kann der Wert eines Elements der Liste jederzeit verändert werden:
End of explanation
"""


temperatures = [
    [17, 28, 24],
    [18, 31, 28],
    [20, 35, 29]
]

"""
Explanation: Mehrdimensionale Listen
Wir haben gesehen, dass eine Liste beliebige Typen enthalten kann. Dazu gehören auch Listen. Wir können also auch eine Liste von Listen erzeugen. Stellen wir uns vor, wir messen drei Mal täglich die Temperatur und möchten diese speichern. Am ersten Tag haben wir diese 3 Messungen: [17, 28, 24]. Am zweiten Tage messen wird diese Werte [18, 31, 28]. Wir haben also eine Liste pro Tag. Die einzelnen Tage (sprich: Listen) können wir wieder in ein Liste speichern:
End of explanation
"""


temperatures[1]

"""
Explanation: Wir können uns diese Temperaturen als Tabelle vorstellen: Jede Zeile repräsentiert einen Tag, jede Spalte einen Meßzeitpunkt (z.B. 6:00, 12:00, 18:00). Wie wir auf die Messwerte eines bestimmten Tages zugreifen können, haben wir schon gelernt:
End of explanation
"""


temperatures[1][0]

"""
Explanation: Da das gewählte Element wieder eine Liste ist, können wir auch auf einzelne Element zugreifen. Den ersten Messwert des zweiten Tages erhalten wir so:
End of explanation
"""


max(temperatures[0])

"""
Explanation: Mit Listen-Werten rechnen
Für numerische Listen (int, float) stellt Python Funktionen bereit, die auf alle Werte einer Liste angewandt werden können:
* max(liste) ermittelt den größten vorkommenden Wert
* min(liste) ermittelt den kleinsten vorkommenden Wert
* sum(liste) ermittelt die Summe aller Werte
End of explanation
"""


with open('data/vornamen/names_short.txt', encoding='utf-8') as fh:
    lines = fh.readlines()
print(lines)

"""
Explanation: Übung
Wie hoch ist die gemittelte Mittagstemperatur?
Zeilen einer Datei in eine Liste lesen
Kehren wir zurück zu unsere Datei mit den Vornamen. Wie wir gesehen haben, liefert die Methode readlines() den Inhalt einer Datei als Liste von Zeilen:
End of explanation
"""


s = 'abc\n'
s.rstrip()

"""
Explanation: Die String-Methoden rstrip(), lstrip() und strip()
Wie wir sehen, enthält jedes Listenelement am Ende das Zeilenumbruchszeichen \n (line feed). Wir könnten dieses z.B. mit Slicing entfernen, jedoch bietet der String-Typ eine Methode .rstrip(), die genau das tut, was wird brauchen:
End of explanation
"""


s = '   abc   '
print('rstrip: "{}"'.format(s.rstrip()))
print('lstrip(): "{}"'.format(s.lstrip()))
print('strip(): "{}"'.format(s.strip()))

"""
Explanation: rstrip() entfernt allen Whitespace (Leerzeichen, Tabulaturen, Zeilenumbrüche etc.) am Ende einen Strings. Zusätzlich gibt es noch lstrip(), das Whitespace am Anfang eines Strings entfernt, und strip(), das Whitespace links und rechts entfernt.
End of explanation
"""


clean_names = []
for line in lines:
    clean_names.append(line.rstrip())
print(clean_names)

"""
Explanation: Zeilenumbrüche in einer Liste von Strings entfernen
Methode 1: in einer Schleife
Wenn wir nun alle Zeilenumbrüche aus unserer Liste lines entfernen wollen, können wir das in einer for-Schleife tun:
End of explanation
"""


queue = ['Anna', 'Hans', 'Berta']
queue.append('Dora')
queue

"""
Explanation: Damit haben wir auch schon die erste Listen-Methode kennengelernt, die es bei Strings (die unveränderbar sind) nicht gibt: Mit list.append(WERT)  können wir der Liste einen weiteren Wert hinzufügen. Dieser Wert wird hinten an die Liste angefügt:
End of explanation
"""


clean_names = [line.rstrip() for line in lines]
print(clean_names)

"""
Explanation: Methode 2: mit einer List Comprehension
List Comprehensions sind ein aus dem Bereich der funktionalen Programmierung kommender Ansatz, um eine Aktion auf alle Elemente eine Liste anzuwenden.
End of explanation
"""


nums = [4, 9, 17, 5, 99]
# TODO: fertig machen

"""
Explanation: Übung
Schreiben Sie eine List Comprehension, die jeden Wert der Liste nums mit sich selbst multipliziert.
End of explanation
"""


