Strukturelle Rekursion - Universität Bielefeld

Werbung
Programmieren
in Haskell
Stefan
Janssen
Programmieren in Haskell
Strukturelle Rekursion
Stefan Janssen
Universität Bielefeld
AG Praktische Informatik
10. Dezember 2014
Strukturelle
Rekursion
Wiederholung: Strukturelle Rekursion
Programmieren
in Haskell
Stefan
Janssen
Schema:
Strukturelle
Rekursion
f
:: [σ] -> τ
f []
=
e1
f (a : as)
=
e2
where s
=
f as
wobei e1 und e2 Ausdrücke vom Typ τ sind und e2 die
Variablen a, as und s (nicht aber f ) enthalten darf.
Strukturelle Rekursion
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Viele Algorithmen auf Listen folgen dem Schema der
strukturellen Rekursion
fold- Funktionen unterstützen dies als
Higher-Order-Functions
Strukturelle Rekursion
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Viele Algorithmen auf Listen folgen dem Schema der
strukturellen Rekursion
fold- Funktionen unterstützen dies als
Higher-Order-Functions
Was ist z.B. mit insert?
Beipiel: insert
Programmieren
in Haskell
1
2
3
4
insert hat 2 Argumente – formell keine strukturelle Rekursion
Aber: insert a folgt dem Schema
> insert x [] = [ x ]
> insert x ( y : ys )
>
| x <= y = x : y : ys
>
| x > y = y :( insert x ys )
Explizit:
Stefan
Janssen
Strukturelle
Rekursion
Beipiel: insert
Programmieren
in Haskell
1
2
3
4
5
6
7
8
insert hat 2 Argumente – formell keine strukturelle Rekursion
Aber: insert a folgt dem Schema
> insert x [] = [ x ]
> insert x ( y : ys )
>
| x <= y = x : y : ys
>
| x > y = y :( insert x ys )
Explizit:
> ins []
= \ a -> [ a ]
> ins (a ’: as ) = \ a -> if a <= a ’
>
then a :a ’: as
>
else a ’:( ins as ) a
Stefan
Janssen
Strukturelle
Rekursion
Beipiel: insert
Programmieren
in Haskell
1
2
3
4
5
6
7
8
insert hat 2 Argumente – formell keine strukturelle Rekursion
Aber: insert a folgt dem Schema
> insert x [] = [ x ]
> insert x ( y : ys )
>
| x <= y = x : y : ys
>
| x > y = y :( insert x ys )
Explizit:
> ins []
= \ a -> [ a ]
> ins (a ’: as ) = \ a -> if a <= a ’
>
then a :a ’: as
>
else a ’:( ins as ) a
⇒ Unnatürlich?
Stefan
Janssen
Strukturelle
Rekursion
Erweitertes Rekursionsschema
Programmieren
in Haskell
Stefan
Janssen
g ::
σ1 → [σ2 ] → τ
g i [] =
e1
g i (a : as) =
e2
where s =
g e3 as
Ausdruck
Variablen
e1
e2
e3
enthält i
kann i, a, as und s enthalten
kann i, a und as enthalten
Strukturelle
Rekursion
Strukturelle Rekursion auf Bäumen
weitere Datenstruktur: Bäume
Modellierung in Haskell
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Strukturelle Rekursion auf Bäumen
weitere Datenstruktur: Bäume
Modellierung in Haskell
→ als algebraischer Datentyp
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Strukturelle Rekursion auf Bäumen
Programmieren
in Haskell
weitere Datenstruktur: Bäume
Stefan
Janssen
Modellierung in Haskell
→ als algebraischer Datentyp
Strukturelle
Rekursion
Example
·
·
·
1 2
·
3 4
5
Eine von vielen Baum-Varianten
Programmieren
in Haskell
Welche Eigenschaften sollen unsere Bäume haben?
Stefan
Janssen
an den Blättern stehen Daten
jeder innere Knoten hat 2 Kinder
Strukturelle
Rekursion
Eine von vielen Baum-Varianten
Programmieren
in Haskell
Welche Eigenschaften sollen unsere Bäume haben?
Stefan
Janssen
an den Blättern stehen Daten
jeder innere Knoten hat 2 Kinder
Darstellung:
Bäume werden in der Informatik von oben nach unten
gezeichnet
Strukturelle
Rekursion
Eine von vielen Baum-Varianten
Programmieren
in Haskell
Welche Eigenschaften sollen unsere Bäume haben?
Stefan
Janssen
an den Blättern stehen Daten
jeder innere Knoten hat 2 Kinder
Darstellung:
Bäume werden in der Informatik von oben nach unten
gezeichnet
Begriffe:
Wurzel
Knoten (Blatt, innerer Knoten)
Kante
Tiefe
Strukturelle
Rekursion
Tree Datentyp
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Tree-Datenyp
1
2
3
4
data Tree a = Leaf a
|
Br
( Tree a ) ( Tree a ) |
Nil
deriving Show
Beispiel
Programmieren
in Haskell
Stefan
Janssen
Br
Br
Leaf
Leaf Leaf
5
Leaf Leaf
1
Strukturelle
Rekursion
Br
Br
2
3
4
Beispiel
Programmieren
in Haskell
Stefan
Janssen
Br
Br
Leaf
Leaf Leaf
5
Leaf Leaf
1
2
3
1
2
Br
Strukturelle
Rekursion
Br
Br
4
( Br ( Leaf 1) ( Leaf 2) )
( Br ( Br ( Leaf 3) ( Leaf 4)) ( Leaf 5))
Strukturelle Rekursion auf Bäumen
Programmieren
in Haskell
Schema der strukturellen Rekursion:
f
:: Tree σ -> τ
f Nil
=
e1
f (Leaf a)
=
e2
f (Br l r)
=
e3
where sl
=
f l
sr
=
f r
e3 darf dabei l, r, sl und sr enthalten, nicht aber f .
Stefan
Janssen
Strukturelle
Rekursion
Strukturelle Rekursion auf Bäumen
1
2
3
4
data Tree a = Leaf a
|
Br
( Tree a ) ( Tree a ) |
Nil
deriving Show
Rekursionsbasis (Nil) Das Problem wird für den leeren
Baum gelöst.
Rekursionsbasis (Leaf a) Das Problem wird für das Blatt
Leaf a gelöst.
Rekursionsschritt (Br l r) Um das Problem für den Baum
Br l r zu lösen, werden rekursiv Lösungen für l
und r bestimmt, die zu einer Lösung für Br l r
erweitert werden.
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Beispiel: Berechnung der Baumgröße
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Beispiel: Berechnung der Baumgröße
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
1
2
3
4
size
size
size
size
:: Tree a -> Integer
Nil
= 1
( Leaf _ ) = 1
( Br l r ) = size l + size r
Beispiel: Berechnung der Baumtiefe
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Beispiel: Berechnung der Baumtiefe
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
1
2
3
4
depth
depth
depth
depth
:: Tree a -> Integer
Nil
= 0
( Leaf _ ) = 0
( Br l r ) = max ( depth l ) ( depth r ) + 1
Beispiel: Blätter aufzählen
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Beispiel: Blätter aufzählen
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
1
2
3
4
>
>
>
>
leaves
leaves
leaves
leaves
:: Tree a -> [ a ]
Nil
= []
( Leaf a ) = [ a ]
( Br l r ) = leaves l ++ leaves r
Beispiel: Blätter aufzählen
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
1
2
3
4
>
>
>
>
leaves
leaves
leaves
leaves
:: Tree a -> [ a ]
Nil
= []
( Leaf a ) = [ a ]
( Br l r ) = leaves l ++ leaves r
⇒ Geht es nicht besser?
Verstärkung der Rekursion (Einbettung)
Programmieren
in Haskell
Exkurs zu leaves
Verstärkung der Rekursion
wie bei der rekursiven Listenfunktion reverse
Stefan
Janssen
Strukturelle
Rekursion
Verstärkung der Rekursion (Einbettung)
Programmieren
in Haskell
Exkurs zu leaves
Verstärkung der Rekursion
wie bei der rekursiven Listenfunktion reverse
1
2
3
4
5
6
7
fleaves
:: Tree a -> [ a ]
fleaves t = f t []
where
f
:: Tree a -> [ a ] -> [ a ]
f Nil
y = y
f ( Leaf a ) y = a : y
f ( Br l r ) y = f l ( f r y )
Und warum ist das nun schneller?
Stefan
Janssen
Strukturelle
Rekursion
Apropos reverse
Programmieren
in Haskell
Stefan
Janssen
1
2
Die langsame Version:
> slowreverse [] = []
> slowreverse ( x : xs ) = slowreverse xs ++ [ x ]
Strukturelle
Rekursion
Apropos reverse
Programmieren
in Haskell
Stefan
Janssen
1
2
1
2
3
Die langsame Version:
> slowreverse [] = []
> slowreverse ( x : xs ) = slowreverse xs ++ [ x ]
Schneller durch Einbettung
> fastreverse xs = f xs [] where
>
f [] ys = ys
>
f ( x : xs ) ys = f xs ( x : ys )
Strukturelle
Rekursion
fold auf Bäumen
Programmieren
1
2
3
4
5
in Haskell
Die Funktionen size, depth, leaves folgen alle dem
Stefan
gleichen Schema.
Janssen
> foldTree :: b - >(a - > b ) - >(b - >b - > b ) - > Tree a -> b
Strukturelle
> foldTree nil leaf br = f where
Rekursion
>
f Nil = nil
>
f ( Leaf a ) = leaf a
>
f ( Br l r ) = br ( f l ) ( f r )
fold auf Bäumen
Programmieren
1
2
3
4
5
1
2
3
in Haskell
Die Funktionen size, depth, leaves folgen alle dem
Stefan
gleichen Schema.
Janssen
> foldTree :: b - >(a - > b ) - >(b - >b - > b ) - > Tree a -> b
Strukturelle
> foldTree nil leaf br = f where
Rekursion
>
f Nil = nil
>
f ( Leaf a ) = leaf a
>
f ( Br l r ) = br ( f l ) ( f r )
Damit erhalten wir
> size ’ = foldTree 1 (\ x - >1)
(+)
> depth ’ = foldTree 0 (\ x - >0) (\ x y - > max x y +1)
> leaves ’= foldTree [] (\ x - >[ x ]) (++)
Das findet man einfach durch Anwendung der
Vogelperspektive
fold im Allgemeinen
Verallgemeinerung:
für jeden algebraischen Datentyp T können wir eine
foldT-Funktion definieren
für jeden Konstruktor hat sie eine passende Funktion als
Argument, sowie ein t ∈ T
in der Vogelperspektive werden die Konstruktoren durch
die passenden Funktionen ersetzt (und anschließend die
entstandene Formel ausgerechnet).
Je komplizierter der Datentyp, deso mehr gewinnt man durch
die Verwendung der fold-Operation
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
fold im Allgemeinen
Verallgemeinerung:
für jeden algebraischen Datentyp T können wir eine
foldT-Funktion definieren
für jeden Konstruktor hat sie eine passende Funktion als
Argument, sowie ein t ∈ T
in der Vogelperspektive werden die Konstruktoren durch
die passenden Funktionen ersetzt (und anschließend die
entstandene Formel ausgerechnet).
Je komplizierter der Datentyp, deso mehr gewinnt man durch
die Verwendung der fold-Operation
1
PS: Und was tut die folgende Funktion f?
> f = foldTree Nil Leaf Br
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Das allgemeine Rekursionsschema
Programmieren
in Haskell
Stefan
Janssen
data T a1 . . . am = C1 t11 . . . t1n1
| ...
| Cr tr 1 . . . trnr
Wir unterscheiden zwei Arten von Argumenten: rekursive (d.h.
tij ist gleich T a1 . . . am ) und nicht-rekursive.
Seien li1 ,. . . , lipi mit 1 ≤ li1 < li2 < · · · < lipi ≤ ni die
Positionen, an denen der Konstruktor Ci rekursiv ist
Strukturelle
Rekursion
Das allgemeine Schema der strukturellen Rekursion
f
:: T σ1 . . . σm -> τ
f (C1 x11 . . . x1n1 )
=
e1
where s11
=
f x1l11
=
f x1l1p1
f (Cr xr 1 . . . xrnr )
=
er
where sr 1
=
f xrlr 1
=
f xrlrpr
...
s1p1
...
...
srpr
Der Ausdruck ei darf die Variablen xi1 , . . . , xini und die
Variablen si1 , . . . , sipi enthalten. Ist pi = 0, so spricht man von
einer Rekursionsbasis, sonst von einem Rekursionsschritt.
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Fazit
Programmieren
in Haskell
Stefan
Janssen
Strukturelle
Rekursion
Strukturelle Rekursion für jeden algebraischen Datentyp
direkte Orientierung an den Konstruktoren
lassen sich alle Probleme mit struktureller Rekursion lösen?
strukturelle versus wohlfundierte Rekursion
Herunterladen