------------------------------------------------------------------------------ -- -- Haskell: The Craft of Functional Programming -- Simon Thompson -- (c) Addison-Wesley, 2011. -- -- Solutions16 -- ------------------------------------------------------------------------------ module Solutions16 where import Tree import UseTree -- The type Var. type Var = Char -- -- Solution 16.1 -- -- The implementation here has names suffixed with "S" type StoreS = [(Var, Integer)] initialS :: StoreS initialS = [] -- Note that in case the variable isn't bound, returns 0. valueS :: StoreS -> Var -> Integer valueS [] v = 0 valueS ((w,n):sto) v | w Var -> Integer -> StoreS updateS [] v n = [(v,n)] updateS ((w,m):sto) v n | w Var -> Maybe Integer valueS' [] _ = Nothing valueS' ((w,n):sto) v | w Var -> Bool hasVal [] _ = False hasVal ((w,n):sto) v | w Priority -> PriQ a -> PriQ a addPQ x p [] = [(p,[x])] addPQ x p qs@((q,ys):rest) | p>q = (p,[x]):qs | p==q = (q,ys++[x]):rest | p (Maybe a, PriQ a) remPQ q | isEmptyPQ q = (Nothing, q) | otherwise = (Nothing, []) -- -- Solution 16.13 -- -- Once accumulated the scores of each letter can put into a priority queue; this -- could help in tree building. -- -- Solution 16.14 -- -- Can define isNil from isNode, and vice versa -- Can define isNil from minTree: it will return Nothing iff tree is Nil. -- Given any (finite) tree value t where elements are in Ord a can define nil thus: {- makeNil :: Ord a => Tree a -> Tree a makeNil t | res == Nothing = t | otherwise = makeNil (delete min t) where res = minTree t Just min = res -} -- -- Solution 16.15 -- -- Depends a bit on what the database is to do, but need lookups and updates, as -- well as initial value. Can define e.g. number of loans from the interface functions. -- -- Solution 16.16 -- -- Two sorts of interface here -- Using an existing index: take word to page range. Take page to all entries, perhaps? -- Building an index: take a text to an index. -- -- Solution 16.17 -- -- QueueState: -- can define queueEmpty from queueLength -- ServerState: -- can define simulationStep using serverStep, shortestQueue and addToQueue -- is it enough to have simulationStep, serverStart and serverSize? certainly -- it is to actually run the simulation step by step. -- -- Solution 16.18 -- -- In the light of the previous answer, it would be enough to include -- simulationStep, serverStart and serverSize in an interface, and to have -- the "next queue" as an element of the state, but not directly accessible -- from the interface: -- type NextState = Int -- newtype ServerState = SS ([QueueState],NextState) -- Alternatively if more is to be exposed, then need a function to reveal -- the current value of the "next state -- -- Solutions 16.19,20 -- -- Standard calculations. -- -- Solution 16.21 -- -- QueueState: running the queue to completion on a list of n inputs -- should produce a list of n outputs, processed in order in which they -- arrived. In order to do this need to define a number of auxiliary -- functions, the major one being a funciton to run the queue to -- completion on an input list. -- Need to take account here of the arrival times: can't expect to -- process something (at least) until it has arrived. Halt processing when -- there are no more input messages to process and the queue itself is -- empty. -- -- Solution 16.22 -- -- Need to know how many queues there are, and can't tell this from an -- arbitrary function of type (Int -> QueueState); need to pair this function -- with an Int telling you the number of queues. Once that's there, replace -- accessing a queue by list indexing and instead just use function application. -- e.g. in the definition of addToQueue don't have to split the list up, -- operate on one element and then form another list; instead simply change the -- value of the function on argument n. -- -- Solution 16.23 -- -- See solution 16.17 above. -- -- Solutions 16.24-25 -- -- See solution 16.18 above. -- -- Solution 16.26 -- -- A different version of the round robin implemetation -- will keep a, ordered list of (Int,Queue) pairs and ensure that -- the current/next queue is always at the head. -- -- Solution 16.27,28 -- -- There are two approaches here: could test the accessor, selector -- and discriminator functions, but we should be able to assume these -- are ok. Here we test for the top level properties of how elementhood -- interacts with insertion and deletion: prop_add_tree :: Int -> Int -> Tree Int -> Bool prop_add_tree n m t = elemT n t == elemT n (insTree m t) || n==m prop_delete_tree :: Int -> Int -> Tree Int -> Bool prop_delete_tree n m t = elemT n t == elemT n (delete m t) || n==m -- Could also check that the minTree function indeed picks the minimum -- value in the tree by comparing it with the nth value in the tree: prop_min_tree :: Integer -> Tree Int -> Bool prop_min_tree i t = let Just min = minTree t in min <= indexT i t || isNil t -- || "i not valid" -- would be easier for this to be defined if indexT returned a Maybe a -- indicating whether or not the index is in range: exercise. -- -- Solution 16.29 -- {- successor :: Ord a => a -> Tree a -> Maybe a successor v Nil = Nothing successor v (Node x t1 t2) | x<=v = successor v t2 | otherwise = case maxT t1 of Nothing -> x Just y -> if y>v then successor v t1 else x maxT :: Ord a => Tree a -> Maybe a maxT Nil = Nothing maxT (Node x _ Nil) = Just x maxT (Node x _ t2) = maxT t2 -} -- -- Solution 16.30 -- -- Stree is defined on p398. -- The paradigm for the solution is given on p398 -- where it is shown that the new field gives the value it -- should, while the other functions need to maintain that -- value. -- -- Solution 16.31 -- -- Built on the model of Stree: need to make sure that the -- functions manipulate the "cached" values appropriately. -- This is not caching the size, incidentally. data MMtree a = NilMM | NodeMM a a a (MMtree a) (MMtree a) insTreeMM :: Ord a => a -> MMtree a -> MMtree a insTreeMM val NilMM = NodeMM val val val NilMM NilMM insTreeMM val (NodeMM x minV maxV t1 t2) | val<=x = NodeMM x newMin maxV newT1 t2 | val>x = NodeMM x minV newMax t1 newT2 where newMin = min val minV newMax = max val maxV newT1 = insTreeMM val t1 newT2 = insTreeMM val t2 -- Note that because of lazy evaluation (Chapter 17) in each -- case will only compute one of newMin / newMax and newT1 / newT2 -- according to the relation between val and x, the value at the -- root of the tree. -- -- Solution 16.32 -- -- The implentation type could remain the same, but it would be better to -- store an occurrence count with each element (with the assumption -- that no element occurs more than once). -- If the implementation type remains the same, then need to scan for all -- occurrences of a particular element when looking for its occurrence -- count. -- Should extend the interface with an element occurrence function, rather -- than simply checking elementhood. This effectively gives "bags" rather -- than "sets". -- -- Solution 16.33 -- -- Search trees keep the implementation ordered. This is a straightforward -- re-implementation of the application. -- -- Solution 16.34 -- -- This solution takes a different approach. Update the b value by passing in -- an update function, of type (b -> b), to the insertion. This is applied to, e.g. -- add an instance of a word to a list of instances, so might be (++[newOccurrence]) -- in that case. data GenTree a b = GenNil b | GenNode a b (GenTree a b) (GenTree a b) insertGenTree :: Ord a => a -> (b -> b) -> GenTree a b -> GenTree a b insertGenTree x f (GenNil b) = GenNode x (f b) (GenNil b) (GenNil b) insertGenTree x f (GenNode a b t1 t2) | x==a = GenNode a (f b) t1 t2 | xa = GenNode a b t1 (insertGenTree x f t2) -- -- Solution 16.35 -- -- One gives a total ordering, but of less value than a (partial) subset ordering. -- -- Solutions 16.36 - 16.44 SEE SolutionsSet.hs -- -- -- Solutions 16.45 - 16.50 SEE SolutionsRelation.hs --