Hausaufgaben

1) Geben Sie alle Unicode-Zeichen zwischen 34 und 250 aus und geben Sie alle aus, die keine Buchstaben oder Zahlen sind


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


['"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S']

oder


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


['"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S']

In [3]:
#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])


['"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?']

Wir können die Funktion auch direkt verwenden


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


['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']

Aber hier stimmt die Logik nicht - wir wollen ja die Zeichen ausgeben, die keine Buchstaben sind, d.h. wir müssen verneinen. Dafür gibt es, wie erwähnt, itertools.filterfalse()


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


['"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?']

2) Wie könnte man alle Dateien mit der Endung *.txt in einem Unterverzeichnis hintereinander ausgeben?


In [6]:
import glob

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


3
1
2

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.


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


Out[7]:
[1, 2, 3, 4]

Hier die Definition der Methode:
sorted(iterable[, key][, reverse])


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


Out[8]:
['e', 'd', 'c', 'b', 'a']

mit key übergeben wir eine Funktion, die das jeweilige Element aus dem Iterator bearbeitet


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


Out[9]:
['A', 'U', 'a', 'd']

In [10]:
sorted(c, key=str.upper)


Out[10]:
['a', 'A', 'd', 'U']

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

Und nun wollen wir diese Liste nach den Noten sortieren.


In [12]:
from operator import itemgetter
sorted(d, key=itemgetter(1))


Out[12]:
[('Cordula', 1), ('Vicky', 2), ('Michael', 3)]

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:


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


Out[13]:
[('auto', 12), ('mensch', 13), ('tier', 10), ('haus', 8)]

In [14]:
sorted(wl.items(), key=itemgetter(1), reverse=True)


Out[14]:
[('mensch', 13), ('auto', 12), ('tier', 10), ('haus', 8)]

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


Out[15]:
['mensch', 'auto', 'tier', 'haus']

Exkurs: Sortieren für Objekte


In [16]:
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]


Cordula	2
Vicky	3
Michael	1
Out[16]:
[None, None, None]

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


Michael	1
Cordula	2
Vicky	3
Out[17]:
[None, None, None]

Programme strukturieren

Aufgabe 1 von der letzten Sitzung:
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
"2 1 2 1" - String (sieht aber aus, wie eine Liste, die zu einem String konvertiert wurde! Also eine Variable für die Ausgabeliste.

2) Schritt: Analysieren Sie, auf welcher Datenstruktur operiert wird
"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?
String, der einen Satz enthält -> Liste von Worten


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

4) Was ist die Operation, die im Kern verlangt wird?
jedes Wort durch eine Zahl ersetzen und zwar die Anzahl der Vokale.
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:


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

Nun können wir die Lösung für unsere Aufgabe schon einmal als Pseudecode hinschreiben:

#input text = "irgend ein text" #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

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.


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

Und das Testen nicht vergessen:


In [21]:
tokenize("Dies ist ein Test. Und gleich noch einer!")


Out[21]:
['Dies', 'ist', 'ein', 'Test', 'Und', 'gleich', 'noch', 'einer']

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.


In [22]:
#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

Test, Test:


In [23]:
count_vowels("Bauernhaus")


Out[23]:
5

Ok, nun können wir alles zusammensetzen:


In [24]:
#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))


[2, 1, 2, 2, 4, 5]

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


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


Out[25]:
[2, 1, 2, 2, 4, 5]

Hier noch einmal das ganze Skript im Zusammenhang:


In [26]:
#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))


Out[26]:
[2, 1, 2, 2, 4, 5]

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.


In [27]:
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)))


Out[27]:
[2, 1, 2, 2, 4, 5]

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:


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


Out[28]:
[(1, 'a'),
 (1, 'b'),
 (1, 'c'),
 (2, 'a'),
 (2, 'b'),
 (2, 'c'),
 (3, 'a'),
 (3, 'b'),
 (3, 'c')]

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:


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


1   a
2   b
3   c

Eleganter ist die Verwendung der Methode zip, die man außerdem auch dann verwenden kann, wenn kein Index vorhanden ist.


In [30]:
for (x,y) in zip(a,b):
     print (x, " ", y)


1   a
2   b
3   c

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:


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


Out[31]:
[3, 1, 4, 2]

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:


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

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:


In [ ]:
g = (i for i in b)
type(g)

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

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.


In [ ]:
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)

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.


In [ ]:
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)

oder auch:


In [ ]:
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)

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


In [ ]:
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)

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


In [ ]:
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)

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.
Ein Beispiel. Wir multiplizieren alle Zahlen einer Liste. Erst wird 1 * 2 multipliziert. Das Ergebnis wird dann mit 3 multipliziert usw.:


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

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


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

In [ ]:
from operator import add
functools.reduce(add, x)

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
Wieviel hat jemand auf dem Konto, der Anfang 2004 750€ eingezahlt hat.

Exkurs: timeit


In [ ]:
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))

In [ ]:
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))

In [ ]:
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))

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.
Tipp 1: Die Eingabe ist eine Liste der Zahlen von 2 bis 100. Die Ausgabe ist eine Liste der Primzahlen
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.
Wie müsste man die Funktion umschreiben, um beliebig große Zahlen zu bearbeiten?

Aufgaben












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
Ausgabe: Wortliste
Zentrale Operation: Vergleiche zwei Wortlisten, welche items kommen nur in der einen oder der anderen vor.
In Pseudecode:
wordlist1 = get_wordlist(Goethe)
wordlist2 = get_wordlist(Schiller)
unique_words = compare(wordlist1, wordlist2)
output(unique_wordlist)











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).


In [ ]:
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]










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?


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











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.


In [ ]:
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)










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
Wieviel hat jemand auf dem Konto, der Anfang 2004 750€ eingezahlt hat.


In [ ]:
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)