module Recursion where
import Question
factorialURL = "http://en.wikipedia.org/wiki/Factorial"
gcdUrl = "http://en.wikipedia.org/wiki/Gcd"
euclidUrl = "http://en.wikipedia.org/wiki/Euclidean_algorithm"
divisorUrl = "http://en.wikipedia.org/wiki/Divisor"
recursion = do
h2 <#>Recursion#>
-- Factorial.
<#>Implement the >factorial
function.#> ?> do
ul $ do
li $ p <#>Name: factorial.#>
li $ p <#>Arguments: n.#>
li $ p <#>Comment: -- Calculates the factorial of the
argument.#>
li $ p <#>Type signature: factorial :: Integer -> Integer#>
li $ do
p <#>Properties:#>
ul $ do
li $ fixed $ unlines
[ "prop_factorial :: Integer -> Property"
, "prop_factorial n ="
, " n >= 0 ==> factorial (n+1) `div` factorial n == n+1"
]
li $ fixed $ unlines
[ "-- n `divides` m is True iff n divides m."
, "divides :: Integer -> Integer -> Bool"
, "n `divides` m | n == 0 = error \"divides: Zero divisor.\""
, " | otherwise = m `mod` n == 0"
, ""
, "prop_factorial' :: Integer -> Property"
, "prop_factorial' n ="
, " n >= 0 ==> factorial n `divides` factorial (n+1)"
]
li $ fixed $ unlines
[ "-- Calculates the factorial of the argument."
, "factorial :: Integer -> Integer"
, "factorial n | n < 0 = error \"factorial: Negative input not allowed.\""
, "factorial 0 = 1"
, "factorial n = n * factorial (n-1)"
]
li $ do
p <#>The error function can be used to signal invalid
input. It halts the program and prints the given error message.
Try to write understandable error messages.#>
fixed $ unlines
[ "Main> factorial (-3)"
, ""
, "Program error: factorial: Negative input not allowed."
]
li $ p <#>By putting a function name in backquotes we can use it in an
infix style. Writing m `divides` n means
exactly the same thing as writing divides m
n. The reason for doing this is that the
expression becomes easier to read.#>
-- Even/odd.
<#>Write two functions. One should check whether its argument is
even, and the other whether its argument is odd. Use
recursion.#> ?> do
ul $ do
li $ p <#>Name: even and odd are already
defined by the Prelude. However, we can use
these names anyway if we hide the predefined functions
by writing import Prelude hiding (even, odd)
in the beginning of the file.#>
li $ p <#>Arguments: n.#>
li $ do
p <#>Comments:#>
ul $ do
li $ p <#>-- Checks if argument is even.#>
li $ p <#>-- Checks if argument is odd.#>
li $ do
p <#>Type signatures:#>
ul $ do
li $ fixed "even :: Integer -> Bool"
li $ fixed "odd :: Integer -> Bool"
li $ do
p <#>Properties:#>
ul $ do
li $ fixed $ unlines
[ "prop_even :: Integer -> Bool"
, "prop_even n = even (2*n) && not (even (2*n + 1))"
]
li $ fixed $ unlines
[ "prop_odd :: Integer -> Bool"
, "prop_odd n = odd (2*n + 1) && not (odd (2*n))"
]
li $ do
p <#>Three ways of solving the problem:#>
ul $ do
li $ fixed $ unlines
[ "-- Checks if argument is even."
, "even :: Integer -> Bool"
, "even 0 = True"
, "even n | n > 0 = not (even (n-1))"
, "even n | n < 0 = not (even (n+1))"
, ""
, "-- Checks if argument is odd."
, "odd :: Integer -> Bool"
, "odd n = not (even n)"
]
li $ fixed $ unlines
[ "-- Checks if argument is even."
, "even :: Integer -> Bool"
, "even 0 = True"
, "even 1 = False"
, "even n | n >= 2 = even (n-2)"
, "even n | n < 0 = even (-n)"
, ""
, "-- Checks if argument is odd."
, "odd :: Integer -> Bool"
, "odd n = not (even n)"
]
li $ fixed $ unlines
[ "-- Checks if argument is even."
, "even :: Integer -> Bool"
, "even 0 = True"
, "even n | n > 0 = odd (n-1)"
, "even n | n < 0 = odd (n+1)"
, ""
, "-- Checks if argument is odd."
, "odd :: Integer -> Bool"
, "odd 0 = False"
, "odd n | n > 0 = even (n-1)"
, "odd n | n < 0 = even (n+1)"
]
-- Number of divisors.
p <#>Compute the number of positive >divisors of an arbitrary positive
integer. You may want to write a helper function that takes two
integer arguments m and n:#>
ul $ do
li $ p <#>n: We want to know how many divisors n
has in the range 1 to m.#>
li $ p <#>m: The number next in turn. We should check
whether it divides n or not.#>
<#>If you get stuck, don't hesitate to take a peak at the
solution.#> ?> do
ul $ do
li $ fixed $ unlines
[ "-- n `divides` m is True iff n divides m."
, "divides :: Integer -> Integer -> Bool"
, "n `divides` m | n == 0 = error \"divides: Zero divisor.\""
, " | otherwise = m `mod` n == 0"
, ""
, "-- divisors n, for positive n, yields the number of positive divisors"
, "-- of n."
, "divisors :: Integer -> Integer"
, "divisors n | n <= 0 = error \"divisors: Negative input.\""
, " | otherwise = divisors' n"
, " where"
, " divisors' :: Integer -> Integer"
, " divisors' 1 = 1"
, " divisors' m | m `divides` n = 1 + divisors' (m - 1)"
, " | otherwise = divisors' (m - 1)"
, ""
, "-- All numbers greater than 1, also primes, have at least two divisors."
, "prop_divisors :: Integer -> Property"
, "prop_divisors n = n > 1 ==> 2 <= divs && divs <= n"
, " where divs = divisors n"
, ""
, "prop_divisors' :: Integer -> Integer -> Property"
, "prop_divisors' m n ="
, " m > 0 && n > 0 ==>"
, " divisors m <= divisors (m * n)"
]
li $ do
p <#>We use where to introduce local values and
functions. The names introduced using where are
only visible in the current function definition. We could
also have written divisors as follows (as
proposed in the hint):#>
fixed $ unlines
[ "-- divisors n, for positive n, yields the number of positive divisors"
, "-- of n."
, "divisors :: Integer -> Integer"
, "divisors n | n <= 0 = error \"divisors: Negative input.\""
, " | otherwise = divisors' n n"
, ""
, "-- Helper function for divisors."
, "divisors' :: Integer -> Integer -> Integer"
, "divisors' 1 n = 1"
, "divisors' m n | m `divides` n = 1 + divisors' (m - 1) n"
, " | otherwise = divisors' (m - 1) n"
]
p <#>However, this definition introduces another name into the
top-level scope, and unless we plan to use divisors'
in another context there is no point in having it as a
separate entity.#>
li $ do
p <#>You may want to think about why the last property above is to
be preferred over the one below. (See the next exercise for
an explanation.)#>
fixed $ unlines
[ "prop_divisors'' :: Integer -> Integer -> Property"
, "prop_divisors'' m n ="
, " m > 0 && n > 0 && m `divides` n ==>"
, " divisors m <= divisors n"
]
li $ p <#>Note that if we had calculated the set of all divisors,
instead of just the size of this set, then it would have
been easier to write interesting properties.#>
-- Gcd.
<#>Write down some properties which the >greatest common divisor (gcd) of two integers, not both
zero, should satisfy. Then implement a function which calculates
the gcd. If you get stuck, try using >Euclid's algorithm. (You may not use the function
gcd found in the Prelude.)#> ?> do
p <#>We cannot use the name gcd, so let's use
gcd' instead.#>
p <#>Some properties:#>
fixed $ unlines
[ "-- n `divides` m is True iff n divides m."
, "divides :: Integer -> Integer -> Bool"
, "n `divides` m | n == 0 = error \"divides: Zero divisor.\""
, " | otherwise = m `mod` n == 0"
, ""
, "-- x =^= y is True iff the absolute values of x and y are equal."
, "(=^=) :: Integer -> Integer -> Bool"
, "x =^= y = abs x == abs y"
, ""
, "prop_gcdDivisor :: Integer -> Integer -> Property"
, "prop_gcdDivisor m n ="
, " m /= 0 || n /= 0 ==> gcd' m n `divides` m"
, ""
, "prop_gcdCommutative :: Integer -> Integer -> Property"
, "prop_gcdCommutative m n ="
, " m /= 0 || n /= 0 ==> gcd' m n =^= gcd' n m"
, ""
, "prop_gcdGreatest :: Integer -> Integer -> Integer -> Property"
, "prop_gcdGreatest m n p ="
, " (m /= 0 || n /= 0) && p /= 0 &&"
, " p `divides` m && p `divides` n ==>"
, " p `divides` gcd' m n"
]
p <#>(We have not specified whether the gcd should be positive or
negative, hence the use of (=^=).)#>
<#>Can you find any problems with the last property?#> ?> do
p <#>What is the probability that the precondition is
True for randomly generated m, n
and p? We can collect test statistics using
collect:#>
fixed $ unlines
[ "prop_gcdGreatest' :: Integer -> Integer -> Integer -> Property"
, "prop_gcdGreatest' m n p ="
, " (m /= 0 || n /= 0) && p /= 0 &&"
, " p `divides` m && p `divides` n ==>"
, " collect p $"
, " p `divides` gcd' m n"
]
fixed $ unlines
[ "Main> quickCheck prop_gcdGreatest'"
, "OK, passed 100 tests."
, "37% -1."
, "28% 1."
, "10% -2."
, "7% 2."
, "6% 3."
, "5% 4."
, "3% -3."
, "2% 6."
, "1% 5."
, "1% -6."
]
p <#>We can see that over 60% of the ps are 1 or
-1; the precondition is almost always true for such
ps. You will learn more about how this situation
can be avoided later in the course.#>
fixed $ unlines
[ "-- gcd' m n computes the greatest common divisor of m and n. At most"
, "-- one of m and n may be zero."
, "gcd' :: Integer -> Integer -> Integer"
, "gcd' 0 0 = error \"The gcd of 0 and 0 is undefined.\""
, "gcd' m 0 = m"
, "gcd' m n = gcd' n (m `mod` n)"
]