I am modeling an infectious disease with a fixed incubation period. I chose a queue to keep track of when people become symptomatic. I imagined using some operations like so:
queue' = enqueue numberOfPeopleWhoJustGotInfected queue
(numberOfPeopleWhoJustBecameSymptomatic,queue'') = dequeue queue
Since enqueue and dequeue would always be atomic and the queue length would be fixed, I wrote a fixed length queue data structure. endequeue models the atomic enqueue & dequeue sequence. I hope that it will be more efficient than the library Data.Queue.
I'd like feedback on:
Efficiency. Can it be improved?
Typeclasses. Did I use them correctly? Am I missing others which are applicable?
Test Cases. Are there other logical properties I could add?
Haskell Code:
module FixedLengthQueue
( endequeue
, fromList
, length
, peek
) where
import Prelude hiding (foldl,foldl1,foldr,foldr1,length)
import Control.Applicative ((<$>),(<*>))
import Data.Array
import Data.Foldable
import Data.Functor
import Data.Maybe (fromJust)
import Data.Monoid
type Pointer = Int
type Length = Int
data FixedLengthQueue a = FLQ Length Pointer (Array Int a)
deriving (Eq) -- I need to manually define, see tests
instance (Show a) => Show (FixedLengthQueue a) where
show = ("fromList "++) . show . toList
instance Functor FixedLengthQueue where
fmap f = fromJust . fromList . fmap f . toList
instance Foldable FixedLengthQueue where
foldMap f (FLQ n p arr)
= f (arr ! p) <> g (succMod p n)
where
g z
| z /= p = f (arr ! z) <> g (succMod z n)
| otherwise = mempty
fromList :: [a] -> Maybe (FixedLengthQueue a)
fromList [] = Nothing -- Nonsensical empty fixed length queue
fromList xs
= Just
. FLQ len 0
. array (0, len-1)
$ zip [0..len-1] xs
where
len = foldl' inc 0 xs
inc = flip (const succ)
length :: FixedLengthQueue a -> Int
length (FLQ n _ _) = n
peek :: FixedLengthQueue a -> a
peek (FLQ _ p arr) = arr ! p
endequeue :: a -> FixedLengthQueue a -> (a,FixedLengthQueue a)
endequeue e (FLQ n p arr)
= ( arr ! p
, FLQ n (succMod p n) $ arr // [(p,e)]
)
succMod n m
| n' >= m = n' - m
| otherwise = n'
where n' = succ n
Basic Tests:
module Main where
import Prelude hiding (mapM_)
import Data.Foldable
import FixedLengthQueue
import Test.QuickCheck
import Safe
main :: IO ()
main =
mapM_ quickCheck
([ \x -> fmap peek (fromList x) == headMay x
, \x -> fmap (fst . endequeue undefined) (fromList x) == headMay x
, \x -> fmap (init . toList . snd . endequeue undefined) (fromList x) == tailMay x
, \x -> let mxs = (replicate 2) <$> (headMay x) >>= fromList
in (snd . (endequeue <$> peek <*> id) <$> mxs) == mxs
] :: [[Int] -> Bool])