1.15 Exercises - Department of Computing

advertisement
Declarative
Programming I
Functional Programming in Haskell
Unassessed Exercises
Tony Field (Room 376)
Answer as many as you can at your own speed.
These exercises do NOT need to be submitted for
marking.
Model answers to each set will be handed out in due
course.
Functional Programming in Haskell
Unassessed Exercise Sheet 1: Expressions, Built-in Types and Prelude
Introduction
All the problems on this sheet can be solved by typing expressions at the Hugs prompt:
Prelude> type your expression here and press RETURN
First read the laboratory notes provided, which introduces you to the Department’s computers
and tells you how to start the Haskell (Hugs) system. As you do each question, read any
accompanying notes carefully. Also, most importantly, make up your own problems and try
them out too.
Note that an important objective of this first batch of problems is to introduce you to
some of Haskell's built-in types and functions (Haskell's so-called prelude). Within Hugs, you
can find out more about any known identifier, including types, functions, classes etc., by typing
:info identifier (or use :i for short), e.g. :info +, :i Int etc. To get a list of all
function names that are in scope at any time type :names (or :n).
You will probably not be able to complete these exercises in the two lab sessions
allocated before Thursday's lectures, but don't panic! There are more questions here than you
need, so skip over some of them once you've got the hang of it.
1.
Type in and evaluate the following Haskell expressions:
(a)
(b)
2 - 3 - 4
2 - ( 3 - 4 )
Q: What does this tell you about the associativity of the Haskell ‘-’ operator?
Remark: a left-associative operator op implies that successive applications are bracketed from
the left, so that a op b op c is evaluated as ( ( a op b ) op c ). For a rightassociative operator, it would be evaluated as ( a op ( b op c ) ).
(c)
(d)
(e)
100 `div` 4 `div` 5
100 `div` ( 4 `div` 5 )
2 ^ 3 ^ 2
Remark: div is the prefix integer division function (quotient function), as in div 12 5 which
gives the answer 2. The back quotes in `div` turns the prefix function into an infix operator, as
in 12 `div` 5 (try this out). Do not confuse back quotes with the forward quotes that are
used to delimit characters. Note also that an infix operator can be turned into a prefix function by
enclosing it in brackets. For example, the expression (+) 8 5 means the same as 8 + 5.
Q: What can you say about the associativity of the `div` and ^ operators?
(f)
(g)
(h)
3 - 5 * 4
2 ^ 2 * 3
2 * 2 ^ 3
Q: What can you say about the relative precedence of the -, * and ^ operators?
Remark: An operator’s precedence is an integer in the range 0-9 which is used to determine the
order in which multiple operator applications are evaluated. If op1 has a higher precedence (a
higher number) than op2 then a op1 b op2 c evaluates to ( ( a op1 b ) op2 c )
and a op2 b op1 c evaluates to ( a op2 ( b op1 c ) ). We say that op1 binds
more tightly than op2, as with * and + respectively, for example. Experiment with the other
Haskell operators.
(i)
(j)
(k)
(l)
(m)
(n)
( 3
ord
chr
chr
'p'
'h'
+ 4 - 5 ) == ( 3 - 5 + 4 )
'a'
( ord 'b' - 1 )
( ord 'X' + ( ord 'a' - ord 'A' ) )
/= 'p'
<= 'u'
Remark: The alphabetic characters ‘a’, ‘A’, ‘b’, ‘B’ etc. are encoded by computers as numbers.
The most commonly used numbering scheme is called the ASCII encoding. The ASCII code for
a character c can be obtained by the Haskell function ord (for ordinal value). The inverse of
ord is called chr. See the attached ASCII table and check it out by applying the ord function
to various characters. The operators ==, /=, <, >, <=, >= also work on characters using the ASCII
numeric representation in the obvious way, e.g. 'a' < 'b' gives True because
ord 'a' < ord 'b'.
(o)
(p)
sqrt 2 ^ 2
sqrt 2 ^ 2 – 2
(try also sqrt 2 ^ 2 == 2 )
(sqrt is a function that calculates the square root of a given number).
Q: What does this tell you about the relative precedence of prefix function application and infix
function application?
Remark: (o) gives a rather unintuitive answer. The reason is that floating-point arithmetic
(arithmetic with Floats and Doubles) is only an approximation to arithmetic with real
numbers. Computers use a finite representation for what is, mathematically, an infinite and
continuous object (the infinite real line). The associated arithmetic is therefore subject to
artefacts such as rounding error (we don’t get exactly the right answer) and overflow (the answer
is too big or too small for the computer to represent). To understand this fully you will have to
wait until the lectures on computer arithmetic which will follow shortly in another course.
Finally, for practice, work out how Haskell brackets the following expressions and test your
answers by typing in equivalent expressions with the brackets explicitly inserted. Note: the
function truncate is a prefix function which rounds a floating point number (Float) down to
the nearest Int, e.g. truncate 19.567 = 19; max and min respectively return the
larger and smaller of two given numbers (works with Int, Integer, Float and
Double types).
(q)
(r)
(s)
(t)
not
1 +
cos
2 ^
True || 5 < 6
sin 0.7 ^ 2 2 * pi ^ 2 >=
truncate pi ^
`mod` truncate 2.8 ^ 2
sqrt 5 * 4
4 || sin pi < max 0.5 0.9
2 < 0.8 && pi > sin 0.8
2.
Using the Haskell operators div, mod and ord, where necessary, write expressions to
find the values of the following:
(a)
(b)
(c)
(d)
(e)
The last digit of the number 123
The penultimate digit of the number 456
The eighth lower-case letter of the alphabet (i.e. starting from ‘a’)
The “middle” upper-case letter of the alphabet
The minimum number of 13 gallon barrels needed to hold the entire contents of an
823 gallon vat
3.
Using the minimum number of brackets as possible, write down expressions which
evaluate to True iff:
(a)
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(i)
(j)
100 is exactly divisible by 10
22 is even (do not use the built-in even function in Haskell!)
The penultimate digit of 139 is different from the last digit of 55
The letter ‘z’ is the 26th lower-case letter of the alphabet
The letter ‘c’ is either the 1st or the 3rd lower-case letter of the alphabet
The letter ‘b’ is neither the 1st nor the 3rd lower-case letter of the alphabet
As for (e) but this time do not use the not operation
100 lies between 50 and 200
10 does not lie between 50 and 200
As for (h) but do not use the not operation
4.
All of the following expressions are syntactically correct, but some of them have some
form of type error. For each, work out whether the expression is correctly typed or not. Where
the type is incorrect work out how Haskell has implicitly bracketed the expressions.
(a)
(b)
(c)
(d)
(e)
(f)
2 * 3 == 6
6 == 2 * 3
2 + 3 == 5
not 2 * 3 == 10
3 == 4 == True
if True then False else True == True
(g)
(h)
(i)
if 3 == 4 then ord 'a' else ord 'b'
ord if 3 == 4 then 'a' else 'b' + 1
8 > 2 ^ if 3 == 4 then 2 else 3 == False
Assuming that the expressions with incorrect types were a result of the programmer accidentally
omitting parentheses, suggest where they might be added to make the types of the expressions
correct. What types do these expressions now have?
5.
Write a qualified expression using a let which finds the two roots of the equation
ax2 + bx + c = 0
using the formula
b (b2  4ac)
______________
2a
in the case where a=1, b=4 and c=1.5. Write your expression in such a way that the term inside
the square-root is computed only once and return the two roots in the form of a tuple.
6.
Using `div` and `mod` write an expression which converts time (an integer called s, say),
representing the number of seconds since midnight, into a triple of integers representing the
number of (whole) hours, minutes and seconds since midnight. Use a let to perform the
calculation for the specific case where s=8473.
7.
A polar coordinate (r,) can be converted into cartesian coordinates using the fact that the
x-axis displacement is r cos , and the y-axis displacement is r sin  Write a let expression
which converts the polar coordinate (1,/4) into cartesian coordinates represented by a pair of
Floats.
Remark: Haskell allows you to match tuples in qualified expressions. As an example, in the
same way that we can write let x = 5 in blah, which gives x the value 5 in expression
blah we can also write let (x,y) = (5,6) in blah which simultaneously gives x the
value 5 and y the value 6. Alternatively, there are built-in functions called fst and snd which
will respectively deliver the first and second elements of a given pair. Solve this problem using
both approaches, i.e. using tuple matching and by explicit use of the so-called projectors fst
and snd.
8.
The operators ==, /=, <, >, <=, >= also work on tuples. To see how, try the following:
(a)
(b)
(c)
( 1, 2 ) < ( 1, 4 )
( ( 4, 9 ), ( 3, 1 ) ) > ( ( 1, 10 ), ( 9, 4 ) )
( 'a', 5, ( 3, 'b' ) ) < ( 'a', 5, ( 3, 'c' ) )
Remark: The ordering used by Haskell's comparison operators is lexicographical - it works from
left to right just like the ordering used in a dictionary.
Functional Programming in Haskell
Unassessed Exercise Sheet 2: Functions
For each function you define enter its type as well. As an additional exercise, when you are
happy that your function is correct, delete the type definition using the editor and then return to
the Hugs system. The Hugs system will automatically work out (infer) the type of your function
and you can check this by typing :type <fun> where <fun> is the name of your function. In
some cases you may be surprised to see that the inferred type is different to the type you
originally wrote down. You may be able to work out what its doing in these cases; if not the later
lectures will help to explain.
1.
Write a function adddigit of two arguments which will add a single-digit integer onto
the right-hand end of an arbitrary-size integer. For example, adddigit 123 4 should
evaluate to 1234. Ignore the possibility of integer overflow.
2.
Write a function convert which converts a temperature given in oC to the equivalent
temperature in oF. (To convert from oF to oC the rule is to subtract 32 from the temperature in oF
and then multiply the result by 5/9.)
3.
Given the type synonym type Vertex = ( Float, Float ) write a function
distance :: Vertex -> Vertex -> Float that will calculate the distance between
two points, each represented as a Vertex. Using the distance function write a function
triArea :: Vertex -> Vertex -> Vertex -> Float that will calculate the area
of the triangle formed by the three given vertices. Note that a triangle whose sides are of length
a, b and c has an area given by s(s  a)( s  b)( s  c) where s  (a  b  c) / 2 .
4.
The factorial of a non-negative integer n is denoted as n! and defined as:
n  (n-1)  (n-2)  (n-3) …  2  1
0! is defined to be 1. Write a function fact which defines the factorial of a non- negative
integer. Ignore the possibility of integer overflow.
5.
Write a function remainder which defines the remainder after integer division.
Implement the division by repeated subtraction (i.e. do not use the predefined functions div and
mod). Ignore the possibility of division by zero.
6.
Write a function quotient which defines integer division using repeated subtraction.
Ignore division by zero.
7.
The Fibonacci numbers are defined by the recurrence
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2, n>1
Write a Haskell function fib that, given an integer n=0, 1, 2, ... returns the nth Fibonacci number
by encoding the above recurrence directly. If you think about the way a call to this function will
be evaluated you will notice that there is an enormous degree of redundancy. For a given n, he
function call Fn-2 is made twice, the call Fn-3 three times, Fn-4 five times etc. (Notice any
pattern?!) A much more efficient way to compute the nth Fibonacci number is to use an auxiliary
function that includes as arguments the kth and the (k+1)th Fibonacci numbers for some number k.
The idea is that the recursive call is made with the (k+1)th and the (k+2)th Fibonacci numbers can you see how to generate them from the kth and the (k+1)th? You will also need to carry a
third argument which keeps count of how many (more) numbers in the sequence to calculate
before arriving at the required (nth) one. Define an auxiliary function fib' which works in this
way and redefine the original fib function to call fib' appropriately.
8.
The Golden Ratio, G, is a beautiful number discovered by the ancient Greeks and used to
proportion the Parthenon and other ancient buildings. The golden ratio has this property: take a
rectangle whose sides (long:short) are in the golden ratio. Cut out the largest square you can and
the sides of the rectangle that remains are also in the golden ratio. From this you should be able
1 5
to work out that G 
(do it!). Curiously, it can also be shown that the ratio of
2
F
consecutive Fibonacci numbers converges on the golden ratio, i.e. lim n n1  G . Inspired
Fn
by your earlier fib' function build a function gRatio which takes a threshold value e of type
F
Float and which returns the value of n  2 where n is the smallest integer satisfying
Fn 1
Fn 1 Fn  2

 e . Hint: notice that you now need three consecutive Fibonacci numbers instead
Fn
Fn 1
of just two. The absolute value of a number (ignoring its sign, that is) can be obtained using
Haskell's abs function. Also, to perform the division above, you must first cast the integers
representing the various Fibonacci numbers into floating-point numbers using the built-in
function fromInt. For now you can think of this function as having type fromInt :: Int
-> Float. The true picture will emerge later in the course.
9.
A decimal number d may be converted to binary representation by the following
algorithm:
i. If d is less than 2, its binary representation is just d.
ii. Otherwise, divide by 2. The remainder gives the last (rightmost) digit of the binary
representation, which is either 0 or 1.
iii. The preceding digits of the binary representation are given by the binary
representation of the quotient from step (b).
Write a function binary which takes an integer and converts it to its binary representation.
This will be a decimal number consisting only of the digits 0 and 1. As an example,
binary 19 should produce the answer 10011.
10.
Write a function newbase of two arguments which converts a number to its
representation in some specified base  10. The value produced will be a decimal number
containing only the digits which are valid in the new base. This is a generalisation of the
binary function above.
11.
The number of arrangements (permutations) of r objects selected from a larger set of n
objects is denoted as nPr and defined as:
n  (n-1)  (n-2) …  (n-r+1)
n!
or ______
(n-r)!
Define a recursive function perm such that perm n r evaluates nPr (do not use fact)
12.
Given an integer argument the built-in function succ returns the next largest integer.
For example, succ 4 returns 5. The function pred does the opposite. Write a function add2
which will add two non-negative numbers using succ and pred only. Use multiple recursion
equations with pattern matching rather than conditionals. Remember that there are two
parameters, each of which can be either zero or non-zero, so a strict definition will require four
equations. Can the number of equations be reduced?
13.
Write a recursive function larger to determine the larger of two positive integers.
Use multiple recursion equations and do not use conditional expressions. Hint: write down the
left-hand sides of the four recursion equations first.
14.
Write a recursive function chop which takes an n-digit number and which uses
repeated subtraction to define a pair of numbers which are the first n-1 digits and the last digit
respectively. Do not use div or mod.
15.
Write a function concatenate which concatenates the digits of two non-zero
integers. concatenate 123 456 evaluates to 123456 but concatenate 123 0 evaluates
to 123. Use the earlier function chop. Estimate the cost of your concatenate function in
terms of the number of multiplications and subtractions it performs.
Functional Programming in Haskell
Unassessed Exercise Sheet 3: Lists and Higher-order Functions
Many of these questions concern some of Haskell's (first-order) built-in list processing functions.
By the end of the course you should be familiar with all of them and be able to use them to best
advantage.
length :: [ a ] -> Int
The number of items in a list
(!!) :: [ a ] -> Int -> a
xs !! n returns the nth element of xs starting at index 0
null :: [ a ] -> Bool
True if the list is empty; False otherwise
(++) :: [ a ] -> [ a ] -> [ a ]
Joins (appends) two lists
head :: [ a ] -> a
Returns the first item in the list (equivalent to index 0)
tail :: [ a ] -> [ a ]
Returns the list with the first item removed
take :: Int -> [ a ] -> [ a ]
take n xs returns the first n items in xs
drop :: Int -> [ a ] -> [ a ]
drop n xs returns the rest of xs after the first n items have been removed
zip :: [ a ] -> [ b ] -> [ ( a, b ) ]
Zips the elements of the two lists pairwise
unzip :: [ ( a, b ) ] -> ( [ a ], [ b ] )
Opposite of zip
The following are list generalisations of the functions +, *, &&, ||, ++, max and
min respectively:
sum :: [ Int ] -> Int
Returns the sum of the elements of the list (also works with Integer, Float and
Double types)
product :: [ Int ] -> Int
As above, but returns the product
and :: [ Bool ] -> Bool
True iff every element of the list is True
or :: [ Bool ] -> Bool
False iff every element of the list is False
concat :: [ [ a ] ] -> [ a ]
Joins the elements of a list of lists
maximum :: [ Int ] -> Int
Delivers the largest element in the given list (also works with Integer, Float and
Double types)
minimum :: [ Int ] -> Int
As above but delivers the smallest element
Now the higher-order functions:
map :: ( a -> b ) -> [ a ] -> [ b ]
Applies the given function to each element of the given list
filter :: ( a -> Bool ) -> [ a ] -> [ a ]
Returns a list of those elements in the given list which satisfy the given predicate
zipWith :: ( a -> b -> c ) - [ a ] -> [ b ] -> [ c ]
Applies the given binary function pairwise to the elements of the two lists
foldr1 :: [ a -> a -> a ) -> [ a ] -> a
Returns the result of inserting the binary operator, in right-associative fashion, in between
each element of the given list. foldl1 does the same but associates to the left.
foldr :: [ a -> b -> b ) -> b -> [ a ] -> b
As above but with a separate right unit. foldl does the same but associates to the left.
1.
By inspection (check them if you like by typing them into Hugs), determine whether the
following are correctly typed. If they are, determine the resulting value. If not explain the cause
of the type error.
(a)
(b)
(c)
(d)
(e)
(f)
(g)
(h)
(i)
(j)
(k)
(l)
(m)
(n)
(o)
(p)
(q)
(r)
(s)
(t)
(u)
"H" : [ 'a', 's', 'k', 'e', 'l', 'l' ]
( 'o' : ['n'] ) ++ "going"
"Lug" ++ ( 'w' : "orm" )
if "a" == ['a'] then [] else "two"
"let xs = "let xs" in length [xs]
tail "emu" : drop 2 "much"
head [ "gasket" ]
zip "12" ( 1 : [ 2 ] )
tail ( (1,1), (2,2), (3,3) )
head [ 1, (2,3) ] /= head [ (2,3), 1 ]]
length [] + length [[]] + length [[[]]]
null ( [ "", "1", "11", "111" ] !! 1 )
zip [ 5, 3, length [] ] [ ( True, False, True ) ]
unzip [ ( 'b', 'd' ), ( 'a', 'o' ), ( 'd', 'g' ) ]
and [ 1 == 1, 'a' == 'a', True /= False ]
sum [ length "one", [ 2, 3 ] !! 0, 4 ]
minimum [ ( 'b', 1 ), ( 'a', 2 ), ( 'd', 3 ) ]
maximum zip "abc" [ 1, 2, 3 ]
concat [ tail [ "is", "not", "with", "standing" ] ]
map head [ 1..n ]
filter null ( map tail [ "a", "ab", "abc" ] )
(v)
(w)
(x)
(y)
(z)
foldr1 (||) ( map even [ 9, 6, 8 , 2 ] )
zipWith (:) "zip" "with"
foldr max 0 [ 0, 1 ]
foldr maximum [0] [ [0,1], [3,2] ]
zipWith (&&) [ True ] ( filter id [ True, False ] )
2.
Provide recursive definitions for each of the built-in functions defined above. (Most of
these will have been covered during the lectures; make sure that you can write them all this way.)
Note that to test them you will have to use different names to those used in the prelude, e.g.
myLength, myMaximum etc.
3.
The operators ==, /=, <, >, <=, >= also work on lists. The 'dictionary' style
(lexicographical) ordering is perhaps more apparent here. Try the following:
(a)
(b)
(c)
(d)
(e)
(f)
"Equals" == "Equals"
"dictionary" <= "dictator"
"False" > "Falsehood"
[ 1, 1, 1 ] < [ 2, 2, 2 ]
[ 1, 2, 3 ] < [ 1, 1, 5 ]
[ ( 1, "way" ) ] < [ ( 2, "ways" ), ( 3, "ways" ) ]
Now write a function precedes (equivalent to (<)) which takes two Strings and evaluates
to True if the first is lexicographically less than the second.
4.
Write a function pos to find the position of a specified Int in a list of Ints. Assume
that the integers are indexed from 0 and that the specified character will always be found). pos
'4' [ 3, 4, 0, 1 ] evaluates to 1. Using pos define quicksort :: [ Int ] ->
[ Int ]. The simplest version of this famous algorithm works by using the first element of a
list to partition the remainder of the list into two sublists: those smaller than (or equal to) the
head element, and those greater than it. The two sublists are sorted recursively and the resulting
lists joined together. Don't forget to add the head element back!
5.
Write a function twoSame :: [ Int ] -> Bool which delivers True iff the
given list of integers contains at least one duplicate element.
6.
Two anagrams can be considered to define a transposition of the characters of a third
string. For instance, "abcde" and "eabcd" define a transposition in which the last character
of a 5-character string is moved to the start. Define a function transpose to transpose the
characters of a string as specified by two anagrams.
transpose "UVWXYZ" "fedcba" "ecabdf" should evaluate to "VXZYWU". Hint: use
pos and !!, noting that all three strings are the same length and that each character of the
second string is unique and appears exactly once in the third.
7.
Write a function substring :: String -> String -> Bool which returns
True iff the first given string is a substring of the second, e.g.
substring "sub" "insubordinate" evaluates to True whilst
substring "not" "tonight" evaluates to False. Hint: one way to do this is to first
generate the list of all suffixes of the second string and then compare the first string with the
appropriate prefix of each suffix. The overall result is True iff at least one of these comparisons
succeeds (use the or function).
8.
Write a polymorphic function perms which generates all permutations of a given list.
9.
Write a polymorphic function right which will insert a single item at the right-hand end
of a list of items. right 's' "Hope" evaluates to "Hopes".
10.
Write a polymorphic function backwards which uses right to reverse a list of objects
of arbitrary type. For example, backwards "bonk" evaluates to "knob". Estimate the cost of
your function in terms of the number of ':' operations it performs to reverse a list of length n.
(Note: this function is called reverse in the Haskell prelude.)
11.
Now modify your backwards function so that it uses repeated applications of ':' to
an accumulating parameter instead of using right. This requires you to define an auxiliary
function with one extra argument to represent the “accumulator” (this will be [] when initally
called). Estimate the cost of the new version of the function in terms of the number of ':'
operations it performs to reverse a list of length n.
12.
Write a function nextWord which given a list of characters s (i.e. a String) returns a
pair consisiting of the next word in s and the remaining characters in s after the first word has
been removed. Words are assumed to be separated one or more whitespace characters (' ',
'\t', '\n'). Note: if you wish you may use the built-in function isSpace.
13.
Write a function splitUp which returns the list of words contained in a given String.
Use the function nextWord defined above.
14. Write a function squash :: ( a -> a -> b ) -> [ a ] -> [ b ] which applies
a given function to adjacent elements of a list, e.g.. squash f [ x1, x2, x3, x4 ]
-> [ f x1 x2, f x2 x3, f x3 x4 ]. Implement the function 1. Using explicit
recursion and pattern matching and 2. In terms of zipWith (hint: make use of the tail
function).
15.
Write a function converge :: ( a -> a -> Bool ) -> [ a ] -> a which
searches for convergence in a given list of values. It should apply its given function to adjacent
elements of the list until the function yields True, in which case the result is the first of the two
convergent values. For example, converge (==) [ 1, 2, 3, 4, 5, 5, 5 ] should
return 5. If no limit is found before the list runs out, print an error message.
16. Write a function limit :: ( a -> a -> Bool ) -> [ a ] -> [ a ] which
again checks for convergence but this time returns the list elements up to the point where the
convergence function delivers True. This is a generalisation of the takeWhile function.

1
Mathematics tells us that if 0  r  1 then  r n 
. Use this to test your limit function with
1 r
n 0
an expression of the form sum ( limit f ( map (r^) [0..] ) for some
convergence function f.
17.
Sometimes we need to compose a single-argument function with a binary function, as in
( succ . (+) ) 4 7, (yielding 12) for example. However, Haskell's (.) function will
only compose two single-argument functions. Define a new composition operator <.> that will
allow compositions of the above form. What is the most general type of <.>?
18.
Using '.', foldr, map and the identity function id, write a function pipeline
which given a list of functions, each of type a -> a will form a pipeline function of type
[ a ] -> [ a ]. In such a pipeline, each function in the original function list is applied in
turn to each element of the input (assume the functions are applied from right to left in this case).
You can imagine this as being like a conveyor belt system in a factory where goods are
assembled in a fixed number of processing steps as they pass down a conveyor belt. Each
process performs a part of the assembly and passes the (partially completed) goods on to the next
process. Test your function by forming a pipeline from the function list
[ (+ 1), (* 2), pred ] with the resulting pipeline being applied to the input list
[ 1, 2, 3 ].
Hint: Notice that if f :: a -> a then map f is a function of type [ a ] -> [ a ].
Functional Programming in Haskell
Unassessed Exercise Sheet 4: User-Defined Data Types
1.
Define a data type Shape suitable for representing arbitrary triangles, whose
dimensions are specified by the length of the triangle's three sides (all Floats), squares, whose
dimensions are given by a single Float, and circles, each specified by its radius (again a
Float). Define a function area :: Shape -> Float which returns the area of a given
Shape. Recall: The area of a triangle with dimensions a, b, c is given by:
A = sqrt( s (s-a) (s-b) (s-c) ) where s = ½(a+b+c)
2.
Now augment Shape with (convex) polygons, specified by a list of the polygon vertices
((x,y) coordinates in the Cartesian plane). Augment the area function accordingly. Note: the area
of a convex polygon is the area of the triangle formed by its first three vertices plus the area of
the polygon remaining when the triangle has been removed from the polygon. Note: the length
of the line joining vertex (x,y) with vertex (x',y') is given by
L = sqrt( (x-x')^2 + (y-y')^2 )
3.
Define a type synonym Date suitable for representing calendar dates. Write a function
age which given the birth date of a person and the current date returns the person’s age in years
as an Int.
4.
Define a new data type Possibly which can represent success/failure outcomes.
Failure should be represented as a parameterless constructor whilst success should be a
constructor that carries with it a result value of arbitrary type (Possibly should be
polymorphic). Define a function tableLookUp :: a -> [ ( a, b ) ] ->
Possibly b which searches for an item in a table of item/value pairs. If the item is found the
lookup should succeed with the corresponding value; otherwise it should report failure.1
5.
A university Computing department has two types of employee: Teaching staff and
Support staff. Teaching staff have an associated research section which is either Systems,
Software or Theory, and a list of courses that they teach. Each course is identified by a unique
number. Support staff are classified as either Administrative or Technical. The department’s staff
database comprises a list of staff records described by the Haskell type synonym:
Database = [ Ustaff ]
1
When you have completed the solution take a look at the type Maybe and the function lookup in the Haskell
prelude. Possibly and tableLookUp should behave identically!
Associated with each employee is also a basic information record comprising the employees
name, sex (male or female), date of birth and salary. Define a data type Sex suitable for
representing a person’s sex and define a type synonym Empdata for representing each basic
information record. Finally define an algebraic data type for Ustaff.
Remark: There is no unique way of representing the database, as described. You may find that
your solution is different to the model answer.
6.
For the above Database write Haskell functions to compute the following:
(a)
(b)
(c)
The total number of support staff
The name of the teacher who teaches a given course
The total salary bill for the department (this is ususally a very small number!)
Hints: You may find it useful to define the following auxiliary functions:
•
name and salary for returning the name and salary of a given Ustaff (functions that
pick out elements of a tuple like this are sometimes called projector functions)
•
isSupport which will return True iff a given Ustaff is a support staff
•
teaches, which given a course number and a Ustaff delivers True iff the given
Ustaff teaches the given course
7.
Define a polymorphic data type for describing binary trees in which all values are stored
at the leaves; there should be no concept of an “empty” tree. Write a function build which will
construct a balanced binary tree from a non-empty list of numbers by using splitAt. You
could issue an error message if build is applied to an empty list.
Remark: A balanced binary tree has the property that at each internal node the sizes of the left
and right subtrees differs by at most 1.
8.
Write a function ends which will convert a binary tree to a list preserving a left-to-right
ordering of the numbers at the fringe of the tree.
9.
Write a function swap which will interchange the subtrees of a binary tree at every level.
For a list xs how does the value of ends ( swap ( build xs ) ) relate to xs?
10.
A queue is a data structure which can contain zero or more elements, ordered by their
arrival times. Define a data type to represent queues of customers represented by words
containing their names. Write functions nobody to create an empty queue, arrive to add a
new customer to the rear of an existing queue, first to find the first (least recently arrived)
customer in a queue and serve to remove the first customer from the front of a non-empty
queue.
11.
Design a data type Time which is capable of representing time in two ways: either in 24hour format (for example 1356hrs) or in conventional hours and minutes, with an additional flag
to indicate whether the time is am or pm (use an additional enumerated data type AmPm to
represent the two possibilities). Use deriving to enable times to be both displayed and
compared for structural equality. Notice that at this stage the derived definition of '==' does
not “know” that the times 1400hrs in 24-hour format and 2-00pm represent the same time!
12.
Write a function to24 which, given a time in conventional format will convert it to 24hour format.
13.
Using to24 write a function equaltime which given two times in either format
returns True if the times are the same; False otherwise. For example given the representations
of 1514hrs and 3:14pm equaltime should return True.
14.
Now delete the deriving statement and explicitly add Time as an instance of the class Eq.
At the same time define '==' on Times which delivers True if two times are equal regardless
of the format of the time. Test your implementation by comparing various Times for both
equality and inequality (recall that '/=' is defined by default in terms of '==' in the
definition of class Eq).
15.
Now make Time an instance of the class Show and provide a definition of the function
showsPrec (see below) for displaying Times (see below). A time in 24-hour format should be
displayed in the form “xxxxHRS” where xxxx is the four-digit 24-hour time. For a time in the
conventional format, hours (hh) and minutes (mm) should be displayed in the form “hh:mmAM”
for times before midday, and “hh:mmPM” for times after midday, with the special cases that 1200pm and 12-00am should be displayed as “Midday” and “Midnight” respectively. Test your
implementation by typing various expressions of type Time on the Haskell command line.
Remark: The class Show has a member function showsPrec. When a new type T is made an
instance of Show the showsPrec function defines how to convert an object of type T into a
(printable) string. The Haskell system uses this function to print representations of objects, as in:
Main> “Hello”
“Hello”
Main> [ Mon, Tues ]
[ Mon, Tues ]
showsPrec is of type Int -> T -> String -> String. The first argument is a
precedence which can be used to print representations using the minimum number of brackets.
Do not worry about this here; simply ignore it! The String argument is an accumulation of
the result string that will form the final printable representation. All you have to do here is
append to this your chosen representation of T; for the Time data type you will need two rules,
one for each time format. Note that the result list is accumulated from right to left, so you will
need to append your representation as the leftmost argument of '++'.
Q16-18. The following questions are quite hard an invite you to think about higher-order
functions over trees, rather than those over lists that you have seen so far. There is a short
introduction to this in the lecture notes, although we will not be covering it in the lectures
themselves.
16.
Define a new polymorphic data type Tree3 which stores values both at the internal
nodes and the leaves, and for which there is an Empty data constructor for representing empty
trees. Arrange it so that values at the nodes may have a different type to those at the leaves.
17.
Define a function map3 which maps two functions over a given Tree3. The first
function should be applied to values at the leaves and the second to values at the nodes.
18.
Define the function fold3, a version of fold for objects of type Tree3 which takes two
functions, one to transform a given leaf value and one to reduce an internal node. (You can best
picture this as a transformation on a tree which replaces the empty tree by a given base case, and
the leaf and node constructors with the supplied functions.) Define functions in terms of fold3
to do the following:
(a)
(b)
(c)
(d)
(e)
Count the number of leaves in a given tree (an empty tree should not be counted as a leaf)
Sum the values in a given tree of Ints
Perform a left-to-right in-order flattening of a tree to yield a list
Perform a right-to-left in-order flattening of a tree which delivers the same result as (c)
only in the reverse order (do this by modifying your folding functions; do not use a
reverse function on the result from (c)!!)
Search a tree of value-attribute pairs of the form (v,a) for all occurrences of a given
value. The attributes associated with each such value in the tree should be returned as a
list. Use an auxiliary function pass :: a -> ( a, b ) -> [ b ] which takes a
value and a value-attribute pair and which returns the singleton list consisting of the
attribute if the two values match; [] otherwise.
APPENDIX: Summary of the Haskell Prelude
Types and Synonyms
data () = () deriving (Eq, Ord, Enum, Bounded)
Instances: Read Show
data [a] = [] | a : [a] deriving (Eq, Ord)
Instances: Read Show Functor Monad MonadZero MonadPlus
data (a,b) = (a,b) deriving (Eq, Ord, Bounded)
Instances: Read Show
data a->b
Instances: Show
data Bool = False | True deriving (Eq, Ord, Enum, Read, Show, Bounded)
data Char
Instances: Eq Ord Enum Read Show
data Double
Instances: Eq Ord Enum Read Show Num Real Fractional RealFrac
Floating RealFloat
data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
type FilePath = String
data Float
Instances: Eq Ord Enum Read Show Num Real Fractional RealFrac
Floating RealFloat
data Int
Instances: Eq Ord Enum Read Show Num Real Integral Bounded
data Integer
Instances: Eq Ord Enum Read Show Num Real Integral
data IO a
Instances: Show Functor Monad
data IOError
Instances: Show Eq
data Maybe a = Nothing | Just a deriving (Eq, Ord, Read, Show)
Instances: Functor Monad MonadZero MonadPlus
data Ordering = LT | EQ | GT deriving (Eq, Ord, Enum, Read, Show, Bounded)
type ReadS a = String -> [(a,String)]
type ShowS = String -> String
type String = [Char]
data Void
Constructors
[]
:
(,)
EQ
False
GT
Just
Left
LT
Nothing
Right
True
Classes
class
class
class
class
class
(Fractional
class
(Num
class
class
(Real a, Enum
class
class
(MonadZero
class
(Monad
class
(Eq a, Show a, Eval
class
(Eq
class
class
(Num a, Ord
class (RealFrac a, Floating
class
(Real a, Fractional
class
a) =>
a) =>
a) =>
m)
m)
a)
a)
=>
=>
=>
=>
a) =>
a) =>
a) =>
Bounded a
Enum a
Eq a
Eval a
Floating a
Fractional a
Functor f
Integral a
Monad m
MonadPlus m
MonadZero m
Num a
Ord a
Read a
Real a
RealFloat a
RealFrac a
Show a
Functions and Methods
(!!)
:: [a] -> Int -> a
[0,1,2] !! 1 = 1
($)
:: (a -> b) -> a -> b
f x $ g y = f x (g y)
(&&)
:: Bool -> Bool -> Bool
Boolean `and'
(||)
:: Bool -> Bool -> Bool
Boolean `or'
(*)
:: Num a => a -> a -> a
(**)
:: Floating a => a -> a -> a
(+)
:: Num a => a -> a -> a
(++)
:: MonadPlus m => m a -> m a -> m a
"abc" ++ "def" = "abdec"
(-)
:: Num a => a -> a -> a
(.)
:: (b -> c) -> (a -> b) -> a -> c
Function composition
(/)
:: Fractional a => a -> a -> a
(/=)
:: Eq a => a -> a -> Bool
not equal
(<)
:: Ord a => a -> a -> Bool
(<=)
:: Ord a => a -> a -> Bool
(==)
:: Eq a => a -> a -> Bool
(>)
:: Ord a => a -> a -> Bool
(>=)
:: Ord a => a -> a -> Bool
(>>)
:: m a -> m b -> m b
Monadic binding
(>>=)
:: Monad m => m a -> (a -> m b) -> m b
Monadic binding
(^)
:: (Num a, Integral b) => a -> b -> a
(^^)
:: (Fractional a, Integral b) => a -> b -> a
negative exponent allowed
abs
:: Num a => a -> a
accumulate
:: Monad m => [m a] -> m [a]
acos
:: Floating a => a -> a
acosh
:: Floating a => a -> a
all
:: (a -> Bool) -> [a] -> Bool
all (/= 'a') "cba" = False
and
:: [Bool] -> Bool
and [True, True, True] = True
any
:: (a -> Bool) -> [a] -> Bool
any (== 'c') "abc" = True
appendFile
:: FilePath -> String -> IO ()
applyM
:: Monad m => (a -> m b) -> m a -> m b
asTypeOf
:: a -> a -> a
Sort of a type cast
asin
:: Floating a => a -> a
asinh
:: Floating a => a -> a
atan
:: Floating a => a -> a
atan2
:: RealFrac a => a -> a
atanh
:: Floating a => a -> a
break
:: (a -> Bool) -> [a] -> ([a], [a])
break (<2) [1,2,3] = ([1],[2,3])
catch
:: IO a -> (IOError -> IO a) -> IO a
ceiling
:: (RealFrac a, Integral b) => a -> b
compare
:: Ord a => a -> a -> Ordering
concat
:: MonadPlus m => [m a] -> m a
concat ["a","bc","d"] = "abcd"
concatMap
:: (a -> [b]) -> [a] -> [b]
const
:: a -> b -> a
cos
:: Floating a => a -> a
cosh
:: Floating a => a -> a
curry
:: ((a, b) -> c) -> a -> b -> c
cycle
:: [a] -> [a]
cycle "abc" = "abcabcabc ..."
decodeFloat
:: RealFloat a => a -> (Integer, Int)
div
:: Integral a => a -> a -> a
divMod
:: Integral a => a -> a -> (a, a)
drop
:: Int -> [a] -> [a]
drop 2 "abcd" = "cd"
dropWhile
:: (a -> Bool) -> [a] -> [a]
dropWhile (>3) [5,3,5] = [3,5]
elem
:: Eq a => a -> [a] -> Bool
'a' `Elem` "abc" = True
encodeFloat
:: RealFloat a => Integer -> Int -> a
enumFrom
:: Enum a => a -> [a]
[n..]
enumFromThen
:: Enum a => a -> a -> [a]
[m,n..]
enumFromThenTo
:: Enum a => a -> a -> a -> [a]
[m,n..o]
enumFromTo
:: Enum a => a -> a -> [a]
[m..n]
error
:: String -> a
even
:: Integral a => a -> Bool
exp
:: Floating a => a -> a
exponent
:: RealFloat a => a -> Int
fail
:: IOError -> IO a
filter
:: MonadZero m => (a -> Bool) -> m a -> m a
filter isUpper "AbCd" = "AB"
flip
:: (a -> b -> c) -> (b -> a -> c)
floatDigits
:: RealFloat a => a -> Int
floatRadix
:: RealFloat a => a -> Integer
floatRange
:: RealFloat a => a -> (Int, Int)
floor
:: (RealFrac a, Integral b) => a -> b
foldl
:: (a -> b -> a) -> a -> [b] -> a
foldl (+) 0 [a,b,c] = ((0+a)+b)+c
foldl1
:: (a -> a -> a) -> [a] -> a
foldl1 (+) [a,b,c] = (a+b)+c
foldr
:: (a -> b -> b) -> b -> [a] -> b
foldr (+) 0 [a,b,c] = a+(b+(c+0))
foldr1
:: (a -> a -> a) -> [a] -> a
foldr (+) [a,b,c] = a+(b+c)
fromEnum
:: Enum a => a -> Int
fromInteger
:: Num a => Integer -> a
fromIntegral
:: (Integral a, Num b) => a -> b
fromRational
:: Fractional a => Rational -> a
fromRealFrac
:: (RealFrac a, Fractional b) => a -> b
fst
:: (a, b) -> a
gcd
:: (Integral a) => a -> a -> a
getChar
:: IO Char
eof generates an IOError
getContents
:: IO String
getLine
:: IO Char
eof generates an IOError
guard
:: MonadZero m => Bool -> m ()
head
:: [a] -> a
id
:: a -> a
init
:: [a] -> [a]
init "abcd" = "abc"
interact
:: (String -> String) -> IO ()
isDenormalized
:: RealFloat a => a -> Bool
isIEEE
:: RealFloat a => a -> Bool
isInfinite
:: RealFloat a => a -> Bool
isNaN
:: RealFloat a => a -> Bool
isNegativeZero
:: RealFloat a => a -> Bool
iterate
:: (a -> a) -> a -> [a]
iterate (++ " ") "" = ["", " ", " ",...]
last
:: [a] -> a
last "abcde" = "e"
lcm
:: Integral a => a -> a -> a
length
:: [a] -> Int
length "Abc" = 3
lex
:: ReadS String
lex "abc def" = [("abc"," def")]
lines
:: String -> [String]
log
:: Floating a => a -> a
logBase
:: Floating a => a -> a -> a
lookup
:: Eq a => a -> [(a, b)] -> Maybe b
map
:: Functor f => (a -> b) -> f a -> f b
mapM
:: Monad m => (a -> m b) -> [a] -> m [b]
mapM_
:: Monad m => (a -> m b) -> [a] -> m ()
max
:: Ord a => a -> a -> a
maxBound
:: Bounded a => a
maximum
:: Ord a => [a] -> a
maybe
:: b -> (a -> b) -> Maybe a -> b
maybe 0 (+1) (Just 1) = 2
min
:: Ord a => a -> a -> a
minBound
:: Bounded a => a
minimum
:: Ord a => [a] -> a
mod
:: Integral a => a -> a -> a
negate
:: Num a => a -> a
not
:: Bool -> Bool
notElem
:: Eq a => a -> [a] -> Bool
null
:: [a] -> Bool
odd
:: Integral a => a -> Bool
or
:: [Bool] -> Bool
otherwise
:: Bool
pi
:: Floating a => a
pred
:: Enum a => a -> a
pred True = False
print
:: Show a => IO ()
adds a newline
product
:: Num a => [a] -> a
properFraction
:: (RealFrac a, Integral b) => a -> (b, a)
putChar
:: Char -> IO ()
putStr
:: String -> IO ()
putStrLn
:: String -> IO ()
adds a newline
quot
:: Integral a => a -> a -> a
quotRem
:: Integral a => a -> a -> (a, a)
read
:: Read a => String -> a
readFile
:: FilePath -> IO String
readIO
:: Read a => String -> IO a
fails with IOError
readList
:: Read a => ReadS [a]
readLn
:: Read a => IO a
readParen
:: Bool -> ReadS a -> ReadS a
reads
:: Read a => ReadS a
reads "1 2" :: [(Int,String)] = [(1," 2")]
readsPrec
:: Read a => Int -> ReadS a
recip
:: Fractional a => a -> a
rem
:: Integral a => a -> a -> a
repeat
:: a -> [a]
repeat 'a' = "aaaaaaaaa..."
replicate
:: Int -> a -> [a]
replicate 4 'a' = "aaaa"
return
:: Monad m => a -> m a
reverse
:: [a] -> [a]
reverse "abc" = "cba"
round
:: (RealFrac a, Integral b) => a -> b
scaleFloat
:: RealFloat a => Int -> a -> a
scanl
:: (a -> b -> a) -> a -> [b] -> [a]
scanl (+) 0 [1,2,3] = [1,3,6]
scanl1
:: (a -> a -> a) -> [a] -> [a]
scanl1 (+) [1,2,3] = [1,3,6]
scanr
:: (a -> b -> b) -> b -> [a] -> [b]
scanr (+) 0 [1,2,3] = [6,5,1,0]
scanr1
:: (a -> a -> a) -> [a] -> [a]
scanr (+) [1,2,3] = [6,5,1]
seq
:: Eval a => a -> a -> b
sequence
:: Monad m => [m a] -> m ()
do operations in sequence
show
:: Show a => a -> String
showChar
:: Char -> ShowS
showList
:: Show a => [a] -> ShowS
showParen
:: Bool -> ShowS -> ShowS
showString
:: String -> ShowS
shows
:: Show a => a -> ShowS
showsPrec
:: Show a => Int -> a -> ShowS
significand
:: RealFloat a => a -> a
signum
:: Num a => a -> a
sin
:: Floating a => a -> a
sinh
:: Floating a => a -> a
snd
:: (a, b) -> b
span
:: (a -> Bool) -> [a] -> ([a], [a])
span isAlpha "ab cd" = ("ab"," cd")
splitAt
:: Int -> [a] -> ([a], [a])
splitAt 2 "abcdef" = ("ab","cdef")
sqrt
:: Floating a => a -> a
strict
:: Eval a => (a -> b) -> (a -> b)
subtract
:: Num a => a -> a -> a
succ
:: Enum a => a -> a
succ False = True
sum
:: Num a => [a] -> a
sum [1,2,3] = 6
tail
:: [a] -> [a]
tail "abc" = "bc"
take
:: Int -> [a] -> [a]
take 3 "abcde" = "abc"
takeWhile
:: (a -> Bool) -> [a] -> [a]
takeWhile (> 2) [3,2,1] = [3]
tan
:: Floating a => a -> a
tanh
:: Floating a => a -> a
toEnum
:: Enum a => Int -> a
toEnum 0 :: Bool = False
toInteger
:: Integral a => a -> Integer
toRational
:: Real a => a -> Rational
truncate
:: (RealFrac a, Integral b) => a -> b
uncurry
:: (a -> b -> c) -> ((a, b) -> c)
undefined
:: a
unlines
:: [String] -> String
until
:: (a -> Bool) -> (a -> a) -> a -> a
until (> 3) (+ 2) 0 = 4
unwords
:: [String] -> String
unzip
:: [(a, b)] -> ([a], [b])
unzip [('a','b'),('c','d')] = ["ac",bd"]
unzip3
:: [(a, b, c)] -> ([a], [b], [c])
userError
:: String -> IOError
words
:: String -> [String]
words "ab d as+3" = ["ab","d","as+3"]
writeFile
:: FilePath -> String -> IO ()
zero
:: MonadZero m => m a
zip
:: [a] -> [b] -> [(a, b)]
zip "abc" "de" = [('a','d'), ('b',e')]
zip3
:: [a] -> [b] -> [c] -> [(a, b, c)]
zipWith
:: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith (+) [1,2] [3,4] = [4,7]
zipWith3
:: (a -> b -> c -> d) -> [a] -> [b] -> [c] -> [d]
Download