current commit
131018.2 · update
module QC.Q
  (Q, empty, isEmpty, put, get, rest)
where

import Control.Monad (liftM2)
import Test.QuickCheck
import Text.Printf (printf)
  
-- | A queue with O(1) `put` and amortized O(1) `get` operations.
data Q a = Q { front :: [a], back :: [a] }
           -- corresponds to front ++ reverse back

instance Show a => Show (Q a) where
  show (Q xs ys) = printf "(F: %s, B: %s)" (show xs) (show ys)

empty :: Q a
empty = Q [] []

isEmpty :: Q a -> Bool
isEmpty q = null (front q) && null (back q)

-- | Adds an element to the back of the queue.
put  :: Q a -> a -> Q a
put q a = Q (front q) (a : back q)
-- put q a = Q [] (a : back q) -- nr's error #1

-- | Returns an element from the top of the queue.
-- Calling @get@ on an empty queue results in a runtime error.
get  :: Q a -> a  -- defined on nonempty queue only
get (Q [] [])    = error "get from empty queue"
get (Q [] as)    = get (Q (reverse as) [])
get (Q (a:as) _) = a

-- | Returns the given queue with the top of the queue removed.
-- Calling @rest@ on an empty queue results in a runtime error.
rest :: Q a -> Q a -- defined on nonempty queue only
rest (Q [] [])    = error "rest from empty queue"
rest (Q [] as)    = rest (Q (reverse as) [])
rest (Q (a:as) bs) = Q as bs
-- rest (Q (a:as) _) = Q as [] -- nr's error #2


-- random testing

-- | QTerms represents a series of *valid* combinations of queue constructors.
data QTerms = Empty
            | Rest QTerms
            | Put QTerms
            deriving Show

-- | Enumerates all valid queues up to a certain number of constructor
-- applications.
enumerate :: Int -- ^ Maximum number of queue terms to enumerate.
             -> [QTerms] -- ^ All valid permutations of queue constructors.
enumerate = e [] Empty 0
  where e :: [QTerms] -- ^ Accumulated permutations.
             -> QTerms -- ^ Initial set of queue terms.
             -> Int -- ^ Number of items in the queue.
             -> Int -- ^ Number of queue terms left to generate.
             -> [QTerms]
        e perms _ _ 0 = perms
        e perms ts 0 n = e (next:perms) next 1 (n-1)
          where next = Put ts
        e perms ts i n = e (next:perms) next (i-1) (n-1)
                         ++ e (next':perms) next' (i+1) (n-1)
          where (next, next') = (Rest ts, Put ts)

instance Arbitrary QTerms where
  arbitrary = elements $ enumerate 15

  -- Removing the outer most constructor application always results
  -- in a valid queue.
  shrink Empty = []
  shrink (Rest ts) = [ts]
  shrink (Put ts) = [ts]

-- | Constructs a queue given a list of values and a construction of
-- a queue via `QTerms`.
asQ :: [a] -> QTerms -> Q a
asQ _ Empty = empty
asQ as (Rest ts) = rest $ asQ as ts
asQ (a:as) (Put ts) = put (asQ as ts) a

-- | Convenience function for building a queue of integers.
intQ :: QTerms -> Q Int
intQ = asQ [1..]

-- | Returns the last item in a queue.
lastItem :: Ord a => Q a -> a
lastItem q = if isEmpty (rest q) then get q else lastItem (rest q)

-- | Drains all of the items in the queue and returns the empty queue.
drain :: Ord a => Q a -> Q a
drain q
  | isEmpty q = empty
  | otherwise = drain (rest q)

-- get (put empty x) == x
prop_empty :: Int -> Bool
prop_empty x = get (put empty x) == x

-- isEmpty (rest (put empty x)) == true
prop_rest :: Int -> Bool
prop_rest x = isEmpty (rest (put empty x))

-- lastItem (put queue x) == x
prop_last :: QTerms -> Int -> Bool
prop_last ts x = lastItem (put (intQ ts) x) == x

-- isEmpty (drain q) == true
prop_drainable :: QTerms -> Bool
prop_drainable ts = isEmpty (drain (intQ ts))

tests :: [(String, Property)]
tests = [ ("get (put empty x) == x", property prop_empty)
        , ("isEmpty (rest (put empty x)) == true", property prop_rest)
        , ("lastItem (put queue x) == x", property prop_last)
        , ("isEmpty (drain q) == true", property prop_drainable)
        ]

main :: IO ()
main = mapM_ (\(s, p) -> do { putStrLn s; quickCheck p}) tests