move recursion with helper functions to lecture 2

- move some exercises from Set1 to a new Set2b
- rename Set2 to Set2a
- one new exercise in Set2a
- improvements to lecture 2 & lecture 3 texts
This commit is contained in:
Joel Kaasinen
2020-05-18 13:38:32 +03:00
parent c2ca744dc9
commit 6640ebf7cd
7 changed files with 1847 additions and 1632 deletions
+1 -126
View File
@@ -80,7 +80,7 @@ checkPassword password = if password == "swordfish"
-- Packages over 5000 grams cost a constant 6000 credits.
--
-- Write a function postagePrice that takes the weight of a package
-- in, and returns the cost in credits.
-- in grams, and returns the cost in credits.
postagePrice :: Int -> Int
postagePrice = todo
@@ -128,128 +128,3 @@ power = todo
ilog3 :: Integer -> Integer
ilog3 = todo
------------------------------------------------------------------------------
-- Ex 12: compute binomial coefficients using recursion. Binomial
-- coefficients are defined by the following equations:
--
-- B(n,k) = B(n-1,k) + B(n-1,k-1)
-- B(n,0) = 1
-- B(0,k) = 0, when k>0
--
-- Hint! pattern matching is your friend.
binomial :: Integer -> Integer -> Integer
binomial = todo
------------------------------------------------------------------------------
-- Ex 13: implement the odd factorial function. Odd factorial is like
-- factorial, but it only multiplies odd numbers.
--
-- Examples:
-- oddFactorial 7 ==> 7*5*3*1 ==> 105
-- oddFactorial 6 ==> 5*3*1 ==> 15
oddFactorial :: Integer -> Integer
oddFactorial = todo
------------------------------------------------------------------------------
-- Ex 14: implement the Euclidean Algorithm for finding the greatest
-- common divisor:
--
-- Given two numbers, a and b,
-- * if one is zero, return the other number
-- * if not, subtract the smaller number from the larger one
-- * replace the larger number with this new number
-- * repeat
--
-- For example,
-- myGcd 9 12 ==> 3
-- In this case, the algorithm proceeds like this
--
-- a b
--
-- 9 12
-- 9 (12-9)
-- 9 3
-- (9-3) 3
-- 6 3
-- (6-3) 3
-- 3 3
-- (3-3) 3
-- 0 3
--
-- Background reading:
-- * https://en.wikipedia.org/wiki/Euclidean_algorithm
myGcd :: Integer -> Integer -> Integer
myGcd = todo
------------------------------------------------------------------------------
-- Ex 15: Implement the function leftpad which adds space characters
-- to the start of the string until it is long enough.
--
-- Examples:
-- leftpad "foo" 5 ==> " foo"
-- leftpad "13" 3 ==> " 13"
-- leftpad "xxxxx" 3 ==> "xxxxx"
--
-- Tips:
-- * you can combine strings with the ++ operator.
-- * you can compute the length of a string with the length function
leftpad :: String -> Int -> String
leftpad = todo
------------------------------------------------------------------------------
-- Ex 16: let's make a countdown for a rocket! Given a number, you
-- should produce a string that says "Ready!", counts down from the
-- number, and then says "Liftoff!".
--
-- For example,
-- countdown 4 ==> "Ready! 4... 3... 2... 1... Liftoff!"
--
-- Hints:
-- * you can combine strings with the ++ operator
-- * you can use the show function to convert a number into a string
-- * you'll probably need a recursive helper function
countdown :: Integer -> String
countdown = todo
------------------------------------------------------------------------------
-- Ex 17: implement the function smallestDivisor that returns the
-- smallest number (greater than 1) that divides the given number evenly.
--
-- That is, when
-- smallestDivisor n ==> k
-- we have
-- n = t*k
-- for some t.
--
-- Ps. your function doesn't need to work for inputs 0 and 1, but
-- remember this in the next exercise!
smallestDivisor :: Integer -> Integer
smallestDivisor = todo
------------------------------------------------------------------------------
-- Ex 18: implement a function isPrime that checks if the given number
-- is a prime number. Use the function smallestDivisor.
--
-- Ps. 0 and 1 are not prime numbers
isPrime :: Integer -> Bool
isPrime = todo
------------------------------------------------------------------------------
-- Ex 19: implement a function biggestPrimeAtMost that returns the
-- biggest prime number that is less than or equal to the given
-- number. Use the function isPrime you just defined.
--
-- Examples:
-- biggestPrimeAtMost 3 ==> 3
-- biggestPrimeAtMost 10 ==> 7
biggestPrimeAtMost :: Integer -> Integer
biggestPrimeAtMost = todo
+1 -68
View File
@@ -28,15 +28,7 @@ tests = [(1,"onetwo",[ex1_one_defined, ex1_one_type
,property ex8_isZero_negative])
,(9,"sumTo",[ex9_sumTo])
,(10,"power",[ex10_power])
,(11,"ilog3",[property ex11_ilog3])
,(12,"binomial",[ex12_binomial])
,(13,"oddFactorial",[ex13_oddFactorial])
,(14,"myGcd",[ex14_myGcd])
,(15,"leftpad",[property ex15_leftpad])
,(16,"countdown",[ex16_countdown])
,(17,"smallestDivisor",[ex17_smallestDivisor_prime, property ex17_smallestDivisor_comp])
,(18,"isPrime",[ex18_isPrime])
,(19,"biggestPrimeAtMost",[ex19_biggestPrimeAtMost])]
,(11,"ilog3",[property ex11_ilog3])]
-- -- -- -- -- --
@@ -107,62 +99,3 @@ ex10_power =
ex11_ilog3 (Blind (Positive n)) =
$(testing [|ilog3 n|]) (?== 1 + floor (logBase 3 $ fromIntegral n))
ex12_binomial =
forAllBlind (elements [0..10]) $ \n ->
forAllBlind (elements [0..n]) $ \k ->
$(testing [|binomial n k|]) (?== f n `div` (f k * f (n-k)))
where f n = product [1..n]
ex13_oddFactorial =
forAllBlind (elements [1..15]) $ \n ->
$(testing [|oddFactorial n|]) (?== f n)
where f n = product [1,3..n]
ex14_myGcd =
forAllBlind (elements [1..max]) $ \x ->
forAllBlind (elements [1..max]) $ \y ->
$(testing [|myGcd x y|]) (?== gcd x y)
where max = 10000
ex15_leftpad =
do text <- word
ws <- listOf (return ' ')
let res = ws++text
let len = length res
return $ $(testing [|leftpad text len|]) (?==res)
ex16_countdown =
forAllBlind (elements [1..20]) $ \n ->
$(testing [|countdown n|]) . was $ \v ->
counterexample "should start with \"Ready!\"" (take 6 v ?== "Ready!")
.&&.
counterexample "should end with \"Liftoff!\"" (reverse (take 8 (reverse v)) ?== "Liftoff!")
.&&.
let cnt = init $ init $ intercalate "... " (map show [n,n-1..0])
in counterexample ("should contain the string " ++ show cnt) (isInfixOf cnt v)
primes = go [2..]
where go (x:xs) = x : go (filter (notDivBy x) xs)
notDivBy x y = mod y x /= 0
ex17_smallestDivisor_prime = do
forAllBlind (elements $ take 12 primes) $ \p ->
$(testing [|smallestDivisor p|]) (?== p)
ex17_smallestDivisor_comp = do
k <- (elements . take 10 $ primes)
p <- (elements . take 20 . drop 10 $ primes)
let n = k*p
return $ $(testing [|smallestDivisor n|]) (?== k)
ex18_isPrime =
forAllBlind (elements [0..max]) $ \n ->
$(testing [|isPrime n|]) (?== elem n primes')
where max = 20
primes' = takeWhile (<=max) primes
ex19_biggestPrimeAtMost =
forAllBlind (elements [2..max]) $ \n ->
$(testing [|biggestPrimeAtMost n|]) (?== last (takeWhile (<=n) primes))
where max = 100
+23 -11
View File
@@ -11,7 +11,7 @@
-- * null
module Set2 where
module Set2a where
import Mooc.Todo
@@ -64,13 +64,25 @@ substring :: Int -> Int -> String -> String
substring i j s = todo
------------------------------------------------------------------------------
-- Ex 5: implement the function palindromify that chops a character
-- Ex 5: check if a string is a palindrome. A palindrome is a string
-- that is the same when read backwards.
--
-- Hint! There's a really simple solution to this. Don't overthink it!
--
-- Examples:
-- isPalindrome "" ==> True
-- isPalindrome "ABBA" ==> True
-- isPalindrome "racecar" ==> True
-- isPalindrome "AB" ==> False
isPalindrome :: String -> Bool
isPalindrome str = todo
------------------------------------------------------------------------------
-- Ex 6: implement the function palindromify that chops a character
-- off the front _and_ back of a string until the result is a
-- palindrome.
--
-- Hint: remember the function palindrome :: String -> Bool from the
-- course material!
--
-- Examples:
-- palindromify "ab" ==> ""
-- palindromify "aaay" ==> "aa"
@@ -81,7 +93,7 @@ palindromify :: String -> String
palindromify s = todo
------------------------------------------------------------------------------
-- Ex 6: implement safe integer devision, that is, a function that
-- Ex 7: implement safe integer devision, that is, a function that
-- returns a Just result normally, but Nothing if the divisor is zero.
--
-- Remember that integer division can be done with the div function.
@@ -94,7 +106,7 @@ safeDiv :: Integer -> Integer -> Maybe Integer
safeDiv x y = todo
------------------------------------------------------------------------------
-- Ex 7: implement a function greet that greets a person given a first
-- Ex 8: implement a function greet that greets a person given a first
-- name and possibly a last name. The last name is represented as a
-- Maybe String value.
--
@@ -106,8 +118,8 @@ greet :: String -> Maybe String -> String
greet first last = todo
------------------------------------------------------------------------------
-- Ex 8: safe list indexing. Define a function safeIndex so that
-- safeIndex xs i
-- Ex 9: safe list indexing. Define a function safeIndex so that
-- sefeIndex xs i
-- gets the element at index i in the list xs. If i is not a valid
-- index, Nothing is returned.
--
@@ -122,7 +134,7 @@ safeIndex :: [a] -> Int -> Maybe a
safeIndex xs i = todo
------------------------------------------------------------------------------
-- Ex 9: another variant of safe division. This time you should use
-- Ex 10: another variant of safe division. This time you should use
-- Either to return a string error message.
--
-- Examples:
@@ -133,7 +145,7 @@ eitherDiv :: Integer -> Integer -> Either String Integer
eitherDiv x y = todo
------------------------------------------------------------------------------
-- Ex 10: implement the function addEithers, which combines two values of type
-- Ex 11: implement the function addEithers, which combines two values of type
-- Either String Int into one like this:
--
-- - If both inputs were Ints, sum the Ints
+124
View File
@@ -0,0 +1,124 @@
{-# LANGUAGE TemplateHaskell, ScopedTypeVariables #-}
module Set2aTest where
import Data.Char
import Data.List
import Test.QuickCheck
import Mooc.Th
import Mooc.Test
import Set2a
main = score tests
tests = [(1,"years",[ex1_years])
,(2,"takeFinal",[ex2_takeFinal_1, ex2_takeFinal_2])
,(3,"updateAt",[ex3_updateAt])
,(4,"substring",[ex4_substring])
,(5,"isPalindrome",[ex5_isPalindrome])
,(6,"palindromify",[ex6_palindromify])
,(7,"safeDiv",[ex7_safeDiv_zero, ex7_safeDiv_nonZero])
,(8,"greet",[ex8_greet_nothing, ex8_greet_just])
,(9,"safeIndex",[ex9_safeIndex_ok, ex9_safeIndex_fail])
,(10,"eitherDiv",[ex10_eitherDiv_zero, ex10_eitherDiv_nonZero])
,(11,"addEithers",[ex11_addEithers_both, ex11_addEithers_one, ex11_addEithers_neither])
]
-- -- -- -- -- --
ex1_years =
let origLength = length in
let length = origLength :: [a] -> Int in
conjoin [$(testing' [|length years|]) (?== 3)
,$(testing' [|head years|]) (?== 1982)
,$(testing' [|years !! 1|]) (?== 2004)
,$(testing' [|last years|]) (?== 2020)]
letter = choose ('a','z')
word = listOf1 letter
ex2_takeFinal_1 = property $ do
n <- choose (0,20)
k <- choose (0,n)
return $ $(testing [|takeFinal k [0..n]|]) (?== [n-k+1..n])
ex2_takeFinal_2 = property $ do
n <- choose (0,20)
k <- choose (0,n)
let inp = reverse [0..n]
return $ $(testing [|takeFinal k inp|]) (?== reverse [0..k-1])
ex3_updateAt = property $ do
ws <- listOf1 word
i <- choose (0,length ws - 1)
j <- choose (0,length ws - 1)
w <- word
return . $(testing [|updateAt i w ws|]) $ \v ->
conjoin [counterexample ("index "++show i) $ v!!i ?== w
,counterexample ("index "++show j) $ i/=j ==> v!!j ?== ws!!j]
ex4_substring = forAll_ $ \(prefix :: String, target, suffix) ->
$(testing [|substring (length prefix) (length prefix + length target) (prefix ++ target ++ suffix)|]) (?==target)
ex5_isPalindrome = forAllBlind word $ \w1 ->
forAllBlind (word `suchThat` (/=reverse w1) `suchThat` ((>1).length)) $ \w2 ->
conjoin [$(testing [|isPalindrome (w1 ++ w2)|]) (?==False)
,$(testing [|isPalindrome (w1 ++ reverse w1)|]) (?==True)
,$(testing [|isPalindrome (w1 ++ w2 ++ reverse w1)|]) (?==False)
,$(testing [|isPalindrome (init w1 ++ reverse w1)|]) (?==True)]
ex6_palindromify = property $ do
beef <- word
center <- sublistOf "z"
bun <- fmap nub word `suchThat` \w -> length w > 1
let answer = beef ++ center ++ reverse beef
input = bun ++ answer ++ bun
return $ $(testing [|palindromify input|]) (?== answer)
ex7_safeDiv_zero = forAll_ $ \x ->
$(testing [|safeDiv x 0|]) (?== Nothing)
ex7_safeDiv_nonZero = forAll_ $ \(x,y) ->
(y/=0) ==> $(testing [|safeDiv x y|]) (?== Just (div x y))
ex8_greet_nothing = forAllBlind word $ \first ->
$(testing [|greet first (Nothing :: Maybe String)|]) (?==("Hello, "++first++"!"))
ex8_greet_just = forAllBlind word $ \first ->
forAllBlind word $ \last ->
$(testing [|greet first (Just last)|]) (?==("Hello, "++first++" "++last++"!"))
ex9_safeIndex_ok = property $ do
ws <- listOf1 word
i <- choose (0,length ws - 1)
return $ $(testing [|safeIndex ws i|]) (?==Just (ws!!i))
ex9_safeIndex_fail = property $ do
is <- listOf1 (choose (10,20::Int))
i <- choose (0,10)
let neg = negate i - 1
let over = length is + i
return $ conjoin [$(testing [|safeIndex is neg|]) (?==Nothing)
,$(testing [|safeIndex is over|]) (?==Nothing)]
ex10_eitherDiv_zero = forAll_ $ \x ->
$(testing [|eitherDiv x 0|]) (?== Left (show x++"/0"))
ex10_eitherDiv_nonZero = forAll_ $ \(x,y) ->
(y/=0) ==> $(testing [|eitherDiv x y|]) (?== Right (div x y))
ex11_addEithers_both = forAll_ $ \(x,y) ->
$(testing [|addEithers (Right x :: Either String Int) (Right y :: Either String Int)|]) (?== Right (x+y))
ex11_addEithers_one = property $ do
err <- fmap Left arbitrary
int <- fmap Right arbitrary
~[x,y] <- shuffle [err,int]
return $ $(testing [|addEithers x y|]) (?==err)
ex11_addEithers_neither = forAll_ $ \(a,b) ->
$(testing [|addEithers (Left a :: Either String Int) (Left b :: Either String Int)|]) (?==Left a)
+131
View File
@@ -0,0 +1,131 @@
module Set2b where
import Mooc.Todo
-- Some imports you'll need. Don't add other imports :)
import Data.List
------------------------------------------------------------------------------
-- Ex 1: compute binomial coefficients using recursion. Binomial
-- coefficients are defined by the following equations:
--
-- B(n,k) = B(n-1,k) + B(n-1,k-1)
-- B(n,0) = 1
-- B(0,k) = 0, when k>0
--
-- Hint! pattern matching is your friend.
binomial :: Integer -> Integer -> Integer
binomial = todo
------------------------------------------------------------------------------
-- Ex 2: implement the odd factorial function. Odd factorial is like
-- factorial, but it only multiplies odd numbers.
--
-- Examples:
-- oddFactorial 7 ==> 7*5*3*1 ==> 105
-- oddFactorial 6 ==> 5*3*1 ==> 15
oddFactorial :: Integer -> Integer
oddFactorial = todo
------------------------------------------------------------------------------
-- Ex 3: implement the Euclidean Algorithm for finding the greatest
-- common divisor:
--
-- Given two numbers, a and b,
-- * if one is zero, return the other number
-- * if not, subtract the smaller number from the larger one
-- * replace the larger number with this new number
-- * repeat
--
-- For example,
-- myGcd 9 12 ==> 3
-- In this case, the algorithm proceeds like this
--
-- a b
--
-- 9 12
-- 9 (12-9)
-- 9 3
-- (9-3) 3
-- 6 3
-- (6-3) 3
-- 3 3
-- (3-3) 3
-- 0 3
--
-- Background reading:
-- * https://en.wikipedia.org/wiki/Euclidean_algorithm
myGcd :: Integer -> Integer -> Integer
myGcd = todo
------------------------------------------------------------------------------
-- Ex 4: Implement the function leftpad which adds space characters
-- to the start of the string until it is long enough.
--
-- Examples:
-- leftpad "foo" 5 ==> " foo"
-- leftpad "13" 3 ==> " 13"
-- leftpad "xxxxx" 3 ==> "xxxxx"
--
-- Tips:
-- * you can combine strings with the ++ operator.
-- * you can compute the length of a string with the length function
leftpad :: String -> Int -> String
leftpad = todo
------------------------------------------------------------------------------
-- Ex 5: let's make a countdown for a rocket! Given a number, you
-- should produce a string that says "Ready!", counts down from the
-- number, and then says "Liftoff!".
--
-- For example,
-- countdown 4 ==> "Ready! 4... 3... 2... 1... Liftoff!"
--
-- Hints:
-- * you can combine strings with the ++ operator
-- * you can use the show function to convert a number into a string
-- * you'll probably need a recursive helper function
countdown :: Integer -> String
countdown = todo
------------------------------------------------------------------------------
-- Ex 6: implement the function smallestDivisor that returns the
-- smallest number (greater than 1) that divides the given number evenly.
--
-- That is, when
-- smallestDivisor n ==> k
-- we have
-- n = t*k
-- for some t.
--
-- Ps. your function doesn't need to work for inputs 0 and 1, but
-- remember this in the next exercise!
smallestDivisor :: Integer -> Integer
smallestDivisor = todo
------------------------------------------------------------------------------
-- Ex 7: implement a function isPrime that checks if the given number
-- is a prime number. Use the function smallestDivisor.
--
-- Ps. 0 and 1 are not prime numbers
isPrime :: Integer -> Bool
isPrime = todo
------------------------------------------------------------------------------
-- Ex 8: implement a function biggestPrimeAtMost that returns the
-- biggest prime number that is less than or equal to the given
-- number. Use the function isPrime you just defined.
--
-- Examples:
-- biggestPrimeAtMost 3 ==> 3
-- biggestPrimeAtMost 10 ==> 7
biggestPrimeAtMost :: Integer -> Integer
biggestPrimeAtMost = todo
+83
View File
@@ -0,0 +1,83 @@
{-# LANGUAGE TemplateHaskell #-}
module Set2bTest where
import Data.List
import Test.QuickCheck
import Mooc.Th
import Mooc.Test
import Set2b
main = score tests
tests = [(1,"binomial",[ex1_binomial])
,(2,"oddFactorial",[ex2_oddFactorial])
,(3,"myGcd",[ex3_myGcd])
,(4,"leftpad",[property ex4_leftpad])
,(5,"countdown",[ex5_countdown])
,(6,"smallestDivisor",[ex6_smallestDivisor_prime, property ex6_smallestDivisor_comp])
,(7,"isPrime",[ex7_isPrime])
,(8,"biggestPrimeAtMost",[ex8_biggestPrimeAtMost])]
ex1_binomial =
forAllBlind (elements [0..10]) $ \n ->
forAllBlind (elements [0..n]) $ \k ->
$(testing [|binomial n k|]) (?== f n `div` (f k * f (n-k)))
where f n = product [1..n]
ex2_oddFactorial =
forAllBlind (elements [1..15]) $ \n ->
$(testing [|oddFactorial n|]) (?== f n)
where f n = product [1,3..n]
ex3_myGcd =
forAllBlind (elements [1..max]) $ \x ->
forAllBlind (elements [1..max]) $ \y ->
$(testing [|myGcd x y|]) (?== gcd x y)
where max = 10000
word = listOf1 (choose ('a','z'))
ex4_leftpad =
do text <- word
ws <- listOf (return ' ')
let res = ws++text
let len = length res
return $ $(testing [|leftpad text len|]) (?==res)
ex5_countdown =
forAllBlind (elements [1..20]) $ \n ->
$(testing [|countdown n|]) . was $ \v ->
counterexample "should start with \"Ready!\"" (take 6 v ?== "Ready!")
.&&.
counterexample "should end with \"Liftoff!\"" (reverse (take 8 (reverse v)) ?== "Liftoff!")
.&&.
let cnt = init $ init $ intercalate "... " (map show [n,n-1..0])
in counterexample ("should contain the string " ++ show cnt) (isInfixOf cnt v)
primes = go [2..]
where go (x:xs) = x : go (filter (notDivBy x) xs)
notDivBy x y = mod y x /= 0
ex6_smallestDivisor_prime = do
forAllBlind (elements $ take 12 primes) $ \p ->
$(testing [|smallestDivisor p|]) (?== p)
ex6_smallestDivisor_comp = do
k <- (elements . take 10 $ primes)
p <- (elements . take 20 . drop 10 $ primes)
let n = k*p
return $ $(testing [|smallestDivisor n|]) (?== k)
ex7_isPrime =
forAllBlind (elements [0..max]) $ \n ->
$(testing [|isPrime n|]) (?== elem n primes')
where max = 20
primes' = takeWhile (<=max) primes
ex8_biggestPrimeAtMost =
forAllBlind (elements [2..max]) $ \n ->
$(testing [|biggestPrimeAtMost n|]) (?== last (takeWhile (<=n) primes))
where max = 100
+1484 -1427
View File
File diff suppressed because it is too large Load Diff