Craft3e-0.1.0.4: Chapter16/Solutions16.hs
------------------------------------------------------------------------------
--
-- 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<v = valueS sto v
| w==v = n
| otherwise = 0
-- This implementation overwrites the previous binding (assumed
-- to be unique).
updateS :: StoreS -> Var -> Integer -> StoreS
updateS [] v n = [(v,n)]
updateS ((w,m):sto) v n
| w<v = (w,m) : updateS sto v n
| w==v = (v,n) : sto
| otherwise = (v,n) : (w,m) : sto
--
-- Solution 16.2
--
-- For the implemetation in 16.1 it's actual equality. For the non-
-- ordered implementation in the chapter, need only to look at the first
-- values given to variables: variable,value pairs later in the list are
-- ignored.
-- For function types need some indication of what the domain is. The neatest
-- way to do this is to pair the function with a list of variables which
-- gives the set of variables defined. Need just to check equalities on these
-- lists.
--
-- Solution 16.3
--
-- Using a maybe type we can avoid returning the conventional 0 when a
-- variable isn't defined. Instead say this, modifying the solution to
-- 16.1.
valueS' :: StoreS -> Var -> Maybe Integer
valueS' [] _ = Nothing
valueS' ((w,n):sto) v
| w<v = valueS' sto v
| w==v = Just n
| otherwise = Nothing
--
-- Solution 16.4
--
hasVal :: StoreS -> Var -> Bool
hasVal [] _ = False
hasVal ((w,n):sto) v
| w<v = hasVal sto v
| w==v = True
| otherwise = False
--
-- Solution 16.5
--
-- Easy for the functional implementation.
-- For the list implementation would have to define a "catch all" variable, or
-- ass an extr field to a record which is the default value for variables as
-- yet unassigned.
--
-- Solution 16.6
--
-- Need to choose an appropriat esubset of the type signatures of the functions
-- in the case study.
-- An important point is not to include in the API functions which can be defined
-- in terms of other API functions. If that's the case, define them in this was so
-- that if/when the API is redefined these functions don't need to be redefined.
--
-- Solution 16.7
--
-- Standard calculations.
--
-- Solution 16.8
--
-- This exercise helps to make concrete the differences between the three implementations.
--
-- Solution 16.9
--
-- If a queue is not empty, then the the first element to be removed will
-- be the same before and after adding another element to the queue.
-- If a queue is empty and x is added, then x is the first element in the
-- queue.
--
-- Solution 16.10
--
-- Can add elements to either end of the queue and if it is non-empty
-- can remove an element from either end too. Could implement with a single
-- list, or with a pair of lists: the latter should be much more efficient.
-- In both cases it's a matter of extending one of the existing implementations
-- with implmentations of the two new operations.
--
-- Solution 16.11
--
-- Same API as the ordinary queue; need to implement so that don't add a new
-- entry for a value already in the queue.
-- Alternatively could add another operation to the queue to check when an
-- entry is already present, so that can know when it's (not) worth adding
-- an entry.
--
-- Solution 16.12
--
type Priority = Int
-- Store elements in descending order of priority. Within a particular priority
-- store in FIFO form.
-- Note that this is a "concrete" implementation. If it's to be an ADT then
-- need to declare as a newtype with a wrapping constructor.
type PriQ a = [(Int,[a])]
emptyPQ = []
isEmptyPQ [] = True
isEmptyPQ _ = False
addPQ :: a -> 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<q = (q,ys) : addPQ x p rest
remPQ :: PriQ a -> (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
| x<a = GenNode a b (insertGenTree x f t1) t2
| x>a = 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
--