De volgende oefeningen zijn bedoeld als herhalingsopdrachten, om jullie begrip van de stof te consolideren. Er zijn veel opdrachten, maar het is allemaal herhaling van dingen die jullie al een keer gezien hebben. Door de oefeningen te maken kun je routine opbouwen in het programmeren met Haskell.
Lever precies één bestand in genaamd Week5.hs; dit bestand moet door Hugs geaccepteerd worden.
In het Haskellbestand kun je uitleg bij een functie schrijven door het tussen {- en -} te zetten. Hugs negeert het dan in plaats van het als Haskellcode te zien. Schrijf alleen uitleg bij een functie als je dat nodig vindt: als de functie je direct duidelijk was en je zonder al te veel moeite de functie kon implementeren hoef je er geen uitleg over te schrijven. Als er bij de opdracht een vraag staat, beantwoord die dan ook. Zet al je uitleg direct voor de Haskellcode voor de functie. Bijvoorbeeld:
{-
De volgende functie rekent de som uit van een lijst van getallen.
-}
som :: [Int] -> Int
som [] = 0
som (x:xs) = x + som xs
Veel van de functies hieronder zijn al standaard aanwezig in Haskell. Als dat het geval is staat de naam van de standaardfunctie erbij vermeld. Je kunt natuurlijk van deze standaardfuncties de types opvragen met :t in Hugs, maar als de opdracht vraagt om zelf een type bedenken, probeer dan niet te spieken. We gebruiken expres andere namen (vaak de vertaalde namen) omdat Hugs anders in de war raakt.
In een lijst moet elk element hetzelfde type hebben. Stel het type van een afzonderlijk element is Int, dan is het type van de hele lijst [Int].
In Haskell zijn lijsten gedefinieerd als zijnde óf de lege lijst [] óf een niet-lege lijst x:xs met kop x en staart xs. Je kunt hiervan gebruik maken bij de volgende functies door patroonherkenning toe te passen:
functie :: ... functie [] = ... functie (x:xs) = ...
leeg die kijkt of een lijst leeg is. Bedenk eerst het type van deze functie.kop :: [a] -> a die de kop van een lijst oplevert. Wat gebeurt er als je er de lege lijst instopt?staart :: [a] -> [a] die de staart van een lijst oplevert. Wat gebeurt er als je er de lege lijst instopt?splits1 en splits2 :: [a] -> (a, [a]) die een lijst opdelen in kop en staart
kop en staart te gebruiken.leeg heet standaard null. kop en staart heten respectievelijk head en tail.
Booleans zijn waarheidswaardes. Er zijn precies twee waardes van type Bool: True en False.
niet :: Bool -> Bool die een boolean omdraait.en :: Bool -> Bool -> Bool die alleen True oplevert als de twee invoerbooleans True zijn; in alle andere gevallen levert de functie False op.of2 :: Bool -> Bool -> Bool die alleen False oplevert als de twee invoerbooleans False zijn; in alle andere gevallen levert de functie True op.of2 in plaats van of, omdat dit laatste als functienaam verboden is in Haskell.)
In Haskell zijn standaard de functie not en operatoren && en || aanwezig. Een functie aanroepen doe je altijd door de naam van de functie vooraan te zetten en dan de argumenten op te schrijven; een operator gebruiken doe je altijd door de operator tussen de twee argumenten in te zetten.
Tupels zijn combinaties van waardes. Het fijne aan tupels is dat de linkerkant en de rechterkant niet van hetzelfde type hoeven te zijn. Ook bij tupels kun je patroonherkenning toepassen aan de linkerkant van de =. Je schrijft dan niet (x:xs) zoals bij lijsten, maar bijvoorbeeld (x, y). Daarmee geef je de linkerkant van het tupel de naam x en de rechterkant de naam y. Die twee namen mag je dan weer aan de rechterkant van de = gebruiken.
eerste :: (a, b) -> a die het eerste element van een tupel oplevert.tweede :: (a, b) -> b die het tweede element van een tupel oplevert.wiebel :: (a, (b, c)) -> ((a, b), c) die de groepering van de waardes omdraait.wobbel :: ((a, b), c) -> (a, (b, c)) die de groepering de andere kant op omdraait.eerste en tweede heten respectievelijk fst en snd.
Een pattern guard gebruik je als je gevalsonderscheid wilt maken op basis van booleans. Je schrijft dan per te onderscheiden geval eerst een vertikale streep |, dan de conditie (een expressie van type Bool) en dan een = met de definitie erachter. In de condities mag je dan de namen gebruiken die je voor de argumenten van de functie hebt verzonnen. De guards kun je bij de volgende functies handig gebruiken.
rem gezien: die vertelt wat de rest is bij een deling. Bijvoorbeeld: 10 gedeeld door 3 is gelijk aan 3 rest 1. rem 10 3 levert daarom 1 op. Schrijf nu met behulp van deze functie een functie evenGetal :: Int -> Bool die aangeeft of de invoer even is.oneven1 en oneven2 :: Int -> Bool
evenGetal.kleinste :: Int -> Int -> Int die van de twee invoergetallen de kleinste oplevert. Je kunt hiervoor de operatoren < en > gebruiken.grootste :: Int -> Int -> Int.In Haskell zijn standaard de functies even, odd, min en max aanwezig. Al deze functies hebben daar algemenere types, maar je kunt ze nog steeds voor Ints gebruiken.
Een recursieve functie roept zichzelf aan in z'n eigen definitie. Zo kun je bijvoorbeeld een hele lijst aflopen.
mnmInt gezien; die gaan we nu reconstrueren. Definieer een functie allerkleinste :: [Int] -> Int die van een hele lijst getallen de kleinste opzoekt. Gebruik in je definitie je functie kleinste die je eerder hebt geschreven. Wat moet er gebeuren bij de lege lijst? En bij een lijst met precies 1 element?allergrootste :: [Int] -> Int.allemaal :: [Bool] -> Bool die alleen True oplevert als elk element in de lijst True is. Gebruik hierbij de functie en die je eerder hebt geschreven.sommigen :: [Bool] -> Bool die alleen False oplevert als elk element in de lijst False is. Gebruik hierbij de functie of die je eerder hebt geschreven.Deze functies heten standaard minimum, maximum, and en or.
laatste die het laatste element van een lijst oplevert. Wat is het type van deze functie?behalveLaatste :: [a] -> [a] die het laatste element van de invoerlijst afhakt.splitsLaatste :: [a] -> ([a], a) die een lijst opsplitst in het laatste element (aan de rechterkant) en alles behalve het laatste element (aan de linkerkant). Maak gebruik van je functies laatste en behalveLaatste.laatste en behalveLaatste zijn we al eerder in de opgaves tegengekomen en heten last en init.
produkt :: [Int] -> Int die van een lijst getallen het produkt uitrekent. Bedenk goed wat er uit moet komen als je er de lege lijst instopt.faculteit :: Int -> Int die van het invoergetal de faculteit uitrekent. Maak gebruik van je produktfunctie. De faculteit van een getal krijg je door de getallen 1 t/m dat getal met elkaar te vermenigvuldigen. Twee voorbeelden:
In Haskell heet de produktfunctie product (nu weten jullie waarom het hier met een k gespeld is...). faculteit is niet standaard aanwezig.
++ gebruikt. We gaan hem nu zelf definiëren: maak een functie genaamd plusplus die twee lijsten aan elkaar plakt. Bedenk eerst wat het type van ++ (en dus plusplus) ook alweer is. De functie heeft als invoer twee lijsten. Is het nodig om op beide lijsten in recursie te gaan? Of is op één lijst in recursie gaan voldoende? Welke moet je dan kiezen? Let op! Je mag in je definitie natuurlijk niet gebruik maken van ++ zelf. Je mag natuurlijk wel plusplus gebruiken in de recursieve aanroep.plak :: [[a]] -> [a] die een lijst van lijsten aan elkaar plakt. Gebruik hierbij je functie plusplus.Haskell biedt hiervoor de operator ++ en de functie concat.
Lijstcomprehensies heb je in de vorige praktica gezien en zijn ook een paar keer in de hoorcolleges voorgekomen. Als je niet meer goed weet hoe je ze opschrijft kun je nog eens naar de opgaven van de eerste week of naar de slides van de hoorcolleges kijken voor enkele voorbeelden.
Een lijstcomprehensie bevat altijd in ieder geval de bouwblokken [, |, <- en ]. Je kunt altijd beginnen met deze bouwblokken opschrijven en dan daarna de gaten invullen.
map. Deze opgave demonstreert dat. Definieer drie functies veelNiet1, veelNiet2 en veelNiet3 :: [Bool] -> [Bool] die een hele lijst booleans omdraaien. Gebruik hierbij telkens de functie niet.
map.<- aan de rechterkant van de |. Schrijf een functie vierkant10 :: [(Int, Int)] die alle combinaties van getallen uit de reeks [1..10] combineren in een tupel. Je krijgt dus in totaal 100 combinaties. Voorbeelden van combinaties die in je resultaat voor moeten komen zijn (4,4), (6,7), (9,2) en (10,10).filter. Schrijf drie functies alleenEven1, alleenEven2 en alleenEven3 :: [Int] -> [Int] die van de invoer alleen de even getallen overlaat. Gebruik hierbij de telkens functie even.
filter.onevenVierkant1 en onevenVierkant2 :: [(Int, Int)] die hetzelfde doet als vierkant10 maar alleen die combinaties oplevert waarvan de som oneven is. (4,5) mag dus wel voorkomen maar (7,9) niet. Gebruik telkens de functie oneven.
vierkant10.vierkant10.Met deze opgaven kun je extra punten verdienen.
aantalKeer :: Int -> a -> [a]. Wat deze functie doet is het makkelijkst te zien aan de hand van enkele voorbeelden:
aantalKeer 5 True = [True, True, True, True, True]aantalKeer 0 "noot" = []aantalKeer 3 'a' = "aaa"faculteit2 :: Int -> Int die net zoals de vorige keer de faculteit van een getal uitrekent, maar maak deze keer gebruik van recursie in plaats van de produktfunctie.aantalKeer heet standaard replicate.
In Haskell kun je ook oneindige lijsten opbouwen. Natuurlijk kun je dan nooit de hele lijst aflopen, maar je kunt altijd een begin maken.
oneindigVaak :: a -> [a] die van een element een oneindig grote lijst maakt. Bijvoorbeeld:
oneindigVaak False = [False, False, False, False, False, False, ...
cykel :: [a] -> [a] die van een lijst een oneindig grote lijst maakt door hem telkens te herhalen. Bijvoorbeeld:
cykel [1,2,3] = [1,2,3,1,2,3,1,2,3,1...
Deze functies heten standaard repeat en cycle.
Deadline maandag 1 juni, 12 uur 's middags. Inleveren op de gebruikelijke manier.