Listen in Haskell - Einfache Listenoperationen
 [ Gliederung ] 
 [ Einführung ] 
 [ Listenoperationen I ] 
 [ Vollständige Induktion ] 
 [ Listenoperationen II ] 
 [ Literatur ] 
Gliederung: Einfache Listenoperationen
Konkatenation I / (++)
(++)       :: [a]->[a]->[a]
[]++ys     =  ys
(x:xy)++ys =  x:(xs++ys)
Anwendung:
?“Hello“++“World“
“HelloWorld“
Eigenschaften:
Assoziative Operation mit neutralem Element []. (*)
(*) Also z.B.: [1,2]++[]=[1,2].
Konkatenation II / concat
concat          :: [[a]]->[a]
concat[]        =  []
concat(xs:xss)  =  xs ++ concat xss 
Anwendung:
?concat [“Hello“,“ “,“World“]
“Hello World“
Eigenschaften:
concat(xss++yss) = concat xss++concat yss
Umdrehen / reverse
reverse        :: [a]->[a]
reverse[]      =  []
reverse(x:xs)  =  reverse xs ++ [x]  (*)
Anwendung:
?reverse [1,2,3]
[3,2,1]
Eigenschaften:
reverse (reverse xs) = xs [für alle endlichen Listen xs] (**)
(*) Diese Implementation hat ein Problem: Da für eine Liste der Länge n n mal (++) 
 ausgewertet wird, und (++) selbst eine lineare Laufzeitkomlexität hat, ist O(n)=n^2. Wir werden
 noch eine effizientere (lineare Laufzeitkomplexität) Implementation kennen lernen.
(**) Diesen Satz werden wir beispielhaft für die Implementationen von reverse und (++), die wir
hier kennengelernt haben, beweisen.
Länge ermitteln / length
length       :: [a]->Int
length[]     =  0
length(x:xs) =  1 + length xs  
Anwendung:
?length “Text“
4
Eigenschaften:
- length(xs++ys) = length xs + length ys
- lineare Laufzeitkomplexität
Index-Zugriff / (!!)
(!!)          :: [a]->Int->a
(x:xs)!!0     =  x
(x:xs)!!(n+1) =  xs!!n  
Anwendung:
?“Text“!!0
‘T‘
Eigenschaften: (*)
- Indexierung beginnt mit 0
- lineare Laufzeit
(*) Programmierer, die in imperativen Sprachen viel mit Arrays gearbeitet haben, könnten versucht sein, 
Listen in Haskell mit "until", "length" und (!!) wie Arrays zu verarbeiten. In funktionalen Sprachen ist dieses
Vorgehen allerdings weder idiomatisch, noch geschickt, da length und (!!) eine lineare Laufzeitkomplexität haben
(nicht konstant wie bei Arrays in imperativen Sprachen). Meistens finden sich elegantere Lösungen, die ohne
length und (!!) auskommen.
Die ersten i Elemente einer Liste nehmen / take
take             :: Int -> [a] -> [a]
take 0 xs        =  []
take (n+1)[]     =  []
take (n+1)(x:xs) =  x:take n xs 
Anwendung:
?take 5 “Wurstbrot“
“Wurst“
Eigenschaften:
Wenn i>=length xs dann: take i xs = xs 
Die ersten i Elemente einer Liste entfernen / drop
drop             :: Int -> [a] -> [a]
drop 0 xs        =  xs
drop (n+1)[]     =  []
drop (n+1)(x:xs) =  drop n xs 
Anwendung:
?drop 5 “Wurstbrot“
“brot“
Eigenschaften:
take n xs ++ drop n xs = xs (*)
(*) Für alle nicht-negativen n. Für sehr große n (> length xs) ist take n xs = xs und drop n xs=[].
Eine Liste an Position i zerteilen / splitAt
splitAt       :: Int->[a]->([a],[a])
splitAt n xs  =  (take n xs, drop n xs)      
Anwendung:
?SplitAt 5 “Wurstbrot“
(“Wurst“, “brot“)
Eigenschaften:
Tatsächliche Implementation effizienter als die hier Angegebene, kommt mit einer Traversierung aus (hier:2).
Listen aus Intervallen von Ordinaltypen erzeugen / [n..m]
[m..n]
[m..] (*) 
Anwendung:
?[‘A‘..‘Z‘]
“ABCDEFGHIJKLMNOPQRSTUVWXYZ“
?[‘Z‘..‘A‘]
““
?take 5 [1..] (**)
[1,2,3,4,5]
(*) Für Elementtypen mit begrenztem Wertebereich ist [m..] = [m..maxBound], also z.B. ['\0'..] = ['\0'..'\255']
 oder [False..] = [False, True].
(**) Hier sehen wir den Effekt der Lazy-Evaluation. Da take nur die ersten 5 Elemente der unendliche Liste [1..] 
 benötigt, werden auch nur diese berechnet. Der Ausdruck kann also (mindestens) so effizient ausgewertet werden, wie take 5 [1..5].
Reißverschluss / zip
zip              :: [a]->[b]->[(a,b)]
zip [] ys        =  []
zip (x:xs)[]     =  [] (*)
zip (x:xs)(y:ys) =  (x,y):zip(xs,ys)
Anwendung:
?zip [1..] “Text“
[(1,‘T‘),(2,‘e‘),(3,‘x‘),(4,‘t‘)]
Eigenschaften:
length(zip xs ys)=min(length xs)(length ys) (**)
(*) Es mag überaschend erscheinen, daß die Definition zip (x:xs) [] = [] anstelle von zip xs [] = []
 verwendet wurde. Diese Definition folgt aber dem Idiom, die linken Seiten der Definitionsgleichungen einer
 Funktion disjunkt zu gestalten. Hier wird also explizit klargestellt, daß der Fall zip [] [] in der ersten, und
 nicht in der zweiten Definitionsgleichung behandelt wird.
 
 Disjunkte Definitionsgleichungen erleichtern auch Beweise, weil man nicht aufpassen muss in die richtige
 Gleichung einzusetzen, wenn es nur eine Mäglichkeit gibt.
 
 In Haskell wird bei Mehrdeutigkeit immer die erste zutreffende Definition ausgewertet.
 
 Illustration: 
 
 last        :: [a]->a
     
 last (x:xs) =  last xs
     
 last [x]    =  x
 
 Ups! Hier wird das Rekursionsende niemals erreicht, da auch im Falle einer einelementigen Liste (x:[]) die
      erste Definition verwendet wird.
 
 Lösung:
 
 last            :: [a]->a
     
 last (x1:x2:xs) =  last (x2:xs)
     
 last [x]        =  x
(**) Ist eine der beiden Listen länger, werden die überzähligen Elemente dieser Liste nicht berücksichtigt.
Reißverschluss öffnen / unzip
unzip            :: [(a,b)]->([a],[b])
unzip            =  pair(map fst,map snd) (*)
Anwendung:
?unzip[(1,‘E‘),(2,‘i‘)]
([1,2],“Ei“)
Eigenschaften:
zipp (unzip xys)=xys
[zipp ist die uncurried-Version von zip]
(*) Diese Implementation greift voraus. Wir werden im Abschnitt "Listenoperationen II" 
  sehen, wie die Funktion map arbeitet, und wann man sie benutzen kann.
weitere Listenoperationen
head :: [a] -> a   -- xs!!0
tail :: [a] -> [a] -- ~“drop 1 xs“ (*)
last :: [a] -> a   -- letztes Element
init :: [a] -> [a] -- “pop_back“ (**)
(*) "tail xs" ist nur ungefähr gleich "drop 1 xs", da "tail [] = undefined" und "drop 1 [] = []".
(**) Ich finde den Begriff init zum Entfernen des letzten Elementes einer Liste gewöhnungsbedürftig. In
imperativen Sprachen heisst diese Operation für gewöhnlich pop_back.
 [ Gliederung ] 
 [ Einführung ] 
 [ Listenoperationen I ] 
 [ Vollständige Induktion ] 
 [ Listenoperationen II ] 
 [ Literatur ]