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 Int
s 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.