packages feed

Combinatorrent-0.2.0: src/Data/Queue.hs

-- | Simple Functional queues based on a double list. This usually achieves good amortized bounds
module Data.Queue (
              -- * Types
                Queue
              -- * Functions
              , empty
              , null
              , first
              , remove
              , push
              , pop
              , Data.Queue.filter
              -- * Test Suite
              , testSuite
              )
where

import Data.List as Lst hiding (null)
import Data.Maybe (fromJust)

import Prelude hiding (null)

import Test.QuickCheck
import Test.Framework
import Test.Framework.Providers.QuickCheck2
import Test.Framework.Providers.HUnit
import Test.HUnit hiding (Path, Test)

data Queue a = Queue [a] [a]
  deriving (Eq, Show)

-- | The definition of an empty Queue
empty :: Queue a
empty = Queue [] []

-- | Returns True on an empty Queue, and False otherwise.
null :: Queue a -> Bool
null (Queue [] []) = True
null _             = False

-- | Pushes a new element to the tail of the list.
--   Operates in constant time.
push :: a -> Queue a -> Queue a
push e (Queue front back) = Queue front (e : back)


-- | Pops the top most element off the queue.
--   Operates in amortized constant time
pop :: Queue a -> Maybe (a, Queue a)
pop (Queue []       [])   = Nothing
pop (Queue (e : es) back) = Just (e, Queue es back)
pop (Queue []       back) = pop (Queue (reverse back) [])

-- | Return the head of the queue, if any
first :: Queue a -> Maybe a
first (Queue [] []) = Nothing
first (Queue (e : _) _) = Just e
first (Queue []  back)  = Just $ last back -- Yeah slow

-- | Kill the first element in the queue
remove :: Queue a -> Queue a
remove (Queue [] [])      = Queue [] []
remove (Queue (_ : es) b) = Queue es b
remove (Queue [] b)       = remove (Queue (reverse b) [])

-- | Generates a new Queue only containing elements for which
--   p returns true.
filter :: (a -> Bool) -> Queue a -> Queue a
filter p (Queue front back) = Queue (Lst.filter p front) (Lst.filter p back)


-- Tests

testSuite :: Test
testSuite = testGroup "Data/Queue"
  [ testCase "Empty Queue is Empty" testEmptyIsEmpty
  , testCase "First/Remove" testFirstRemove
  , testProperty "Simple push/pop" testPushPopSimple
  , testProperty "push/pop more"   testPushPopMore
  , testProperty "push/pop interleave" testPushPopInterleave
  ]

-- Rudimentary boring simple tests
testEmptyIsEmpty :: Assertion
testEmptyIsEmpty = do
    assertBool "for Empty Q" (null empty)
    assertBool "for non-Empty Q" (not $ null (push "Foo" empty))
    assertEqual "for popping the Empty Q" (pop $ snd . fromJust . pop $ push "Foo" empty) Nothing

testFirstRemove :: Assertion
testFirstRemove = do
    -- Should really cover this more
    let nq = push 2 (push (1 :: Integer) empty)
    assertEqual "first" (first nq) (Just 1)
    assertEqual "first/removed" (first (remove nq)) (Just 2)
    assertEqual "emptied" (first (remove (remove nq))) Nothing

testPushPopSimple :: String -> Bool
testPushPopSimple s =
    let nq = pop (push s empty)
    in case nq of
        Nothing -> False
        Just (r, q) -> r == s && null q

testPushPopMore :: [String] -> Bool
testPushPopMore ls =
    let nq = foldl (flip push) empty ls
        popAll = unfoldr pop nq
    in popAll == ls

data Operation = Push | Pop
  deriving (Eq, Show)

instance Arbitrary Operation where
    arbitrary = oneof [return Push, return Pop]

testPushPopInterleave :: [Operation] -> [String] -> Bool
testPushPopInterleave ops ls = testQ empty ops ls []
  where
    testQ q op lst res =
     case op of
        [] -> popAll q == reverse res
        Pop : r -> case pop q of
                     Nothing -> testQ empty r lst []
                     Just (e, nq) ->
                        if (last res) == e
                            then testQ nq r lst (init res)
                            else False
        Push : r -> case lst of
                     [] -> testQ q r lst res
                     (e : es) -> testQ (push e q) r es (e : res)
    popAll = unfoldr pop