import Test.QuickCheck

{- Exam questions 2017-10 / 2017-12 -}

-- Q1 2017-10 -------------------------
q1 :: [Int]  -> Int
q1 []                    = 0
q1 [x]                   = x
q1 (x:_:xs)              = max x (q1 xs)

-- What does this give:

this = q1 (map abs [-1,-6,-5,7])


-- Q1 2017-12 -------------------------
q1' :: [Int]  -> Int
q1' []                    = 0
q1' [x]                   = 0
q1' (x:y:xs)              = max (q1' xs) y
-- Write out the computation steps for computing the value of

a1' = q1' [1,3,4,2]
sol = [ q1' [1,3,4,2]
      , max (q1' [4,2]) 3
      , max (max (q1' []) 2) 3
      , max (max 0 2) 3
      , 3]

-- Note: this requires approximately 5 steps, so you should write five expressions, each one equivalent to q1' [1,3,4,2]
-- and the last one should be a number (the final value of that expression).


---------------------------------------
-- Q2 2017-10 -------------------------
-- Assume you have:
type WeekNumber = Int
rainfall :: WeekNumber -> Double    -- assume this function exists
rainfall = undefined
-- A week is dry when rainfall < 5

-- Complete the definition of the following function:
dryWeeks :: WeekNumber -> Int
dryWeeks n | n < 1          = 0
           | otherwise      = countDry n + dryWeeks (n-1)

countDry n | rainfall n < 5 = 1
           | otherwise      = 0

-- such that dryWeeks n (when n > 0) gives the number of dry weeks
-- in the range 1 up to n.

-- Your solution MUST be recursive


-- Q2 2017-12 -------------------------

-- A week is considered to be “wet” if the
-- rainfall in that week is more than 10
-- Give a definition of a function

countWetWeeks :: WeekNumber -> Int
countWetWeeks n =  length [w | w <- [1..n], rainfall w > 10 ]

-- such that countWetWeeks n (when n > 0) give the total count of the number of wet weeks in the week range 1 up to n.

-- Your solution must use a list comprehension, but may also use other predefined functions. RECURSION = FAIL 
---------------------------------------


-- Q3 2017-10 -------------------------
{- In this question you should define a data type to represent a bus ticket of a certain kind described below.

A bus ticket is either 
 (i)  a single ticket (a ticket valid for a certain number of minutes) or a   
 (ii) period ticket (a ticket that lasts a number of whole days). 

A single ticket is marked with the date and time when it expires. A period ticket is marked with the date when it expires.

You should use the types Date and Time given below (although the details of their definitions are not important for this question):
-}
type Year   = Int
type Month  = Int
type Day    = Int
type Hour   = Int
type Minute = Int

data Date = Date Year Month Day 
data Time = Time Hour Minute
-- Your task is (only) to complete the following definition:

data BusTicket = Single Date Time | Period Date 
 -- optional: add an Int to each of these for the duration




-- Q3 2017-12 -------------------------
{- In this question you should define a data type to represent a picture made of simple geometric shapes. 

More precisely, a picture is a list of zero or more simple shapes. 

A simple shape is either a circle or a rectangle, 
  and has a colour (either black, red, green, or blue), 
  and a position on the coordinate plane. 

If it is a circle it has a specified diameter (an integer of unspecified units). 

If it is a rectangle it has a height and a width (also integers).

Your task is (only) to complete the following Haskell definition of a data type Picture to represent this:  -}

data Picture = Picture [Shape]
data Shape = Circle Colour Position Integer
           | Rectangle Colour Position Integer Integer

-- together with any other types that you need for this purpose. You may make use the following definitions:

data Colour    = Black | Red | Green | Blue
type Position  = (Int,Int)



---------------------------------------
-- Q4 2017-10 -------------------------
data Expr = X | Num Int | BinOp Op Expr Expr
 deriving (Eq,Show)

data Op = Add | Mul | Subtract
  deriving (Eq,Show)

-- Subtraction is represented but not really needed:
-- 100 - X can be written as 100 + (-1) * X.
ex4 = BinOp Subtract (Num 100) X

ex4' = BinOp Add (Num 100) (BinOp Mul (Num (-1)) X)

prop_removeSub = removeSub ex4 == ex4'

-- Define
removeSub :: Expr -> Expr
removeSub = undefined




-- Q4 2017-12 -------------------------

-- (Given a similar datatype but without variables)
-- Define a function
-- data Expr = Num Int | BinOp Op Expr Expr

largestNum :: Expr -> Int
-- which given an expression, returns the largest number that appears in the expression.
largestNum (Num n) = n
largestNum (BinOp op e1 e2) = largestNum e1 `max` largestNum e2

---------------------------------------
-- Q5 2017-10 -------------------------
-- Define a quickCheck property

prop_take :: Int -> String -> Bool

{- 
which relates the function isPrefixOf with the function 
take :: Int -> [a] -> [a] 
Your definition must use the two arguments as part of the test.
-}
prop_take n s = take n s `isPrefixOf` s


-- Q5 2017-12 -------------------------
check :: Int -> Bool
check n
  | (n >= 10) == True && (n <= 40)  == True = True
  | (n >= 50) == True && (n <= 104) == True = True
  | otherwise                               = False

check' n = (n >= 10) && (n <= 40) || (n >= 50)  && (n <= 104)
  

{- Rewrite this definition so that it does not use the equality operator (==) and also does not use definition by cases (|), and does not use if-then -else. -}


---------------------------------------
-- Q6 2017-10 -------------------------
data Suit = Hearts | Clubs | Diamonds | Spades
  deriving (Eq,Show)
data Rank = Numeric Int | Jack | Queen | King | Ace
  deriving (Eq,Show)
data Card = Card Rank Suit
  deriving (Eq,Show)

isRed, isDiamond :: Suit -> Bool
isRed s             = s == Hearts || s == Diamonds
isDiamond s         = s == Diamonds

isAce, isLow :: Rank -> Bool
isAce r             = r == Ace

isLow (Numeric n)   = n < 5
isLow _             = False

lowDiamonds cs = [Card r s | Card r s <- cs,  isLow r && isDiamond s ]

redAces cs     = [Card r s | Card r s <- cs,  isAce r && isRed s ]

lowRedCards cs = [Card r s | Card r s <- cs,  isLow r && isRed s ]

{- The last three functions in this code contain a lot of “cut-and-paste” repetition. Define a function which generalises these three functions: -}

selectCards :: (Rank -> Bool) -> (Suit -> Bool) -> [Card] -> [Card]
selectCards rankP suitP cs
   = [Card r s | Card r s <- cs, rankP r && suitP s ]




-- so that: 
prop_selectCards cs = lowDiamonds cs == selectCards isLow isDiamond cs
                   && redAces     cs == selectCards isAce isRed cs
                   && lowRedCards cs == selectCards isLow isRed cs
                   
-- Q6 2017-12 -------------------------
-- Give a recursive definition of the following standard function:

filter :: (a -> Bool) -> [a] -> [a]
filter p xs = [ x | x <- xs, p x ]

filter' p [] = []
filter' p (x:xs) | p x       = x: filter' p xs
                 | otherwise = filter' p xs



---------------------------------------
-- Q7 2017-10 -------------------------
-- Give the definition of a QuickCheck generator

quadlist :: Gen [Integer]

{- for lists of Integers, where for every list generated, the length of the list is a multiple of 4. I.e., the generated lists contain 0 numbers, or 4 numbers, or 8 numbers, or 12 numbers, and so on. 

Hint: QuickCheck function vectorOf :: Int -> Gen a -> Gen [a] which generates a list of a specific length, as well as the generator arbitrary may be useful. -}

quadlist = do
         n <- arbitrary
         vectorOf (4 * abs n) arbitrary
         

-- Hints: (i) don’t make the common mistake of trying to apply a function of type Integer -> a to something of type Gen Integer,

-- (ii) don’t forget that you can work with things of type Gen Integer using do-notation.

-- Q7 2017-12 -------------------------

-- Give the definition of a QuickCheck generator

lenlist :: Gen [Int]
{- for lists of numbers, where for every list generated, 
the values of the elements in the list are not negative, 
but never larger than the length of the list. 

So for example, if it generates a list of ten elements, 
then every number in the list is no bigger that ten 
and no smaller than zero. -}

lenlist = do
  n <- arbitrary
  vectorOf n $ choose (0,n)