#1a) alle unicode Z. zwischen 34 u 250 ausgeben
a = [chr(c) for c in range(34,250)]
print(a[:50])

"""
Explanation: Hausaufgaben
1) Geben Sie alle Unicode-Zeichen zwischen 34 und 250 aus und geben Sie alle aus, die keine Buchstaben oder Zahlen sind
End of explanation
"""


a = list(map(chr, range(34, 250)))
print(a[:50])

#1b) nur die ausgeben, die keine Buchstaben oder Zahlen sind
def is_no_char(c):
    if c.isalnum():
        return False
    else:
        return True
    
a = list(filter(is_no_char, [chr(c) for c in range(34,250)]))
print(a[:20])

"""
Explanation: oder
End of explanation
"""


a = list(filter(lambda c: c.isalnum(), [chr(c) for c in range(34,250)]))
print(a[:20])

"""
Explanation: Wir können die Funktion auch direkt  verwenden
End of explanation
"""


from itertools import filterfalse
a = list(filterfalse(lambda c: c.isalnum(), [chr(c) for c in range(34,250)]))
print(a[:20])

"""
Explanation: Aber hier stimmt die Logik nicht - wir wollen ja die Zeichen ausgeben, die <b>keine</b> Buchstaben sind, d.h. wir müssen verneinen. Dafür gibt es, wie erwähnt, itertools.filterfalse()
End of explanation
"""


import glob

for file in glob.glob("test\\*.*"):
    with open(file, "r") as fin:
        [print(l) for l in fin]


"""
Explanation: 2) Wie könnte man alle Dateien mit der Endung *.txt in einem Unterverzeichnis hintereinander ausgeben?
End of explanation
"""


a = [4,2,1,3]
sorted(a)

"""
Explanation: 3) Schauen Sie sich in der Python-Dokumentation die Funktionen sorted und itemgetter an. Wie kann man diese so kombinieren, dass man damit ein Dictionary nach dem value sortieren kann.
End of explanation
"""


#mit reverse drehen wir die Sortierreihenfolge um
b = ["b", "a", "d", "c","e"]
sorted(b, reverse=True)

"""
Explanation: Hier die Definition der Methode: <br/>
sorted(iterable[, key][, reverse]) <br/>
End of explanation
"""


c = ["d", "a", "A", "U"]
sorted(c)

sorted(c, key=str.upper)

#nehmen wir an, wir haben eine Liste von 2-Tuples, z.B. Namen und Noten
d = [("Michael", 3), ("Vicky", 2), ("Cordula", 1) ]

"""
Explanation: mit key übergeben wir eine Funktion, die das jeweilige Element aus dem Iterator bearbeitet
End of explanation
"""


from operator import itemgetter
sorted(d, key=itemgetter(1))

"""
Explanation: Und nun wollen wir diese Liste nach den Noten sortieren.
End of explanation
"""


wl = dict(haus=8, auto=12, tier=10, mensch=13)
[e for e in wl.items()]

sorted(wl.items(), key=itemgetter(1), reverse=True)

[a[0] for a in sorted(wl.items(), key=itemgetter(1), reverse=True)]

"""
Explanation: Die Zahl, die itemgetter als Parameter übergeben wird, ist der index im Tuple, der zum Sortieren verwendet werden soll, hier also das zweite Element. Nun können wir das auf ein Dictionary anwenden:
End of explanation
"""


class student:
    def __init__(self, name, note):
        self.name = name
        self.note = note

studenten = [student("Cordula",2), student("Vicky", 3), student("Michael", 1)]
[print(stud.name + "\t" + str(stud.note)) for stud in studenten]

from operator import attrgetter
[print(stud.name + "\t" + str(stud.note)) for stud in sorted(studenten, key=attrgetter("note"))]

"""
Explanation: Exkurs: Sortieren für Objekte
End of explanation
"""


#parses a string and returns a list of words
def tokenize (line):
    wordlist = []
    #hier passiert was Aufregendes 
    return wordlist

"""
Explanation: Programme strukturieren
Aufgabe 1 von der letzten Sitzung:<br/>
Ersetzen Sie eine Reihe von Worten durch eine Reihe von Zahlen, die die Anzahl der Vokale anzeigen. Z.B.: "Dies ist ein Satz" -> "2 1 2 1"
1) Schritt: Analysieren Sie den Input und den Output - welche Datenstruktur ist hier notwendig?
"Dies ist ein Satz" - String. Wir brauchen also eine Variable für diesen string<br/>
"2 1 2 1" - String (sieht aber aus, wie eine Liste, die zu einem String konvertiert wurde! Also eine Variable für die Ausgabeliste.<br/>
2) Schritt: Analysieren Sie, auf welcher Datenstruktur operiert wird<br/>
     "Ersetzen Sie eine Reihe von Worten" -> also auf Worten. Da wir die Ausgabe der Zahlen in der Sequenz der Worte brauchen, brauchen wir die Worte in einer Liste, die die Reihenfolge bewahrt.
3) Wie kommen Sie von der Datenstruktur des Inputs zur Datenstruktur, die Sie für die Verarbeitung brauchen?<br/>
String, der einen Satz enthält -> Liste von Worten
End of explanation
"""


#count vowels in a word, returns their number
def count_vowels(word):
    nr_of_vowels = 0
    #something will happen here
    return nr_of_vowels

"""
Explanation: 4) Was ist die Operation, die im Kern verlangt wird?<br/>
jedes Wort durch eine Zahl ersetzen und zwar die Anzahl der Vokale.<br/>
daraus können wir ableiten: wir brauchen eine Funktion, die ein Wort als Input hat und die Anzahl der Vokale als Output. Damit fangen wir an:
End of explanation
"""


#parses a string and returns a list of words
import re
def tokenize (line):
    wordlist = re.findall("\w+", line) 
    return wordlist

"""
Explanation: Nun können wir die Lösung für unsere Aufgabe schon einmal als Pseudecode hinschreiben:
So, nun müssen wir nur noch die Methoden ausfüllen und den Pseude-Code in richtigen Code verwandeln. Also erst einmal tokenize. Das zerlegen von Strings in Listen kann man mit mehreren vorgefertigten Methoden erledigen. Wir können die primitive split()-Methode nehmen, die allerdings nicht so gut geeignet ist, wenn der Text nicht nur an einem einzelnen Zeichen zerlegt werden soll. Oder wir verwenden re.findall() aus dem regular expressions-Modul, das hier deutlich flexibler ist. Eine richtige Tokenisierung müsste natürlich noch einmal komplexer sein.
End of explanation
"""


tokenize("Dies ist ein Test. Und gleich noch einer!")

"""
Explanation: Und das Testen nicht vergessen:
End of explanation
"""


#count vowels in a word, returns their number
def count_vowels(word):
    nr_of_vowels = 0
    for c in word:
        if c in "aeiouäöüAEIUOÄÖÜ":
            nr_of_vowels += 1
    return nr_of_vowels

"""
Explanation: Nun muss noch die eigentlich Kernmethode erledigt werden: Das Zählen der Vokale. Am einfachsten ist wieder einmal eine Schleife, die bei jedem Buchstaben prüft, ob es ein Vokal ist und dann einen Zähler um eins erhöht.
End of explanation
"""


count_vowels("Bauernhaus")

"""
Explanation: Test, Test:
End of explanation
"""


#input
text = "Dies ist ein langer, lausiger Textbaustein."
#output
list_of_vowels = []
#preprocess input
wordlist = tokenize(text)
#main loop
#for each word in wordlist do count_vowels and add result to list_of_vowels
#Es gibt zwei Möglichkeiten, diesen Loop zu gestalten. Erstens ganz traditionell:
for word in wordlist:
    list_of_vowels.append(count_vowels(word))
print (str(list_of_vowels))

"""
Explanation: Ok, nun können wir alles zusammensetzen:
End of explanation
"""


#output
list_of_vowels = []
list(map(count_vowels, wordlist))

"""
Explanation: Wir können  die Anwendung der Methode auf jedes Element unserer Wortliste auch einfach mit map() erledigen:
End of explanation
"""


#parses a string and returns a list of words
import re
def tokenize (line):
    wordlist = re.findall("\w+", line) 
    return wordlist

#count vowels in a word, returns their number
def count_vowels(word):
    nr_of_vowels = 0
    for c in word:
        if c in "aeiouäöüAEIUOÄÖÜ":
            nr_of_vowels += 1
    return nr_of_vowels


#input
text = "Dies ist ein langer, lausiger Textbaustein."
#output
list_of_vowels = []
#preprocess input
wordlist = tokenize(text)
#apply count method on all words in list
list(map(count_vowels, wordlist))

"""
Explanation: Hier noch einmal das ganze Skript im Zusammenhang:
End of explanation
"""


import re

#input
text = "Dies ist ein langer, lausiger Textbaustein."

#count vowels in a word, returns their number
def cv(word):
    return sum([1 for c in word if c in "aeiouäöüAEIUOÄÖÜ"])

list(map(cv, re.findall("\w+",text)))


"""
Explanation: Und hier die etwas stärker komprimierte Form - schauen Sie mal, ob Sie sehen, was hier gemacht wird. Aber tatsächlich ist die Kürze der Schreibung nicht so wichtig! Dadurch wird ein Programm nicht effizienter.
End of explanation
"""


a = [1,2,3]
b = ["a","b","c"]
[(x,y) for x in a for y in b]

"""
Explanation: Aufgabe 1
Finden Sie heraus, welche Wörter in Faust I von Goethe und Maria Stuart von Schiller nur in dem jeweiligen Text vorkommen. Begründen Sie Ihr Vorgehen.
Funktionales Programmieren - 2. Teil
Daten aus zwei Iteratoren verwenden
Wir haben bereits gesehen, wie wir Daten aus zwei Iteratoren verwenden können, wenn wir praktisch verschachtelte Schleifen brauchen:
End of explanation
"""


for i in range(len(a)):
    print (a[i], " ", b[i])

"""
Explanation: Wie gehen wir aber vor, wenn wir die beiden Iteratoren elementweise bearbeiten wollen, also die beiden ersten Elemente, dann die beiden nächsten Elemente usw.
Wenn die Iteratoren einen Index haben, dann können wir einen Counter verwenden:
End of explanation
"""


for (x,y) in zip(a,b):
     print (x, " ", y)

"""
Explanation: Eleganter ist die Verwendung der Methode zip, die man außerdem auch dann verwenden kann, wenn kein Index vorhanden ist.
End of explanation
"""


a = [3,1,4,2]
[i for i in a]

"""
Explanation: zip() beendet die Arbeit, sobald der kürzere Iterator erschöpft ist. Wenn Sie das nicht wollen, dann verwenden Sie itertools.zip_longest()
Aufgabe 2
Sie haben zwei Dateien, die eigentlich identisch sein sollen. Prüfen Sie das und geben Sie jede Zeile aus, die nicht identisch ist. (Basteln Sie sich selbst vorher zwei Testdateien, die 5 identische Zeilen und 2 nicht-identische haben). 
Aufgabe 2a (optional)
Die deutsche Sprache hat 29 Buchstaben einschließlich der Umlaute. Wieviele Wörter mit drei Buchstaben können wir daraus bilden, wenn wir vereinfachend davon ausgehen, dass der Unterschied zwischen Groß- und Kleinschreibung keine Rolle spielt und dass ein Buchstabe nur einmal in einem Wort vorkommen darf? 
Generator Expressions
Generator expression sind in der Syntax und Funktionsweise List Comprehensions sehr ähnlich. Noch einmal ein Blick auf letzere:
End of explanation
"""


#b = range(1000000000000)  #dauert sehr lange
[i for i in b]

"""
Explanation: Sie sehen, der Rückgabe-Wert ist eine Liste. Wie Sie wissen, sind Listen iterierbar, aber eine Liste ist immer ganz im Arbeitsspeicher. Das kann Probleme machen:
End of explanation
"""


g = (i for i in b)
type(g)

for x in g:
    print(x)
    if x > 10: break

"""
Explanation: In einem solchen Fall ist es sinnvoll generator expressions zu verwenden, die im Prinzip nur statt der eckigen Klammern runde verwenden. Diese geben nicht eine Liste zurück, sondern einen Iterator:
End of explanation
"""


def generate_ints(N):
    for i in range(N):
        yield i
        
#hier benutzen wir die Funktion; der Parameter N könnte beliebig groß sein, da 
#immer nur die Zahl ausgegeben wird, die gerade abgefragt wird.        
for j in generate_ints(5):
    print(j)

"""
Explanation: Kurzum, wenn Sie sehr große Datenmengen verarbeiten (oder unendlichen Listen), dann sollten Sie mit generator expressions arbeiten. 
Generators
Wenn Sie selbst Funktionen schreiben, dann können Sie als Rückgabewert natürlich Listen verwenden. Aber auch hier könnte es sein, dass die Größe der Liste möglicherweise den Arbeitsspeicher sprengt. Dann können Sie Generators verwenden. Der entscheidende Unterschied liegt in der Verwendung des Schlüsselworts yield in der Definition der Funktion. <br/>
End of explanation
"""


import random

def get_random_NGram(ngram_length):
    chars = []
    for i in range(ngram_length):
        x = random.randint(66,90)
        chars.append(chr(x))
    return "".join(chars)

get_random_NGram(5)

"""
Explanation: Noch ein -etwas länges - Beispiel. Im folgenden definieren wir erst einmal eine Funktion, die eine Liste zufälliger Buchstabenkombinationen in definierbarer Länge zurückgibt.
End of explanation
"""


import random

def get_random_NGram(ngram_length):
    return "".join([chr(random.randint(66,90)) for i in range(ngram_length)])

get_random_NGram(3)

"""
Explanation: oder auch:
End of explanation
"""


import random

def get_random_NGram(ngram_length, nr_of_ngrams):
    ngrams = []
    for j in range(nr_of_ngrams):
        chars = []
        for i in range(ngram_length):
            x = random.randint(66,90)
            chars.append(chr(x))
        ngrams.append("".join(chars))
    return ngrams

get_random_NGram(5,5)

"""
Explanation: Nun wollen wir die Funktion so umschreiben, dass Sie als Parameter außerdem die Anzahl der Ngramme enthält, die zurückgegeben werden soll:
End of explanation
"""


import random

def get_random_NGram(ngram_length, nr_of_ngrams):
    for j in range(nr_of_ngrams):
        chars = []
        for i in range(ngram_length):
            x = random.randint(66,90)
            chars.append(chr(x))
        yield "".join(chars)
    

for x in get_random_NGram(5,5):
    print(x)

"""
Explanation: Um nun zu verhindern, dass wir ein Speicherproblem bekommen, wenn die Anzahl zu groß ist, machen wir daraus einen generator:
End of explanation
"""


import functools
from operator import mul 
a = [1,2,3,4,5]
functools.reduce(mul, a)

"""
Explanation: Aufgabe 3
Schreiben Sie eine Funktion, die für beliebig große Textdateien jeweils den nächsten Satz ausgibt. Gehen Sie dabei von der (überstark vereinfachten) Regel aus, dass ein Satz durch eines dieser Zeichen [.?!] + Leerzeichen oder Absatzmarke beendet wird.
reduce
functools.reduce(func, iter, [initial_value])  wendet die Funktion func auf alle Elemente einer iterierbaren Struktur (iter) an. Die Funktion func muss als Eingabe zwei Elemente akzeptieren und ein Element ausgeben. Das Besondere der reduce-Funktion besteht darin, dass die Ausgabe der ersten Anwendung der Funktion, Teil der Eingabe der nächsten Anwendung der Funktion wird. Angenommen in der iterierbaren Datenstruktur findet sich [A, B, C, D], dann würde die Funktion func zuerst also Parameter A und B nehmen. Die Ausgabe X wird dann wiederum zur Eingabe für den Aufruf der Funktion: es berechnet dann also: func(func(A, B), C) usw. bis die Liste erschöpft ist. Sie können den Anfangswert (also den Wert von A) als initialvalue setzen.<br/>
Ein Beispiel. Wir multiplizieren alle Zahlen einer Liste. Erst wird 1 * 2 multipliziert. Das Ergebnis wird  dann mit 3 multipliziert usw.:
End of explanation
"""


a = [1,2,3]
b = [4,5,6]
c = [7,8,9]
x = [a,b,c]
x

from operator import add
functools.reduce(add, x)

"""
Explanation: Hier ein etwas realistischeres Beispiel. Das 'flatttening of a list of lists' (Denken Sie daran, dass die Addition auf Listen angewandt diese verknüpft.)
End of explanation
"""


def test():
    """Stupid test function"""
    L = [i for i in range(100)]

if __name__ == '__main__':
    import timeit
    print(timeit.repeat("test()", setup="from __main__ import test", number=1000000, repeat=3))


import random

def get_random_NGram(ngram_length):
    chars = []
    for i in range(ngram_length):
        x = random.randint(66,90)
        chars.append(chr(x))
    a = "".join(chars)
     

if __name__ == '__main__':
    import timeit
    print(timeit.repeat("get_random_NGram(5)", setup="from __main__ import get_random_NGram", number=100000, repeat=3))



def get_random_NGram(ngram_length):
    a = "".join([chr(random.randint(66,90)) for i in range(ngram_length)])

if __name__ == '__main__':
    import timeit
    print(timeit.repeat("get_random_NGram(5)", setup="from __main__ import get_random_NGram", number=100000, repeat=3))



"""
Explanation: Aufgabe 4
Hier ist eine Liste der Jahres-Zinssätze für Privatdarlehen der letzten 10 Jahre (frei erfunden!): 
2004: 4,3 - 2005: 4,0 - 2006: 3,5 - 2007: 3,0 - 2008: 2,5 - 2009: 3,2 - 2010: 3,3 - 2011: 1,8 - 2012: 1,4 - 2013: 0,7<br/>Wieviel hat jemand auf dem Konto, der Anfang 2004 750€ eingezahlt hat.                       
Exkurs: timeit
End of explanation
"""


with open("file1.txt", "r", encoding="utf-8") as f1:
    with open("file2.txt", "r", encoding="utf-8") as f2:
        [print(x + y) for x,y in zip(f1,f2) if x != y] 
                 

"""
Explanation: Hausaufgabe
Primzahlen sind Zahlen, die nur durch sich selbst und durch 1 teilbar ohne Rest sind. Die anderen Zahlen lassen sich dagegen in Faktoren zerlegen. Man kann offensichtlich alle Faktoren zerlegen, bis jede Zahl, die nicht eine Primzahl ist als Multiplikation von Primzahlen geschrieben werden kann. Schreiben Sie ein Programm, dass die Primzahlen von 2 bis 100 ermittelt. <br/>
Tipp 1: Die Eingabe ist eine Liste der Zahlen von 2 bis 100. Die Ausgabe ist eine Liste der Primzahlen<br/>
Tipp 2: Wenn Sie prüfen, ob eine Zahl eine Primzahl ist, dann können Sie bei der Wurzel der Zahl aufhören, neue Faktoren zu suchen. <br/>
Wie müsste man die Funktion umschreiben, um beliebig große Zahlen zu bearbeiten?
Aufgaben
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
Aufgabe 1
Finden Sie heraus, welche Wörter in Faust I von Goethe und Maria Stuart von Schiller nur in dem jeweiligen Text vorkommen. Begründen Sie Ihr Vorgehen.
Eingabe: zwei Text-Dateien<br/>
Ausgabe: Wortliste<br/>
Zentrale Operation: Vergleiche zwei Wortlisten, welche items kommen nur in der einen oder der anderen vor. <br/>
In Pseudecode:<br/>
wordlist1 = get_wordlist(Goethe)<br/>
wordlist2 = get_wordlist(Schiller)<br/>
unique_words = compare(wordlist1, wordlist2)<br/>
output(unique_wordlist)<br/>
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
Aufgabe 2
Sie haben zwei Dateien, die eigentlich identisch sein sollen. Prüfen Sie das und geben Sie jede Zeile aus, die nicht identisch ist. (Basteln Sie sich selbst vorher zwei Testdateien, die 5 identische Zeilen und 2 nicht-identische haben).
End of explanation
"""


import itertools
chars = list("abcdefghijklmnopqrstuvwxyzöäü")
len(list(itertools.permutations(chars, 3)))

"""
Explanation: <br/><br/><br/><br/><br/><br/><br/><br/><br/>
Aufgabe 2a
Die deutsche Sprache hat 29 Buchstaben einschließlich der Umlaute. Wieviele Wörter mit drei Buchstaben können wir daraus bilden, wenn wir vereinfachend davon ausgehen, dass der Unterschied zwischen Groß- und Kleinschreibung keine Rolle spielt und dass ein Buchstabe nur einmal in einem Wort vorkommen darf?
End of explanation
"""


def get_sentence(filehandler):    
    in_markup = False
    sentence = ""
    while True:
        c = filehandler.read(1)
        if c == "":
            break
        elif c in ".?!":
            sentence += c
            in_markup = True
        elif (c == " " or c == "\n") and in_markup == True:
            yield sentence
            sentence = ""
            in_markup = False
        else: 
            if in_markup == True:
                in_markup == False
            if c != "\n":
                sentence += c


with open("text.txt", "r", encoding="utf-8") as fin:
    for s in get_sentence(fin):
        print(s)

"""
Explanation: <br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
Aufgabe 3
Schreiben Sie eine Funktion, die für beliebig große Textdateien jeweils den nächsten Satz ausgibt. Gehen Sie dabei von der (überstark vereinfachten) Regel aus, dass ein Satz durch eines dieser Zeichen [.?!] + Leerzeichen oder Absatzmarke beendet wird.
End of explanation
"""


from functools import reduce
#we define a function which adds the yearly interests to the sum
def jahreszins(betrag, zins):
    return betrag + (betrag * zins / 100)

#we put the interests into a list
zinsen = [4.3, 4.0, 3.5, 3.0, 2.5, 3.2, 3.3, 1.8, 1.4, 0.7]
#we use reduce to calculate the result
reduce(jahreszins, zinsen, 750)

"""
Explanation: <br/><br/><br/><br/><br/><br/><br/><br/><br/>
Aufgabe 4
Hier ist eine Liste der Jahres-Zinssätze für Privatdarlehen der letzten 10 Jahre (frei erfunden!): 
2004: 4,3 - 2005: 4,0 - 2006: 3,5 - 2007: 3,0 - 2008: 2,5 - 2009: 3,2 - 2010: 3,3 - 2011: 1,8 - 2012: 1,4 - 2013: 0,7<br/>Wieviel hat jemand auf dem Konto, der Anfang 2004 750€ eingezahlt hat.
End of explanation
"""


