print('Hello, world!')

"""
Explanation: Python Tutorial
Eine minimale Einführung in Python für Studierende mit Programmiererfahrung die keinen Anspruch auf Vollständigkeit erhebt.
Ausführlichere Einführungen und Tutorials finden sich an zahlreichen Stellen im Internet. Beispielsweise

Learn Python the Hard Way
Software Carpentry
PeP et al. Toolbox Workshop
Python.org Beginners Guide

Installation
Um Jupyter (formals IPython) Notebooks interaktiv auf deinem Computer benutzen zu können, musst du zunächst ein paar Pakete installieren. Falls du Linux oder Mac OS benutzt und dich etwas auskennst, kannst du einfach deinen Paketmanager verwenden. Für diesen Kurs verwenden wir Python 3.5 mit den Paketen numpy, scipy und matplotlib. Zusätzlich installiere bitte das jupyter Paket um die interaktiven Notebooks nutzen zu können. Falls dir das zu viel Aufwand ist, gibt es den Anaconda Installer der alle nötigen Pakete mitbringt und auf allen gängigen Plattformen funktioniert. Eine ausführliche Anleitung dafür findet sich auf den Seiten des PeP et al. Toolbox Workshops.
Interaktives Notebook
Dieses interaktive Notebook enthält ausführbare Zellen. Um eine Zelle auszuführen, bringe sie in den Fokus (z.B. durch Anklicken) und drücke Strg+Enter.
End of explanation
"""


# Dies ist ein Kommentar :)

x = 1  # x ist ein int
print(x)

x = 'Hallo, Welt!'  # x ist jetzt ein string
print(x)

y = 3.1415  # y is ein float
print(y)

z = [1, 'a', 2.7182]  # z ist eine (heterogene) Liste mit drei Einträgen
                      # Auch wenn es vom Typsystem nicht gefordert wird,
                      # ist es eine gute Idee, Listen nur homogen zu befüllen.
z = [1, 2, 3]
print(z)
print(z[1])
print(z[0:-1]) # Mit Hilfe von "Slices" können wir Teile von Listen addressieren.
               # Die Syntax ist dabei {Anfang}:{Ende}:{Schrittweite} wobei negative
               # Indices vom Ende der Liste gezählt werden.

(a, b) = (100, 'Zaphod')  # Tuple können benutzt werden, um Ausdrücke zu entpacken
                        # und so effektiv mehrere Variablen gleichzeitig zuzuweisen
                        # Die Klammern können dabei weggelassen werden
a, b = 42, 'Ford'
print(a)
print(b)

x = 'Die Antwort ist {}.'.format(a)  # Strings lassen sich formatieren, so können die Inhalte
                                   # von Variablen bequem ausgegeben werden.
print(x)

"""
Explanation: Variablen, Datentypen, Operatoren
Python hat ein dynamisches Typsystem, das bedeutet dass sich der Typ einer Variablen von Zuweisung zu Zuweisung ändern kann. Die eingebauten Typen von Python sind

Der nullwertige Typ NoneType und sein einziger Wert None
Den wahrheitswertigen Typen bool und seine Werte True und False
Numerische Typen
int z.B. 42
float z.B. 3.1415
complex z.B. 2+3j


Sequenzen
string z.B. 'Hallo, Welt!'
list z.B. [1, 2, 3]
tuple z.B. (1, 'a', 3.1415)


Den Abbildungstypen dict, z.B. {'name': 'Hans', 'alter': 42}
Den Mengentypen set, z.B. {1, 2, 3}
End of explanation
"""


not []

"""
Explanation: Jeder dieser Typen lässt sich in einem wahrheitswertigen Kontext verwenden. In einem solchen ist beispielsweise ein leerer String oder eine leere Liste gleichbedeutend mit False.
End of explanation
"""


a = 1.337
b = a * 5
c = b ** 10
c

"""
Explanation: Mit Hilfe von Operatoren lassen sich Typen verknüpfen. Für numerische Typen gibt es beispielsweise die arithmetischen Operatoren

+ Addition
- Subtraktion
* Multiplikation
/ Division
// ganzzahlige Division
% Restwertbildung
** Potenzieren
End of explanation
"""


name = ''
if 5 == 3:
    print('Irgendwas stimmt mit dem Universum nicht.')
elif name:  # Hier wird die Wahrheitswertigkeit verwendet
    print('Hallo, {}!'.format(name))
else:  # Setze einen Namen ein, um nicht hier zu landen
    print('Nun, das ist jetzt etwas peinlich…')

i = 0
while i < 5:
    print(i)
    i += 1

"""
Explanation: Eine vollständige Übersicht der Typen und verfügbaren Operationen findet sich in der offiziellen Dokumentation.
Konstrollstrukturen, Schleifen, Iteration
Auch in Python gibt es if-Verzweigungen und (selten benutzte) while-Schleifen
End of explanation
"""


names = ['Klaus', 'Dieter', 'Hans']
for name in names:
    print('Hello {}'.format(name))
    
for i in range(5):  # Praktisch, um Zahlenfolgen zu erzeugen.
    print(i)

"""
Explanation: Eine for-Schleife wie in C gibt es in Python nicht. Das for-Schlüsselwort kommt immer in Begleitung seines Freundes in; die beiden sind unzertrennlich. Damit lässt sich über Sequenzen iterieren.
End of explanation
"""


for index, name in enumerate(names):
    print('Person {} heißt {}.'.format(index, name))

"""
Explanation: Das liest sich doch deutlich angenehmer als die while-Schleife, oder? Falls du mal explizit die Indices einer Sequenz brauchst, hilft die enumerate Funktion.
End of explanation
"""


def square(x):
    """This function squares its input.
        x - A value of a type that implements `**` (power).
    """
    return x ** 2

print(square(4))

help(square)

"""
Explanation: Funktionen
Mit print, range und enumerate haben wir bereits Beispiele für Funktionen und die Aufrufsyntax gesehen. Um eigenen Funktionen zu definieren verwenden wir das def Schlüsselwort. Außerdem geben wir der Funktion eine Beschreibung. Die Beschreibung ist zwar optional, kann aber immens zum Verständnis des Codes beitragen. Sie wird für die automatisch generierte Hilfe der Funktion verwendet. Sie sollte in Englisch sein um unseren Code portabel zu halten.
End of explanation
"""


for i in range(100):
    print(i)

"""
Explanation: Schau dir doch mal die Hilfe zu print an und finde heraus, wie wir verhindern können, dass nach jedem Aufruf von print eine neue Zeile begonnen wird. Passe dann die folgende Zelle so an, dass alle Zahlen durch Leerzeichen getrennt in der selben Zeile erscheinen.
End of explanation
"""


greetings = {
    'English': 'Hello, {}!',
    'Deutsch': 'Hallo, {}!',
    'Francais': 'Salut, {}!',
    'Espagnol': '¡Hola, {}!'
}

def greet(name, language=None):
    if language is None:  # So wird ein Standardwert für Sequenztypen definiert
        language = 'English'
    greeting = greetings.get(language)
    if greeting is None:
        print("Well, this is embarassing. I don't speak {}.".format(language))
    else:
        print(greeting.format(name))
        
greet('William')
greet('Wilhelm', language='Deutsch')
greet('Guillaume', 'Francais')  # Wenn die Reihenfolge stimmt, kann das Schlüsselwort
                                # auch weggelassen werden.
greet('Guillermo', language='Espagnol')
greet('Guglielmo', language='Italiano')

"""
Explanation: Funktionen können mehrere Argumente annehmen. Dabei wird zwischen Argumenten und Schlüsselwortargumenten unterschieden. Primitive Datentypen wie int können als Schlüsselwortargument vordefiniert werden, da sie "by value" übergeben werden. Komplexere Datentypen wie zum Beispiel Listen werden als Referenzen übergeben was zur Folge hat, dass ein Standardwert innerhalb der Funktion verändert werden könnte. Deswegen wird i.d.R. das Verfahren aus dem untenstehenden Beispiel verwendet.
End of explanation
"""


[x ** 2 for x in range(10)]

"""
Explanation: Comprehension
Listen, bzw. Sequenzen allgemein, aus Ausdrücken generieren zu können ist eines der stärksten Features von Python. Um zum Beispiel eine Liste der Quadrate aller Zahlen in einer anderen Liste zu berechnen benötigen wir nur eine einzige Zeile Code
End of explanation
"""


[x for x in range(10) if x % 2 != 0]

"""
Explanation: Auf die gleiche Art lassen sich auch Sequenzen filtern.
End of explanation
"""


odd_squares = [x ** 2 for x in range(10) if x % 2 == 1]
print(odd_squares)

nums = [z for z in [x + y for x in range(3) for y in range(3, 6)] if z < 6]
print(nums)

"""
Explanation: Das Ganze lässt sich natürlich kombinieren und verschachteln.
End of explanation
"""


