-- | Working with List
-- Examples to illustrate pattern matching, recursion and testing for lists
-- Functional Programming course 2017.
-- Thomas Hallgren
{-
This started out as a skeleton, the definitions were be filled in
during the lecture.
-}
import Prelude hiding ((++),null,length,sum,reverse,take,drop,splitAt,zip,unzip)
import qualified Prelude
import Test.QuickCheck
--------------------------------------------------------------------------------
null :: [a] -> Bool
null [] = True
null _ = False
length :: [a] -> Int
length [] = 0
length (x:xs) = length xs + 1
sum :: Num a => [a] -> a
sum [] = 0
sum (n:ns) = n + sum ns
(++) :: [a] -> [a] -> [a]
[] ++ ys = ys
(x:xs) ++ ys = x : (xs++ys)
cons :: a -> [a] -> [a]
cons x xs = x:xs -- O(1)
snoc :: [a] -> a -> [a]
snoc xs x = xs ++ [x] -- O(n)
reverse :: [a] -> [a]
reverse [] = []
reverse (x:xs) = reverse xs ++ [x] -- O(n^2)
-- complexity? how to make it more efficient?
-- | Take the first n elements of a list
take :: Int -> [a] -> [a]
take 0 xs = []
take _ [] = []
take n (x:xs) = x:take (n-1) xs
prop_take n xs = length (take n' xs) == min n' (length xs)
where
n' = abs n
-- | Discard the first n elements of a list
drop :: Int -> [a] -> [a]
drop 0 xs = xs
drop _ [] = []
drop n (_:xs) = drop (n-1) xs
prop_take_drop :: Int -> [Int] -> Bool
prop_take_drop n xs = take n xs ++ drop n xs == xs
nonprop_take_drop :: Int -> [Int] -> Bool
nonprop_take_drop n xs = drop n xs ++ take n xs == xs
-- | "Quicksort"
qsort :: Ord a => [a] -> [a]
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort bigger
where
smaller = [y | y<-xs, y<=x]
bigger = [y | y<-xs, y>x]
-- | insert a new element at the right position in a sorted list
insert :: Ord a => a -> [a] -> [a]
insert x [] = [x]
insert x (x':xs) | x [Int] -> Property
prop_insert x xs = isSorted xs ==> isSorted (insert x xs)
-- quickCheck says "Gave up!" because most of the random lists it generates
-- are not sorted. We will return to this issue in a future lecture.
-- | Insertion sort (sort a list by using insert)
isort :: Ord a => [a] -> [a]
isort [] = []
isort (x:xs) = insert x (isort xs)
prop_qsort :: [Int] -> Bool
prop_qsort xs = qsort xs == isort xs
--------------------------------------------------------------------------------
-- * Filled in after the lecture
-- | splitAt n xs = (take n xs,drop n xs)
splitAt :: Int -> [a] -> ([a],[a])
splitAt 0 xs = ([],xs)
splitAt n [] = ([],[])
splitAt n (x:xs) = (x:ys,zs)
where
(ys,zs) = splitAt (n-1) xs
-- When calling a function that returns a pair, it is often useful to use
-- pattern matching to access the two parts of the pair
-- | Combine a pair of list into a list of pairs
zip :: [a] -> [b] -> [(a,b)]
zip (x:xs) (y:ys) = (x,y):zip xs ys
zip _ _ = []
-- We have two lists that both can be empty or non-empty, so there are
-- 4 combinations to consider. By putting the case when both lists are
-- non-empty first, all the other cases (one or both lists are empty) can
-- be handled with one equation (since zip stops and returns the empty list
-- when it reaches the end of the shorter of the two lists).
prop_length_zip xs ys = length (zip xs ys) == min (length xs) (length ys)
-- | Split a list of pairs into a pair of lists
unzip :: [(a,b)] -> ([a],[b])
unzip [] = ([],[])
unzip ((x,y):xys) = (x:xs,y:ys)
where
(xs,ys) = unzip xys
prop_zip_unzip :: [(Int,Int)] -> Bool
prop_zip_unzip xys = zip xs ys == xys
where
(xs,ys) = unzip xys
prop_unzip_zip :: [Int] -> [Int] -> Bool
prop_unzip_zip xs ys = unzip (zip xs ys) == (take n xs,take n ys)
where
n = min (length xs) (length ys)