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