The Queue abstraction employs a First-In-First-Out (FIFO) policy for removing ("dequeing") elements.
empty : Queue a
isEmpty : Queue a -> Bool
enqueue : a -> Queue a -> Queue a
dequeue : Queue a -> Maybe (Queue a)
peek : Queue a -> Maybe a
NOTE: Another reasonable alternative for dequeue would be to also return the dequeued element.
Without having O(1) time access to the last element in a List, special care must be taken to provide efficient Queue operations in a purely functional language.
SlowQueue.elmtype Queue a =
Q (List a)
empty : Queue a
empty =
Q []
isEmpty : Queue a -> Bool
isEmpty q =
q == empty
enqueue : a -> Queue a -> Queue a
enqueue x (Q xs) =
Q (xs ++ [x])
dequeue : Queue a -> Maybe (Queue a)
dequeue (Q xs) =
case xs of
_::xs_ -> Just (Q xs_)
[] -> Nothing
peek : Queue a -> Maybe a
peek (Q xs) =
case xs of
x::_ -> Just x
[] -> Nothing
Running times:
enqueue : Θ(n)dequeue : O(1)peek : O(1)AnotherSlowQueue.elmtype Queue a
= Front (List a)
| Back (List a)
empty : Queue a
empty =
Front []
isEmpty : Queue a -> Bool
isEmpty q =
case q of
Front [] -> True
Back [] -> True
_ -> False
enqueue : a -> Queue a -> Queue a
enqueue x q =
case q of
Front xs -> Back (x :: List.reverse xs)
Back xs -> Back (x :: xs)
dequeue : Queue a -> Maybe (Queue a)
dequeue q =
case q of
Back [] -> Nothing
Back (x::xs) -> Just (Back xs)
Front xs -> dequeue (Back (List.reverse xs))
peek : Queue a -> Maybe a
peek q =
case q of
Back [] -> Nothing
Back (x::_) -> Just x
Front xs -> peek (Back (List.reverse xs))
Running times:
enqueue : O(n)dequeue : O(n)peek : O(n)MediumQueue.elmtype Queue a =
Q { front: List a, back: List a }
empty : Queue a
empty =
Q {front = [], back = []}
isEmpty : Queue a -> Bool
isEmpty q =
q == empty
enqueue : a -> Queue a -> Queue a
enqueue x (Q {front, back}) =
Q {front = front, back = x::back }
dequeue : Queue a -> Maybe (Queue a)
dequeue (Q {front, back}) =
case (front, back) of
(_::f, _) -> Just (Q {front = f, back = back})
([], []) -> Nothing
([], _) -> dequeue (Q {front = List.reverse back, back = []})
peek : Queue a -> Maybe a
peek (Q {front, back}) =
case (front, back) of
(x::_, _) -> Just x
([], []) -> Nothing
([], _) -> peek (Q {front = List.reverse back, back = []})
Running times:
enqueue : O(1)dequeue : O(n)peek : O(n)FastQueue.elmSame representation Q {front, back} as previous version, but with the invariant front == [] implies back == [].
enqueue : a -> Queue a -> Queue a
enqueue x (Q {front, back}) =
case front of
[] -> Q {front = [x], back = []}
_ -> Q {front = front, back = x::back }
dequeue : Queue a -> Maybe (Queue a)
dequeue (Q {front, back}) =
case front of
[] -> Nothing
_::[] -> Just (Q {front = List.reverse back, back = []})
_::f_ -> Just (Q {front = f_, back = back})
peek : Queue a -> Maybe a
peek (Q {front, back}) =
List.head front
We can factor out a common pattern from enqueue and dequeue. Notice that arguments f and b to checkFront do not necessarily satisfy the invariant, but the output Queue does.
checkFront : List a -> List a -> Queue a
checkFront f b =
case f of
[] -> Q {front = List.reverse b, back = []}
_ -> Q {front = f, back = b}
enqueue_ : a -> Queue a -> Queue a
enqueue_ x (Q {front, back}) =
checkFront front (x::back)
dequeue_ : Queue a -> Maybe (Queue a)
dequeue_ (Q {front, back}) =
case front of
[] -> Nothing
_::f_ -> Just (checkFront f_ back)
Running times:
enqueue : O(1)dequeue : O(n)peek : O(1)The worst-case bound for dequeue does not tell the whole story, however, since it often runs in O(1) time and only occasionally runs in O(n) time. Using amortized asymptotic analysis, we will be able to argue that dequeue runs in O(1) on average.