dequeue-0.1: src/Data/Dequeue.hs
{- |
Module : Data.Dequeue
Description : A typeclass and an implementation for double-ended queues.
Copyright : (c) Henry Bucklow 2009
License : BSD3
Maintainer : henry@elsie.org.uk
Stability : provisional
Portability : portable
A typeclass for double-ended queues, and an implementation of Banker's
Dequeues, as described in Chris Okasaki's Purely Functional Data Structures.
-}
module Data.Dequeue (
-- * The 'Dequeue' type class.
Dequeue(..),
-- * QuickCheck properties for 'Dequeue' instances
prop_pushpop_front,
prop_pushpop_back,
prop_push_front,
prop_push_back,
prop_length_toList,
-- * Banker's Dequeues
BankersDequeue,
-- * QuickCheck properties for 'BankersDequeue'
prop_pushpop_front_bq,
prop_pushpop_back_bq,
prop_push_front_bq,
prop_push_back_bq,
prop_length_toList_bq,
prop_push_front_bq_balance,
prop_push_back_bq_balance,
prop_pop_front_bq_balance,
prop_pop_back_bq_balance
) where
import Prelude hiding (foldl, foldr, foldl1, foldr1, length, last)
import Control.Monad
import Data.Foldable
import qualified Data.List as List
import Test.QuickCheck hiding (check)
import Safe
-- | A typeclass for double-ended queues.
class Dequeue q where
-- | Generates an empty queue.
empty :: q a
-- | Returns 'True' if this queue is empty.
null :: q a -> Bool
-- | Returns the number of elements in this queue.
length :: q a -> Int
-- | Returns the item on the front of the queue.
first :: q a -> Maybe a
-- | Returns the item on the end of the queue.
last :: q a -> Maybe a
-- | Returns the first n items from the front of the queue.
takeFront :: Int -> q a -> [a]
-- | Returns the last n items from the end of the queue.
takeBack :: Int -> q a -> [a]
-- | Pushes an item onto the front of the queue.
pushFront :: q a -> a -> q a
-- | Pops an item from the front of the queue.
popFront :: q a -> (Maybe a, q a)
-- | Pushes an item onto the back of the queue.
pushBack :: q a -> a -> q a
-- | Pops an item from the back of the queue.
popBack :: q a -> (Maybe a, q a)
-- | Converts a list into a queue.
fromList :: [a] -> q a
-- QuickCheck properties for Dequeue instances.
-- | Validates that if you push, then pop, the front of the queue,
-- you get the same queue.
prop_pushpop_front :: (Dequeue q, Eq a, Eq (q a)) => q a -> a -> Bool
prop_pushpop_front q a =
let (a', q') = popFront (pushFront q a) in
a' == Just a && q' == q
-- | Validates that if you push, then pop, the back of the queue,
-- you get the same queue.
prop_pushpop_back :: (Dequeue q, Eq a, Eq (q a)) => q a -> a -> Bool
prop_pushpop_back q a =
let (a', q') = popBack (pushBack q a) in
a' == Just a && q' == q
-- | Validates that 'first' returns the last 'pushFront''d element.
prop_push_front :: (Dequeue q, Eq a) => q a -> a -> Bool
prop_push_front q a = first (pushFront q a) == Just a
-- | Validates that 'last' returns the last 'pushBack''d element.
prop_push_back :: (Dequeue q, Eq a) => q a -> a -> Bool
prop_push_back q a = last (pushBack q a) == Just a
-- | Validates that the length of a queue is the same as the length of the
-- list generated from the queue.
prop_length_toList :: (Dequeue q, Foldable q) => q a -> Bool
prop_length_toList q = List.length (toList q) == length q
-- | An implementation of Banker's Dequeues, as described in Chris Okasaki's
-- Purely Functional Data Structures. The functions for the 'Dequeue'
-- instance have the following complexities (where n is the 'length' of the
-- queue):
--
-- * 'length': O(1)
--
-- * 'first': O(1)
--
-- * 'last': O(1)
--
-- * 'takeFront': O(n)
--
-- * 'takeBack': O(n)
--
-- * 'pushFront': O(1) amortised
--
-- * 'popFront': O(1) amortised
--
-- * 'pushBack': O(1) amortised
--
-- * 'popBack': O(1) amortised
--
-- * 'fromList': O(n)
data BankersDequeue a = BankersDequeue Int [a] Int [a]
instance Functor BankersDequeue where
fmap f (BankersDequeue sizeF front sizeR rear) =
BankersDequeue sizeF (fmap f front) sizeR (fmap f rear)
instance Foldable BankersDequeue where
fold (BankersDequeue _ front _ rear) = fold (front ++ reverse rear)
foldMap f (BankersDequeue _ front _ rear) = foldMap f (front ++ reverse rear)
foldr f a (BankersDequeue _ front _ rear) = foldr f a (front ++ reverse rear)
foldl f a (BankersDequeue _ front _ rear) = foldl f a (front ++ reverse rear)
foldr1 f (BankersDequeue _ front _ rear) = foldr1 f (front ++ reverse rear)
foldl1 f (BankersDequeue _ front _ rear) = foldl1 f (front ++ reverse rear)
instance Dequeue BankersDequeue where
empty = BankersDequeue 0 [] 0 []
null (BankersDequeue 0 [] 0 []) = True
null _ = False
length (BankersDequeue sizeF _ sizeR _) = sizeF + sizeR
first (BankersDequeue _ [] _ [x]) = Just x
first (BankersDequeue _ front _ _) = headMay front
last (BankersDequeue _ [x] _ []) = Just x
last (BankersDequeue _ _ _ rear) = headMay rear
takeFront i (BankersDequeue sizeF front _ rear) =
take i front ++ take (i - sizeF) (reverse rear)
takeBack i (BankersDequeue _ front sizeR rear) =
reverse $ take i rear ++ take (i - sizeR) (reverse front)
pushFront (BankersDequeue sizeF front sizeR rear) x =
check $ BankersDequeue (sizeF + 1) (x : front) sizeR rear
popFront (BankersDequeue _ [] _ []) = (Nothing, empty)
popFront (BankersDequeue _ [] _ [x]) = (Just x, empty)
popFront (BankersDequeue _ [] _ _) = error "Queue is too far unbalanced."
popFront (BankersDequeue sizeF (f : fs) sizeR rear) =
(Just f, check $ BankersDequeue (sizeF - 1) fs sizeR rear)
pushBack (BankersDequeue sizeF front sizeR rear) x =
check $ BankersDequeue sizeF front (sizeR + 1) (x : rear)
popBack (BankersDequeue _ [] _ []) = (Nothing, empty)
popBack (BankersDequeue _ [x] _ []) = (Just x, empty)
popBack (BankersDequeue _ _ _ []) = error "Queue is too far unbalanced."
popBack (BankersDequeue sizeF front sizeR (r : rs)) =
(Just r, check $ BankersDequeue sizeF front (sizeR - 1) rs)
fromList list = check $ BankersDequeue (List.length list) list 0 []
-- | The maximum number of times longer one half of a 'BankersDequeue' is
-- permitted to be relative to the other.
bqBalance :: Int
bqBalance = 4
-- | Checks to see if the queue is too far out of balance. If it is, it
-- rebalances it.
check :: BankersDequeue a -> BankersDequeue a
check q@(BankersDequeue sizeF front sizeR rear)
| sizeF > c * sizeR + 1 =
let front' = take size1 front
rear' = rear ++ drop size1 front
in
BankersDequeue size1 front' size2 rear'
| sizeR > c * sizeF + 1 =
let front' = front ++ reverse (drop size1 rear)
rear' = take size1 rear
in
BankersDequeue size2 front' size1 rear'
| otherwise = q
where
size1 = (sizeF + sizeR) `div` 2
size2 = (sizeF + sizeR) - size1
c = bqBalance
instance (Arbitrary a) => Arbitrary (BankersDequeue a) where
arbitrary = (liftM fromList) arbitrary
coarbitrary (BankersDequeue _ front _ rear) =
variant 0 . coarbitrary front . coarbitrary rear
instance Eq a => Eq (BankersDequeue a) where
queue1 == queue2 = toList queue1 == toList queue2
instance Show a => Show (BankersDequeue a) where
show q = "BankersDequeue " ++ show (toList q)
-- QuickCheck properties for BankersDequeue.
-- | Validates that if you push, then pop, the front of a 'BankersQueue',
-- you get the same queue.
prop_pushpop_front_bq :: BankersDequeue Int -> Int -> Bool
prop_pushpop_front_bq = prop_pushpop_front
-- | Validates that if you push, then pop, the back of a 'BankersDequeue',
-- you get the same queue.
prop_pushpop_back_bq :: BankersDequeue Int -> Int -> Bool
prop_pushpop_back_bq = prop_pushpop_back
-- | Validates that 'first' returns the last 'pushFront''d element.
prop_push_front_bq :: BankersDequeue Int -> Int -> Bool
prop_push_front_bq = prop_push_front
-- | Validates that 'last' returns the last 'pushBack''d element.
prop_push_back_bq :: BankersDequeue Int -> Int -> Bool
prop_push_back_bq = prop_push_back
-- | Validates that the length of a 'BankersDequeue' is the same as the length
-- of the list generated from the queue.
prop_length_toList_bq :: BankersDequeue Int -> Bool
prop_length_toList_bq = prop_length_toList
balanced :: BankersDequeue a -> Bool
balanced (BankersDequeue 0 _ 0 _) = True
balanced (BankersDequeue 1 _ 0 _) = True
balanced (BankersDequeue 0 _ 1 _) = True
balanced (BankersDequeue sizeF _ sizeR _) =
sizeF <= bqBalance * sizeR + 1 && sizeR <= bqBalance * sizeF + 1
-- | Validates that a 'BankersDequeue' remains balanced despite repeated
-- pushes to the front.
prop_push_front_bq_balance :: BankersDequeue Int -> Int -> Bool
prop_push_front_bq_balance q count =
let push _ = (flip pushFront) 0
q' = foldr push q [0 .. count] in
balanced q'
-- | Validates that a 'BankersDequeue' remains balanced despite repeated
-- pushes to the back.
prop_push_back_bq_balance :: BankersDequeue Int -> Int -> Bool
prop_push_back_bq_balance q count =
let push _ = (flip pushBack) 0
q' = foldr push q [0 .. count] in
balanced q'
-- | Validates that a 'BankersDequeue' remains balanced despite repeated
-- pops from the front.
prop_pop_front_bq_balance :: BankersDequeue Int -> Int -> Bool
prop_pop_front_bq_balance q count =
let pop _ queue = let (_, queue') = popFront queue in queue'
q' = foldr pop q [0 .. count] in
balanced q'
-- | Validates that a 'BankersDequeue' remains balanced despite repeated
-- pops from the back.
prop_pop_back_bq_balance :: BankersDequeue Int -> Int -> Bool
prop_pop_back_bq_balance q count =
let pop _ queue = let (_, queue') = popBack queue in queue'
q' = foldr pop q [0 .. count] in
balanced q'