Tutoraufgabe 1 (Auswertungsstrategie):

Werbung
Programmierung WS12/13
Lösung - Übung 10
Prof.aa
Dr. J. Giesl
M. Brockschmidt, F. Emmes, C. Otto, T. Ströder
Tutoraufgabe 1 (Auswertungsstrategie):
Gegeben sei das folgende
Haskell-Programm:
absteigend :: Int -> [ Int ]
absteigend 0 = []
absteigend n = n : absteigend (n -1)
produkt :: [ Int ] -> Int
produkt [] = 1
produkt ( x : xs ) = x * produkt xs
summe :: [ Int ] -> Int
summe xs = summe ' xs 0
where summe ' []
a = a
summe ' ( x : xs ) a = summe ' xs ( a + x )
absteigend berechnet die absteigende Liste der natürlichen Zahlen bis hinunter zu 1. Zum
absteigend 5 die Liste [5,4,3,2,1]. Die Funktion produkt multipliziert die Elemente
Liste, beispielsweise ergibt produkt [3,5,2,1] die Zahl 30. Die Funktion summe addiert die Elemente
Liste. Zum Beispiel liefert summe [5,2,7] den Wert 14.
Die Funktion
Beispiel berechnet
einer
einer
Geben Sie alle Zwischenschritte bei der Auswertung der folgenden Ausdrücke an:
1.
produkt (absteigend 2)
2.
summe (absteigend 2)
Beachten Sie, dass
wie
Haskell eine Leftmost-Outermost Auswertungsstrategie besitzt. Allerdings sind Operatoren
* und +, die auf eingebauten Zahlen arbeiten, strikt, d.h. hier müssen vor Anwendung des Operators seine
Argumente vollständig ausgewertet worden sein.
Lösung:
produkt (absteigend 2)
→ produkt (2 : absteigend (2-1))
→ 2 * produkt (absteigend (2-1))
→ 2 * produkt (absteigend 1)
→ 2 * produkt (1 : absteigend (1-1))
→ 2 * (1 * produkt (absteigend (1-1)))
→ 2 * (1 * produkt (absteigend 0)))
→ 2 * (1 * produkt [])
→ 2 * (1 * 1)
→2 * 1
→2
summe (absteigend 2)
→ summe' (absteigend 2) 0
→ summe' (2 : absteigend (2-1)) 0
→ summe' (absteigend (2-1)) (0+2)
→ summe' (absteigend 1) (0+2)
→ summe' (1 : absteigend (1-1)) (0+2)
→ summe' (absteigend (1-1)) ((0+2)+1)
→ summe' (absteigend 0) ((0+2)+1)
→ summe' [] ((0+2)+1)
→ (0+2)+1
1
Programmierung WS12/13
Lösung - Übung 10
→ 2+1
→3
Aufgabe 2 (Auswertungsstrategie):
Gegeben sei das folgende
(7 Punkte)
Haskell-Programm:
produkt :: [ Int ] -> Int
produkt [] = 1
produkt ( x : xs ) = x * produkt xs
jedesZweite
jedesZweite
jedesZweite
jedesZweite
:: [ Int ] -> [ Int ]
[] = []
[x] = [x]
( x : _ : xs ) = x : jedesZweite xs
minus10 :: [ Int ] -> [ Int ]
minus10 [] = []
minus10 ( x : xs ) = x - 10 : minus10 xs
Die Funktion
Zahl
30.
produkt
Die Funktion
multipliziert die Elemente einer Liste, beispielsweise ergibt
jedesZweite
produkt [3,5,2,1]
die
bekommt eine Liste als Eingabe und gibt die gleiche Liste zurück, wobei
jedes zweite Element gelöscht wurde. So ergibt
jedesZweite [1,2,3] die Liste [1,3]. Die Funktion minus10
gibt seine Eingabeliste zurück, wobei bei von jedem Element 10 subtrahiert wurde.
Geben Sie alle Zwischenschritte des Ausdrucks
produkt (jedesZweite (minus10 [3,2,1])) bei der Ausp, j und m statt produkt, jedesZweite und minus10.
wertung an. Schreiben Sie hierbei um Platz zu sparen
Hinweise:
ˆ
Beachten Sie, dass
ratoren wie
*
und
Haskell eine Leftmost-Outermost Auswertungsstrategie besitzt. Allerdings sind Ope-
+,
die auf eingebauten Zahlen arbeiten, strikt, d.h. hier müssen vor Anwendung des
Operators seine Argumente vollständig ausgewertet worden sein (wobei zunächst das linke Argument
ausgewertet wird).
Lösung:
p (j (m [3,2,1]))
→ p (j (3 - 10 : m [2,1]))
→ p (j (3 - 10 : 2 - 10 : m [1]))
→ p (3 - 10 : j (m [1]))
→ (3 - 10) * p (j (m [1]))
→ (-7) * p (j (m [1]))
→ (-7) * p (j (1 - 10 : m []))
→ (-7) * p (j (1 - 10 : []))
→ (-7) * p (1 - 10 : [])
→ (-7) * ((1 - 10) * p [])
→ (-7) * ((-9) * p [])
→ (-7) * ((-9) * 1)
→ (-7) * (-9)
→ 63
Tutoraufgabe 3 (Listen):
Seien
x, y
ganze Zahlen vom Typ
Int
und
xs
und
ys
Listen der Längen
n
und
m
vom Typ
[Int].
Welche der folgenden Gleichungen zwischen Listen sind richtig und welche nicht? Begründen Sie Ihre Antwort.
2
Programmierung WS12/13
Lösung - Übung 10
Falls es sich um syntaktisch korrekte Ausdrücke handelt, geben Sie für jede linke und rechte Seite auch an, wie
viele Elemente in der jeweiligen Liste enthalten sind und welchen Typ sie hat.
Beispiel : Die Liste [[1,2,3],[4,5]] hat den Typ [[Int]] und enthält 2 Elemente.
Hinweise:
ˆ
Hierbei steht
++
für den Verkettungsoperator für Listen. Das Resultat von
entsteht, wenn die Elemente aus
ys
in der Reihenfolge wie sie in
ys
xs ++ ys
ist die Liste, die
stehen an das Ende von
xs
angefügt werden.
Beispiel : [1,2]
ˆ
++ [1,2,3] = [1,2,1,2,3]
Falls linke und rechte Seite gleich sind, genügt
eine
Angabe des Typs und der Elementzahl
a) x:xs = [x] ++ xs
b) (x:y):xs = x:y:xs
c) [x,y,xs] = x:y:xs
d) x:y:((x:[x]) ++ xs) = [x,y,x] ++ (x:xs)
e) []:[[],[[1]]] = [[],[]]:[[[1]]]
Lösung:
a)
Beide Ausdrücke repräsentieren die gleichen Listen der Länge
b)
Der erste Ausdruck ist nicht typkorrekt, da im Teilausdruck
n+1
x:y
[Int].
und vom Typ
die Variable
y
keine Liste ist. Sie
kann somit nicht als zweites Argument des Konstruktors : verwendet werden. Der rechte Ausdruck ist
typkorrekt und repräsentiert eine Liste der Länge
n+2
vom Typ
[Int].
Die Ausdrücke sind also nicht
gleich.
c)
Der linke Ausdruck ist nicht typkorrekt, da
x
und
y
nicht den gleichen Typ wie
xs
haben, aber beide in
der gleichen Liste enthalten sind. Der rechte Ausdruck repräsentiert eine Liste der Länge
vom Typ
d)
[Int].
n+2
und ist
Die Ausdrücke sind also nicht gleich.
Beides sind typkorrekte Listenausdrücke (äquivalent zu
vom Typ
e)
[Int].
[x,y,x,x] ++ xs der Länge n + 4 und ebenfalls
Die Ausdrücke sind also gleich.
[[],[],[[1]]], also eine dreielementige Liste, die
[Int]. Der Typ des gesamten Ausdrucks
ist demnach [[[Int]]]. Der zweite Ausdruck ergibt ausgeschrieben die Liste [[[],[]],[[1]]], also eine
zweielementige Liste, die Listen von Listen enthält. Auch hier ist der Typ der innersten Liste [Int] und
der Typ des gesamten Ausdrucks ist [[[Int]]]. Da die beiden Ausdrucke zwar den gleichen Typ haben,
Der linke Ausdruck ergibt ausgeschrieben die Liste
Listen von Listen enthält. Der genaue Typ der innersten Liste ist
aber nicht die gleichen Listen darstellen, sind die Ausdrücke also nicht gleich.
Aufgabe 4 (Listen):
Seien
x, y, z
ganze Zahlen vom Typ
(9 Punkte)
Int
und
xs
und
ys
Listen der Längen
n
und
m
vom Typ
[Int].
Welche der folgenden Gleichungen zwischen Listen sind richtig und welche nicht? Begründen Sie Ihre Antwort.
Falls es sich um syntaktisch korrekte Ausdrücke handelt, geben Sie für jede linke und rechte Seite auch an, wie
viele Elemente in der jeweiligen Liste enthalten sind und welchen Typ sie hat.
Beispiel : Die Liste [[1,2,3],[4,5]] hat den Typ [[Int]] und enthält 2 Elemente.
Hinweise:
ˆ
Falls linke und rechte Seite gleich sind, genügt wiederum
a) [] ++ [xs] = [] : [xs]
3
eine
Angabe des Typs und der Elementzahl
Programmierung WS12/13
Lösung - Übung 10
b) [[]] ++ [x] = [] : [x]
c) [x] ++ [y] = x : [y]
d) x:y:z:(xs ++ ys) = [x,y,z] ++ xs ++ ys
e) [(x:xs):[ys],[[]]] = (([]:[]):[]) ++ ([([x] ++ xs),ys]:[])
Lösung:
a)
Beide Ausdrücke haben den Typ
[[Int]].
Jedoch hat die erste Liste ein Element, während die zweite
Liste zwei Elemente besitzt. Die Ausdrücke sind also nicht gleich.
b)
Beide Ausdrücke sind nicht typkorrekt. Daher würde die Gleichung in
Haskell nicht gelten. Allerdings
[[],x]
würden beide Ausdrücke die gleiche (ungültige) Liste darstellen, nämlich
(somit sind beide Ant-
worten, ob die Gleichung gilt oder nicht, zulässig). Diese Liste ist nicht typkorrekt, da sie sowohl eine
Int
Liste, als auch einen
c)
Wert enthält.
Die Gleichung gilt, denn beide Ausdrücke repräsentieren die Liste
[x,y]
vom Typ
[Int],
welche zwei
Elemente enthält.
x,
y, z, anschlieÿend die n Elemente der Liste xs und schlieÿlich die m Elemente der Liste ys enthält. Diese
Liste enthält also insgesamt 3 + n + m Elemente und ist vom Typ [Int].
d)
Die Gleichung gilt, denn beide Ausdrücke repräsentieren die gleiche Liste, welche zuerst die Elemente
e)
Beide Ausdrücke sind vom gleichen Typ
[[[Int]]] und sind Listen mit zwei Elementen. Diese Elemente
[[]] und andererseits die Liste [x:xs,ys]), allerdings ist ihre
sind auch noch gleich (einerseits die Liste
Reihenfolge in den beiden Ausdrücken unterschiedlich, sodass die Gleichung nicht gilt.
Tutoraufgabe 5 (Programmieren):
Implementieren Sie alle der im Folgenden beschriebenen Funktionen in
Typdeklarationen an. Verwenden Sie auÿer Listenkonstruktoren
Vergleichsoperatoren wie
<=, ==,. . . keine
[]
und
Haskell.
:
Geben Sie jeweils auch die
(und deren Kurzschreibweise) und
vordenierten Funktionen (dies schlieÿt auch arithmetische Opera-
toren ein), auÿer denen, die in den jeweiligen Teilaufgaben explizit erlaubt werden.
a) sekunden h m s
h Stunden, m Minuten und s Sekunden vergehen. So
sekunden 5 4 3 den Wert 18243. Die Funktion darf sich auf negativen Eingaben
dürfen hier + und * verwenden.
Gibt die Anzahl der Sekunden zurück, die in
berechnet zum Beispiel
beliebig verhalten. Sie
b) turm x y
x ↑↑ y .
Berechnet den Potenzturm
Der Potenzturm ist die logische Weiterentwicklung von Multiplikation
und Potenzfunktion:
Multiplikation
a·n
:=
a + a + ··· + a
{z
}
|
an
:=
a
| · a ·{z. . . · a}
n
Potenz
n
a·
a ↑↑ n
Potenzturm
Beispiele:
2 2(2 )
ˆ 2 ↑↑ 4 = 2
4
= 2(2
)
= 216 = 65.536
4
:=
a
|
(aa )
··
{z
n
!
}
Programmierung WS12/13
Lösung - Übung 10
ˆ 3 ↑↑ 3 = 7.625.597.484.987
Die Funktion darf sich auf negativen Eingaben beliebig verhalten. Sie dürfen auf
- und ^ (zur Berechnung
der Potenz) zurückgreifen.
Hinweise:
ˆ
Auch in
Haskell gibt es unterschiedliche Integer-Datentypen mit verschiedenen Wertebereichen, wes-
turm 3 3 nicht das erwartete Ergebnis 7625597484987 liefert.
Integer statt Int für die Darstellung ganzer Zahlen verwenden, vermeiden
halb in Ihrer Lösung vermutlich
Wenn Sie den Datentyp
Sie das Problem.
c) wurzel x
Berechnet die abgerundete dritte (!) Wurzel aus
4.
x. Zum Beispiel liefert der Aufruf wurzel 124 den Wert
- und * verwenden.
Die Funktion darf sich auf negativen Eingaben beliebig verhalten. Sie dürfen hier
d) getEnd xs
Berechnet das letzte Element der
23.
Int-Liste xs. Beispielsweise berechnet getEnd [12, 7, 23] den Wert
Die Funktion darf sich auf leeren Listen beliebig verhalten.
e) insertEnd x ys
Berechnet die
Int-Liste,
Beispielsweise berechnet
f ) anzahl x ys
Berechnet die Anzahl der Vorkommen von
3 [1,2,3,4,5,4,3,2,1]
x an das Ende
[12, 7, 23].
die entsteht, wenn man den Wert
insertEnd [12, 7] 23
den Wert
2.
die Liste
der
Int-Liste ys
einfügt.
x in der Int-Liste ys. Zum Beispiel liefert der Aufruf anzahl
+ verwenden.
Sie dürfen hier
g) einfuegen x ys
Int-Liste, die entsteht, wenn man den Wert x so in die sortierte Int-Liste ys einfügt, dass
einfuegen 5 [1,3,5,7] den
[1,3,5,5,7].
Berechnet die
diese anschlieÿend weiterhin sortiert ist. Zum Beispiel liefert der Aufruf
Wert
h) insSort xs
Sortiert die
Int-Liste xs. Verwenden Sie dabei die Funktion einfuegen. Der Aufruf insSort [5,3,1,8]
[1,3,5,8].
liefert dann beispielweise den Wert
i) zipping xs ys
Berechnet die Kombination der beiden
Int-Listen xs und ys im Reiÿverschlussprinzip. Das heiÿt, dass
xs, dann das erste Element von ys, dann das zweite Element
das Ergebnis zuerst das erste Element von
von
xs usw. enthält. Wenn eine der beiden Listen leer ist, sollen nur noch Elemente aus der anderen Liste
zipping [1,2,5,8,7,6] [3,9,4] die Liste [1,3,2,9,5,4,8,7,6].
folgen. Beispielsweise berechnet
j) flach xs
Berechnet für eine Liste von
Eingabe
xs
Int-Listen
eine ache Liste, die die Elemente der inneren Listen der
in der ursprünglichen Reihenfolge enthält. Der Aufruf
zum Beispiel den Wert
[1,5,2,2,3].
Lösung:
-- a
sekunden :: Int -> Int -> Int -> Int
sekunden h m s = 60*60* h + 60* m + s
-- b
turm :: Integer -> Integer -> Integer
turm _ 0 = 1
turm x y = x ^ ( turm x (y -1))
5
flach [[1,5,2],[],[2,3]]
liefert
Programmierung WS12/13
Lösung - Übung 10
-- c
wurzel :: Int -> Int
wurzel 0 = 0
wurzel x = wurzelH x
where wurzelH :: Int -> Int
wurzelH w | w * w * w <= x = w
| otherwise = wurzelH (w -1)
-- d
getEnd :: [ Int ] -> Int
getEnd [ x ] = x
getEnd ( _ : xs ) = getEnd xs
-- e
insertEnd :: Int -> [ Int ] -> [ Int ]
insertEnd x
[] = [ x ]
insertEnd x ( y : ys ) = y : insertEnd x ys
-- f
anzahl :: Int -> [ Int ] -> Int
anzahl _ []
= 0
anzahl x ( y : ys ) | x == y
= 1 + anzahl x ys
| otherwise
=
anzahl x ys
-- g
einfuegen :: Int -> [ Int ] -> [ Int ]
einfuegen x []
= [x]
einfuegen x ( y : ys ) | x < y
= x : y : ys
| otherwise = y : einfuegen x ys
-- h
insSort :: [ Int ] -> [ Int ]
insSort []
= []
insSort ( x : xs ) = einfuegen x ( insSort xs )
-- i
zipping :: [ Int ] -> [ Int ] -> [ Int ]
zipping []
ys = ys
zipping ( x : xs ) ys = x : zipping ys xs
-- j
flach
flach
flach
flach
:: [[ Int ]] -> [ Int ]
[]
= []
(
[]: ys ) = flach ys
(( x : xs ): ys ) = x : flach ( xs : ys )
Aufgabe 6 (Programmieren):
(1+1+2+1+1+1+2+2+2+2 = 15 Punkte)
Implementieren Sie alle der im Folgenden beschriebenen Funktionen in
Typdeklarationen an. Verwenden Sie auÿer Listenkonstruktoren
Vergleichsoperatoren wie
<=, ==,. . . keine
[]
und
Haskell.
:
Geben Sie jeweils auch die
(und deren Kurzschreibweise) und
vordenierten Funktionen (dies schlieÿt auch arithmetische Opera-
toren ein), auÿer denen, die in den jeweiligen Teilaufgaben explizit erlaubt werden.
a) millimeter f z
Gibt (halbwegs präzise) aus, wie viele Millimeter
6
f
Fuÿ und
z
Zoll sind. Gehen Sie hierbei davon aus,
Programmierung WS12/13
Lösung - Übung 10
305mm entspricht und ein Zoll genau 25mm ist. So berechnet
1625. Die Funktion darf sich auf negativen Eingaben beliebig
dass ein Fuÿ
4
den Wert
und
*
zum Beispiel
millimeter 5
+
verhalten. Sie dürfen hier
verwenden.
b) mult x y
Berechnet
x · y.
Sie dürfen dazu
+
und
-
verwenden. Die Funktion darf sich auf negativen Eingaben
beliebig verhalten.
c) bLog x
x. Somit liefert bLog 1 den Wert 0, bLog 5
5. Sie dürfen hier + und * verwenden. Die Funktion darf sich auf
Eingabe 0 beliebig verhalten.
Berechnet den aufgerundeten Logarithmus zur Basis 2 von
den Wert
3
und
bLog 32
den Wert
negativen Eingaben oder der
d) getLastTwo xs
Int-Liste xs. Beispielsweise berechnet getLastTwo
[12, 7, 23] den Wert [7, 23]. Die Funktion darf sich auf Listen der Länge 0 und 1 beliebig verhalten.
Berechnet die Teilliste der letzten zwei Elemente der
e) singletons x
x
Berechnet eine Liste mit
Elementen, wobei jedes Listenelement eine einelementige Liste ist. Die Ele-
mente der einelementigen Listen sind die Zahlen
die Liste
hier
-
[[3],[2],[1]].
x, x − 1,
. . . , 1. Beispielsweise berechnet
singletons 3
Die Funktion darf sich auf negativen Eingaben beliebig verhalten. Sie dürfen
verwenden.
f ) cleanEmpty xs
Die Eingabe ist eine Liste von Listen von Zahlen (vom Typ
[[Int]]).
Die Funktion berechnet die Liste
von Listen von Zahlen, die aus der Eingabe entsteht, indem alle leeren Listen aus dieser entfernt werden.
Beispielsweise berechnet
cleanEmpty [[5,6],[],[8],[]]
g) kleinstes xs
Berechnet das kleinste Element, das in der
kleinstes [42, 7, 23]
den Wert
7.
die Liste
Int-Liste xs
[[5,6],[8]].
vorkommt. Zum Beispiel liefert der Aufruf
Die Funktion darf sich auf leeren Listen beliebig verhalten.
h) nachHinten x xs
Verschiebt die x ersten Elemente der Liste xs an das Ende. Beispielsweise berechnet nachHinten 2
[1,2,3,4] die Liste [3,4,1,2]. Sie dürfen hier ++ und - verwenden. Die Funktion darf sich auf negativen
Eingaben für x oder bei einer leeren Eingabeliste beliebig verhalten.
i) einpacken xs ys
Das erste Argument
xs
ist eine Liste von Listen von Zahlen (vom Typ
ist eine einfache Liste von Zahlen (vom Typ
[Int]).
[[Int]]),
das zweite Argument
Die Funktion ersetzt leere Listen in der ersten
Eingabeliste durch einelementige Listen. Der Inhalt dieser einelementigen Listen ist die jeweils nächste
Zahl aus der zweiten Eingabeliste. Beispielsweise berechnet
Liste
[[5,6],[1],[8],[2]].
einpacken [[5,6],[],[8],[]] [1,2]
die
Wenn im zweiten Argument nicht genug Zahlen zur Verfügung stehen,
werden zusätzliche leere Listen im ersten Argument leer gelassen. Wenn im zweiten Argument zu viele
Zahlen zur Verfügung stehen, werden diese ignoriert.
j) listAdd x xs
Addiert jeweils das
x
n-te Listenelement auf das (n+1)-te Listenelement, wobei auf das erste Listenelement
listAdd 5 [1,9,3] die Liste [6,10,12]. Sie dürfen hier +
addiert wird. Beispielsweise berechnet
verwenden.
Lösung:
-- a
millimeter :: Int -> Int -> Int
millimeter feet inch = 305* feet + 25* inch
7
Programmierung WS12/13
Lösung - Übung 10
-- b
mult :: Int -> Int -> Int
mult _ 0 = 0
mult x y = x + mult x (y -1)
-- c
bLog :: Int -> Int
bLog 1 = 0
bLog x = bLogH 2 1
where
bLogH :: Int -> Int -> Int
bLogH y r | x > y
= bLogH ( y *2) ( r +1)
| otherwise = r
-- d
getLastTwo :: [ Int ] -> [ Int ]
getLastTwo [x , y ]
= [x , y ]
getLastTwo ( _ : x : xs ) = getLastTwo ( x : xs )
-- e
singletons :: Int -> [[ Int ]]
singletons 0 = []
singletons n = [ n ] : singletons (n -1)
-- f
cleanEmpty
cleanEmpty
cleanEmpty
cleanEmpty
:: [[ Int ]] -> [[ Int ]]
[]
= []
([]: xs ) = cleanEmpty xs
( x : xs ) = x : cleanEmpty xs
-- g
kleinstes :: [ Int ] -> Int
kleinstes ( x : xs ) = kleinstesH x xs
where
kleinstesH :: Int -> [ Int ] -> Int
kleinstesH m [] = m
kleinstesH m ( y : ys ) | m < y
= kleinstesH m ys
| otherwise = kleinstesH y ys
-- alternative Loesung :
kleinstes ' :: [ Int ] -> Int
kleinstes ' [ x ] = x
kleinstes ' ( x : y : xs ) | x < y
= kleinstes ' ( x : xs )
| otherwise = kleinstes ' ( y : xs )
-- h
nachHinten :: Int -> [ Int ] -> [ Int ]
nachHinten _ [] = []
nachHinten n xs = nachHintenHelp [] n xs
where nachHintenHelp :: [ Int ] -> Int -> [ Int ] -> [ Int ]
nachHintenHelp xs 0 ys
= ys ++ xs
nachHintenHelp xs n ( y : ys ) = nachHintenHelp ( xs ++ [ y ]) (n -1) ys
-- alternative Loesung :
nachHinten ' :: Int -> [ Int ] -> [ Int ]
nachHinten ' _ []
= []
8
Programmierung WS12/13
Lösung - Übung 10
nachHinten ' 0 xs
= xs
nachHinten ' n ( x : xs ) = nachHinten ' (n -1) ( xs ++[ x ])
-- i
einpacken
einpacken
einpacken
einpacken
einpacken
:: [[ Int ]] -> [ Int ] -> [[ Int ]]
[]
_
= []
xs
[]
= xs
([]: xs ) ( y : ys ) = [ y ] : einpacken xs ys
( x : xs ) ( y : ys ) = x
: einpacken xs ( y : ys )
-- j
listAdd :: Int -> [ Int ] -> [ Int ]
listAdd _ []
= []
listAdd z ( x : xs ) = ( z + x ) : listAdd x xs
9
Herunterladen