5
Declarative Programming Answers to workshop exercises set 6. QUESTION 1 Write a function fibs :: Int -> [Integer] which returns a list containing the first n numbers in the Fibonacci sequence: [0,1,1,2,3,5,8,...], where the third and subsequent numbers are the sum of the two preceeding numbers (0+1=1, 1+1=2, 1+2=3, 2+3=5, etc). We use Integer rather than Int because the numbers grow exponentially and therefore overflow native Ints quite quickly. Is the algorithmic complexity of your solution acceptable? ANSWER Here is one possible solution. It uses a helper function which generates a list of Fibonacci numbers given the previous two Fibonacci numbers and the required list length. This makes it efficient; its complexity is O(n). Naive codings can do *lots* of repeated computation and can have exponential complexity. >fibs :: Int -> [Integer] >fibs 0 = [] >fibs 1 = [0] >fibs n | n > 1 = 0:1:fibs1 0 1 (n-2) >fibs1 fpp fp 0 = [] >fibs1 fpp fp (n+1) = (fpp+fp) : fibs1 fp (fpp+fp) n QUESTION 2 If we do pairwise addition of the elements of the Fibonacci sequence and its tail, we get the tail of the tail of the sequence: 0 1 1 2 3 5 8 ... fibs + 1 1 2 3 5 8 ... tail fibs = 1 2 3 5 8 ... tail (tail fibs) Use this property to write a definition of allfibs :: [Integer] which is the (infinite) Fibonacci sequence (Hint: the zipWith Prelude function is useful). Define fibs in terms of allfibs. How efficient is this definition of fibs compared to your previous one? ANSWER >allfibs :: [Integer] >allfibs = 0 : 1 : (zipWith (+) allfibs (tail allfibs)) >fibs' n = take n allfibs The two fibs definitions are likely to have quite similar efficiency: both are O(n), though the second will have a higher constant factor. One subtlety (not covered in lectures) is that allfibs is what is known as a "constant applicative form", or CAF. These are constants defined at the top level of the program (not inside a let or where clause). Haskell guarantees that CAFs are never evaluated more than once. Thus the first time we need a list of Fibonacci numbers, allfibs will compute what is needed and the result will be saved. If we need another shorter list of Fibonacci numbers later, no further (re)computation of allfibs will be required. If we need a longer list, the lazy evaluation will continue from where it stopped previously. This can all save time. However, it may use more space. If allfibs was defined inside a let or where clause (which could be e.g. in the definition

Workshop 06

Embed Size (px)

Citation preview

7/30/2019 Workshop 06

http://slidepdf.com/reader/full/workshop-06 1/5

Declarative Programming

Answers to workshop exercises set 6.

QUESTION 1Write a function fibs :: Int -> [Integer] which returns a list containingthe first n numbers in the Fibonacci sequence: [0,1,1,2,3,5,8,...], wherethe third and subsequent numbers are the sum of the two preceeding numbers(0+1=1, 1+1=2, 1+2=3, 2+3=5, etc). We use Integer rather than Int becausethe numbers grow exponentially and therefore overflow native Ints quitequickly. Is the algorithmic complexity of your solution acceptable?

ANSWERHere is one possible solution. It uses a helper function which generatesa list of Fibonacci numbers given the previous two Fibonacci numbers andthe required list length. This makes it efficient; its complexity is O(n).Naive codings can do *lots* of repeated computation and can have exponentialcomplexity.

>fibs :: Int -> [Integer]>fibs 0 = []>fibs 1 = [0]>fibs n | n > 1 = 0:1:fibs1 0 1 (n-2)

>fibs1 fpp fp 0 = []>fibs1 fpp fp (n+1) = (fpp+fp) : fibs1 fp (fpp+fp) n

QUESTION 2If we do pairwise addition of the elements of the Fibonacci sequenceand its tail, we get the tail of the tail of the sequence:

0 1 1 2 3 5 8 ... fibs+ 1 1 2 3 5 8 ... tail fibs= 1 2 3 5 8 ... tail (tail fibs)

Use this property to write a definition of allfibs :: [Integer] which isthe (infinite) Fibonacci sequence (Hint: the zipWith Prelude function

is useful). Define fibs in terms of allfibs. How efficient is this definitionof fibs compared to your previous one?

ANSWER

>allfibs :: [Integer]>allfibs = 0 : 1 : (zipWith (+) allfibs (tail allfibs))>fibs' n = take n allfibs

The two fibs definitions are likely to have quite similar efficiency:both are O(n), though the second will have a higher constant factor.

One subtlety (not covered in lectures) is that allfibs is what is known

as a "constant applicative form", or CAF. These are constants definedat the top level of the program (not inside a let or where clause).Haskell guarantees that CAFs are never evaluated more than once.Thus the first time we need a list of Fibonacci numbers, allfibswill compute what is needed and the result will be saved.If we need another shorter list of Fibonacci numbers later, no further(re)computation of allfibs will be required. If we need a longer list,the lazy evaluation will continue from where it stopped previously.This can all save time. However, it may use more space. If allfibs wasdefined inside a let or where clause (which could be e.g. in the definition

7/30/2019 Workshop 06

http://slidepdf.com/reader/full/workshop-06 2/5

of fibs'), it would be recomputed in each different call to fibs', and thespace it used would be reclaimed after each time.

QUESTION 3Consider the bottom-up merge sort implementation from workshop 2.

>mergesort xs = repeat_merge_all (merge_consec (to_single_els xs))>>to_single_els [] = []>to_single_els (x:xs) = [x] : to_single_els xs>>merge [] ys = ys>merge (x:xs) [] = x:xs>merge (x:xs) (y:ys)> | x <= y = x : merge xs (y:ys)> | x > y = y : merge (x:xs) ys>>merge_consec [] = []>merge_consec [xs] = [xs]>merge_consec (xs1:xs2:xss) = (merge xs1 xs2) : merge_consec xss>>repeat_merge_all [] = []>repeat_merge_all [xs] = xs>repeat_merge_all xss@(_:_:_) = repeat_merge_all (merge_consec xss)

With list xs of length n, what is the maximum additional space that isneeded at any one time, assuming strict evaluation, for evaluatingmerge_consec (to_single_els xs)? What if lazy evaluation is used instead?

What is the maximum additional space is needed at any one time, assumingstrict evaluation, for evaluating mergesort xs? Can we do significantlybetter than this?

ANSWERStrict evaluation of merge_consec (to_single_els xs) proceeds as follows(we make n a power of two for simplicity):

[4,1,6,2,8,7,3,5] xs-> [[4],[1],[6],[2],[8],[7],[3],[5]] to_single_els-> [[1,4],[2,6],[7,8],[3,5]] merge_consec

The result of to_single_els has n additional cons cells (2n total), andthis is completely constructed before merge_consec is evaluated.

With lazy evaluation, the merge_consec and to_single_els evaluationsare interleaved: [1,4] is constructed as soon as [4] and [1] havebeen constructed (and the cons cells for [4] and [1] can be reclaimed).The maximum extra space needed is n/2 cons cells (plus a constant becausethe suspensions/thunks take a bit of space; the strict version probablyhas a smaller constant overhead).

For mergesort the evaluation proceeds on from the merge_consec result asfollows:

-> [[1,2,4,6],[3,5,7,8]]-> [[1,2,3,4,5,6,7,8]]-> [1,2,3,4,5,6,7,8]

The maximum space is needed when the result of to_single_els is computed.Determining what happens with Haskell is very tricky. However, there are

7/30/2019 Workshop 06

http://slidepdf.com/reader/full/workshop-06 3/5

7/30/2019 Workshop 06

http://slidepdf.com/reader/full/workshop-06 4/5

> indent_mtree i (Mnode val children) = do> putStrLn $ (replicate i ' ') ++ (show val)> foldl (>>) (return ()) (map (indent_mtree (i+1)) children)

>type Line = String>>print_mtree' :: Show a => Mtree a -> IO ()>print_mtree' t => let> toLines :: Show a => Mtree a -> [Line]> toLines (Mnode val cs) = show val : map (' ':) (concatMap (toLines) cs)> in foldl (\acc str -> acc >> (putStrLn str)) (return ()) (toLines t)>>-- A clearer version>print_mtree2 :: Show a => Mtree a -> IO ()>print_mtree2 t => let> toLines :: Show a => Mtree a -> [IO ()]> toLines (Mnode val cs) => print val : map (putChar ' ' >>) (concatMap (toLines) cs)> in foldl1 (>>) (toLines t)

QUESTION 6Lectures have given the definitions of two higher order functions in the

Haskell prelude, filter and map:

filter :: (a -> Bool) -> [a] -> [a]map :: (a -> b) -> [a] -> [b]

Filter returns those elements of its argument list for which the given functionreturns True, while map applies the given function to every element of thegiven list.

Suppose you have a list of numbers, and you want to (a) filter out all thenegative numbers, and (b) apply the sqrt function to all the remainingintegers.

(a) Write code to accomplish this task using filter and map.(b) Write code to accomplish this task that does only one list traversal,without any higher order functions.(c) Transform a to b.

ANSWERFilter + map version. The inner call to filter traverses xs andproduces an intermediate list, which is traversed by map.

>sqrt_pos1 :: (Ord a, Floating a) => [a] -> [a]>sqrt_pos1 ns = map sqrt (filter (>=0) ns)

Single-traversal version - we just do more complex processing for each

element, combining the test for >=0 and sqrt.

>sqrt_pos2 :: (Ord a, Floating a) => [a] -> [a]>sqrt_pos2 [] = []>sqrt_pos2 (x:xs) => if x >= 0 then sqrt x : sqrt_pos2 xs> else sqrt_pos2 xs

The definitions from lectures are

7/30/2019 Workshop 06

http://slidepdf.com/reader/full/workshop-06 5/5

filter :: (a -> Bool) -> [a] -> [a]filter _ [] = []filter f (x:xs) =

if f x == True then x:fxs else fxswhere

fxs = filter f xs

map :: (a -> b) -> [a] -> [b]map _ [] = []map f (x:xs) = (f x):(map f xs)

The definition of sqrt_pos ns is map sqrt (filter (>=0) ns). We can usea form of structural induction on the list ns and use the definitions offilter and map above to simplify instances of this expression, as follows:

For ns=[] we havemap sqrt (filter (>=0) [])

= map sqrt []= []

Thus we can derive the equation

sqrt_pos [] = []

For ns = (x:xs) we havemap sqrt (filter (>=0) (x:xs))= map sqrt (if (>=0) x == True then x:fxs else fxs)

wherefxs = filter (>=0) xs

= map sqrt (if x >= 0 then x:filter (>=0) xs else filter (>=0) xs)= if x >= 0 then map sqrt (x:filter (>=0) xs)

else map sqrt (filter (>=0) xs)= if x >= 0 then sqrt x : map sqrt (filter (>=0) xs)

else map sqrt (filter (>=0) xs)= if x >= 0 then sqrt x : sqrt_pos xs

else sqrt_pos xs

Thus we can derive the equation

sqrt_pos (x:xs) =if x >= 0 then sqrt x : sqrt_pos xselse sqrt_pos xs