Listen in Haskell - Listenoperationen höherer Ordnung


[ Gliederung ] [ Einführung ] [ Listenoperationen I ] [ Vollständige Induktion ] [ Listenoperationen II ] [ Literatur ]

Gliederung: Listenoperationen höherer Ordnung


Anwenden einer Funktion auf alle Elemente einer Liste / map

map           :: (a->b)->[a]->[b]
map f []      =  []
map (x:xs)    =  f x : map f xs
Anwendung:
?map ord “Hallo“
[72,97,108,108,111]
Eigenschaften:
map id = id
map f . map g = map (f.g)(*)
(*) Man kann diese Eigenschaft nutzen, um Traversierungen zu sparen. Zumindestens in der Theorie können solche Optimierungen auch von Compilern ausgeführt werden.


Aufsammeln aller Elemente die einem Prädikat p genügen / filter

filter           :: (a->Bool)->[a]->[a]
filter p []      =  []
filter p (x:xs)  =  if p x then x : filter p xs else filter p xs
Anwendung:
?filter isUpper “AbKUErzungsFImmel“
“AKUEFI“
Eigenschaften:
filter p . filter q = filter (p and q) (*)
filter p . concat = concat . map(filter p)
(*) Auch hier können wieder Traversierungen gespart werden!


"Mathematische Mengenkonstruktion"(*) / List comprehensions

[expr(x) | x<-xs, constraint(x)] = ...
[expr(x,y) | x<-xs, y<-s, constraints(x,y)] = ... (**)
usw.usf.
Anwendung:
?[x*x|x<-[1..10],odd x]
[1,9,25,49,81]
?[x*y|x<-[1..10],y<-[1,2],odd x]
[1,2,3,6,5,10,7,14,9,18] (***)
(*) Der Begriff "Mathematische Mengenkonstruktion" ist natürlich Humbug. Ich habe diese Bezeichnung nur deshalb benutzt, weil man in Haskell zur Konstruktion von Listen eine Schreibweise nutzen kann, die der Schreibweise, mit der in der Mathematik üblicherweise Mengen definiert werden, sehr ähnlich ist.
Dieses Konstrukt versagt natürlich spätestens dann, wenn unabzählbare Mengen beschrieben werden sollen.

(**) Die Reihenfolge der Erzeuger (x<-xs) und Constraints, sowie deren Anzahl ist beliebig. Die hier verwendete Schreibweise ist jedoch idiomatisch.
(***) Der zuletzt deklarierte Erzeuger bildet offensichtlich hier die "innere Schleife":
x*y: {1*1, 1*2, 3*1, 3*2, 5*1, 5*2, 7*1, 7*2, 9*1, 9*2}



Die Fold-Funktionen (fold-right) / foldr

foldr            :: (a->b->b)->b->[a]->b
foldr f e []     =  e
foldr f e (x:xs) =  f x (foldr f e xs)
Anwendung:
?foldr (+) 0 [1,2,3] (*)
6
Eigenschaften:
foldr ($) e [x1,x2,..,xn] = x1 $ (x2 $ (..(xn $ e)..)) (**)
(*) Üblicherweise wird als Startelement e das neutrale Element von f übergeben. Auf diese Weise akkumuliert sich das Ergebnis über die gesamte Liste beginnend mit dem letzten Listenelement welches von rechts mit dem "neutralen Element" verknüpft wird.
(**) ($) sei irgenteine zweistellige Verknüpfung. Da wir keine Annahme darüber machen, ob es sich um eine rechts-/oder links-assoziative Verknüpfung handelt sind die Klammern notwendig.


Die Fold-Funktionen (fold-left) / foldl

foldl            :: (b->a->b)->b->[a]->b
foldl f e []     =  e
foldl f e (x:xs) =  foldl f (f e x) xs 
Anwendung:
?foldl f [] [1,2,3] where f xs x = x:xs (*)
[3,2,1]
Eigenschaften:
foldl ($) e [x1,x2,..,xn] = (..((e $ x1) $ x2)..) $ xn
(*) Here we go! reverse in einer Zeile auf der Command line reingehackert, und zudem ist es noch effizient. Lineare Laufzeitkomplexität im Vergleich zu Quadratischer bei der Implementation im vor-vorhergehdem Abschnitt.


Die Fold-Funktionen (fold-left-nonempty) / foldl1

foldl1          :: (a->a->a)->[a]->a
foldl1 f (x:xs) =  foldl f x xs
Anwendung:
?foldl1 max [3,(-2),5,1] (*)
5
Eigenschaften:
- foldl1 funktioniert nur auf nicht leeren Listen.
- foldr1 existiert äquivalent.
(*) max ist eine typische Anwendung für foldl1, da es für max (auf ganzen Zahlen) kein neutrales Element gibt, ist es nicht möglich max irgentwie sinnvoll auf die leere Liste anzuwenden. Hat man mindestens ein Element zur Verfügung kann man sich dieses allerdings schnappen und als "neutrales Element" in die foldl Funktion reinstecken.


Die Scan-Funktionen (scan-left) / scanl

scanl           :: (b->a->b)->b->[a]->[b]
scanl f e []    =  [e]
scanl f e (x:xs)=  e : scanl f (f e x) xs (*)
Anwendung:
?scanl (*) 1 [1..5]
[1,1,2,6,24,120]
Eigenschaften:
Es gibt scanr,scanl1,scanr1 mit den äquivalenten Eigenschaften der Fold-Familie.
(*) Anders als bei foldl wird bei scanl das akkumulierte Element bei jedem Zwischenschritt in der Ergebnisliste eingehängt. Die Ergebnisliste hat immer ein Element mehr als die übergebende Liste. Dieses Element ist das Element e welches bei foldl an die erste Stelle der Ergebnisliste gelangt, und bei foldr an die Letzte.


[ Gliederung ] [ Einführung ] [ Listenoperationen I ] [ Vollständige Induktion ] [ Listenoperationen II ] [ Literatur ]