1 - Korrektur 2 - Funktionen als Daten

Werbung
1 - Korrektur
2 - Funktionen als Daten
Wir definieren eine Menge analog zu den Feldern aus der Vorlesung:
1
type Set a = a -> Bool
Eine Menge wird also dargestellt durch ihre charakteristische Funktion, die das Enthaltensein berechnet.
Die leere Menge enthält natürlich keine Elemente:
2
3
empty :: Set a
empty _ = False
Ein Element können wir wie folgt in die Menge einfügen:
4
5
insert :: Eq a => a -> Set a -> Set a
insert x s y = if x == y then True else s y
Wir können genauso gut aber auch eine einelementige Menge konstruieren und diese mit der ursprünglichen
Menge per union vereinigen:
6
7
insert' :: Eq a => a -> Set a -> Set a
insert' x s = union (\y -> x == y) s
Dem schreibfaulen Haskell-Programmierer ist auch diese Lösung noch zu lang, weswegen er zu folgender Form
kommt:
8
9
10
insert'' x s = union (x ==) s
-- Section für lambda-Ausdruck
insert'' x s = union ((==) x) s
-- Argument herausziehen
insert'' x s = (union . (==)) x s -- Umordnen
Nun tauchen x und s auf beiden Seiten nur an letzter Stelle auf, sodass wir diese Parameter auch weglassen
können (η-Reduktion):
11
12
insert'' :: Eq a => a -> Set a -> Set a
insert'' = union . (==)
remove könnten wir wie insert definieren, dabei aber False zurückgeben. Stattdessen führen wir remove aber
auf difference zurück:
13
14
remove :: Eq a => a -> Set a -> Set a
remove x m = difference m (x ==)
Und für die Schreibfaulen:
15
16
17
remove' :: Eq a => a -> Set a -> Set a
-- remove' x m = (flip difference) (x==) m
remove' = flip difference . (==)
isElem entspricht der Anwendung der charakteristischen Funktion auf den zu testenden Wert:
18
19
isElem :: a -> Set a -> Bool
isElem x s = s x
Auch isElem kann man punktfrei definieren:
20
21
22
23
24
25
isElem' :: a -> Set a -> Bool
isElem x s = s $ x
-- isElem x s = ($) s x
-- isElem x s = flip ($) x s
-- isElem = flip ($)
-- isElem' = flip id
1
union und intersection kann man implementieren, indem man das logische oder bzw. und auf Mengen
überträgt. Dies machen wir mit einer Hilfsfunktion lift:
26
27
union :: Set a -> Set a -> Set a
union = lift (||)
28
29
30
intersection :: Set a -> Set a -> Set a
intersection = lift (&&)
31
32
33
lift :: (a -> b -> c) -> (d -> a) -> (d -> b) -> d -> c
lift op f g x = op (f x) (g x)
Das Mengenkomplement entspricht der Negation der charakteristischen Funktion:
34
35
36
37
complement :: Set a -> Set a
complement m x = not (m x)
-- complement m = not . m
-- complement = (not .)
Die Differenz zweier Mengen ist die Schnittmenge aus der ersten und dem Komplement der zweiten Menge:
38
39
40
difference :: Set a -> Set a -> Set a
difference m n = intersection m (complement n)
-- difference m = intersection m . complement
Aus einer Liste konstruieren wir eine Menge, indem wir für einen Wert schauen ob er in der Liste enthalten ist:
40
41
42
listToSet :: Eq a => [a] -> Set a
listToSet xs x = x `elem` xs
-- listToSet = flip elem
Vorteile:
• einfache Implementierung
• endliche Darstellung (einiger) unendlicher Mengen
Nachteile:
• Zugriffszeit abhängig von der Anzahl vorhergehender Operationen
• Teilmengenprädikat nicht implementierbar (ebenso isEmpty oder setToList)
3 - Divide and Conquer
Wir definieren zunächst die Funktion dc zum Lösen vom Divide&Conquer-Problemen. Die einzelnen Parameter
führen zu der folgenden Typsignatur:
1
2
3
4
5
6
7
dc :: (p -> Bool)
-> (p -> [p])
-> (p -> s)
-> ([s] -> s)
-> p
-> s
dc trivial divide
-- Trivialitätstest
-- Aufteilung des Problems
-- Lösen eines trivialen Problems
-- Kombination von Lösungen
-- Problem
-- Lösung
solve combine p
Wenn das Problem trivial ist, so lösen wir es direkt:
8
| trivial p = solve p
Ansonsten teilen wir das Problem auf, lösen die Einzelprobleme und kombinieren anschließend die Einzellösungen:
2
9
10
| otherwise = combine $ map conquer $ divide p
where conquer = dc trivial divide solve combine
Es folgt die Funktion split:
11
12
13
14
split :: (a -> Bool) -> [a] -> ([a], [a])
split _ []
= ([], [])
split p (x:xs) = if p x then (x:ys, zs) else (ys, x:zs)
where (ys, zs) = split p xs
Die findige Studentin wird feststellen, dass man split jedoch auch gut mittels foldr implementieren kann:
15
16
17
split' :: (a -> Bool) -> [a] -> ([a], [a])
split' p = foldr dispatch ([], [])
where dispatch x (ys, zs) = if p x then (x:ys, zs) else (ys, x:zs)
Mit dc und split können wir nun QuickSort einfach definieren als
18
19
20
21
22
23
24
quickSort :: Ord a => [a] -> [a]
quickSort = dc trivial divide id concat
where
trivial xs
= null xs || null (tail xs)
divide []
= error "divide: empty list"
divide (x:xs) = [smaller, [x], notSmaller]
where (smaller, notSmaller) = split (< x) xs
Den Trivialitätstest hätte man zwar auch als trivial = (< 2) . length definieren können, dies wäre aber
insbesondere für große Listen sehr ineffizient!
Ein Element im Suchbaum können wir ebenfalls mit Hilfe von dc suchen:
data SearchTree = Empty | Branch SearchTree Int SearchTree
deriving (Eq, Show)
elemTree :: Int -> SearchTree -> Bool
elemTree e t = dc trivial divide solve or t
where
trivial Empty
= True
trivial (Branch lt _ rt) = lt == Empty && rt == Empty
solve Empty
= False
solve (Branch
_ v _ ) = e == v
divide Empty
= error "divide: empty tree"
divide (Branch lt v rt)
| e < v
= [lt]
| e > v
= [rt]
| otherwise = [Branch Empty v Empty]
4 - Lambdas in Java
1
2
3
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
4
5
public class Function {
6
7
8
9
interface Fun<T, U> {
public U apply(T t);
}
3
10
11
12
13
interface Fun2<T, U, V> {
public V apply(T t, U u);
}
14
15
16
17
18
19
20
21
public static <T, U> List<U> map(Fun<T, U> f, List<T> list) {
List<U> res = new ArrayList<>(list.size());
for (T t : list) {
res.add(f.apply(t));
}
return res;
}
22
23
24
25
26
27
28
29
public static <T, U, V> Fun<T, V> compose(Fun<U, V> f, Fun<T, U> g) {
return new Fun<T, V>() {
public V apply(T t) {
return f.apply(g.apply(t));
}
};
}
30
31
32
33
34
35
36
37
public static <A, B> B foldr(Fun2<A, B, B> f, B e, List<A> xs) {
if (xs.isEmpty()) {
return e;
} else {
return f.apply(xs.get(0), foldr(f, e, xs.subList(1, xs.size())));
}
}
38
39
40
41
42
43
44
45
public static <A, B> B foldl(Fun2<A, B, B> f, B e, List<A> xs) {
B res = e;
for (A a : xs) {
res = f.apply(a, res);
}
return res;
}
46
47
48
49
public static void main(String[] args) {
List<Integer> a = new ArrayList<>(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
50
Fun<Integer, Integer> square = x -> x * x;
List<Integer> b = map(compose(square, square), a);
System.out.println(b);
51
52
53
54
Integer sum = foldr((x, y) -> x + y, 0, a);
System.out.println(sum);
55
56
57
sum = foldl((x, y) -> x + y, 0, a);
System.out.println(sum);
58
59
60
61
62
63
64
65
}
List<Fun<Integer, Integer>> incs = map(x -> y -> x + y, a);
for (Fun<Integer, Integer> inc : incs) {
System.out.println(inc.apply(0));
}
66
4
67
}
5 - Kombinatorfunktionen in Haskell
Ausdrücke ohne Lambda
• Der Ausdruck \x y -> f (g x) y entspricht f . g, denn
\x y -> f (g x) y
= \x
-> f (g x)
= \x
-> (f . g) x
= f . g
• Der Ausdruck \x y -> f (g (h x y)) entspricht ((f . g) .) . h, denn
=
=
=
=
=
=
=
=
=
\x y -> f (g (h x y))
\x y -> f (g ((h x) y))
\x y -> f ((g . h x) y)
\x y -> (f . (g . h x)) y
\x
-> f . (g . h x)
\x
-> f . (((.) g . h) x)
\x
-> ((.) f . ((.) g . h)) x
(.) f . ((.) g . h)
(f.) . (g.) . h
((f . g) .) . h
• Der Ausdruck \f g x -> g (f x) entspricht flip (.), denn
=
=
=
=
=
=
\f g x -> g (f x)
\f g x -> (g . f) x
\f g
-> (g . f)
\f g
-> (.) g f
\f g
-> flip (.) f g
\f
-> flip (.) f
flip (.)
Ausdrücke mit Lambda
• Der Ausdruck flip id entspricht \x f -> f x, denn
=
=
=
=
=
flip
\x
\x f
\x f
\x f
id
->
->
->
->
flip id x
flip id x f
id f x
f x
• Der Ausdruck (.) . (.) entspricht \f g x y -> f (g x y), denn
=
=
=
=
=
=
=
=
=
(.) . (.)
\f
->
\f
->
\f g
->
\f g
->
\f g x
->
\f g x
->
\f g x y ->
\f g x y ->
((.) . (.)) f
(.) (f.)
(.) (f.) g
(f.) . g
((f.) . g) x
(f.) (g x)
(f.) (g x) y
f (g x y)
zipWith
5
1
2
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f xs ys = map (uncurry f) (zip xs ys)
6
Herunterladen