{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} -- | -- Module : Data.Queue.Bounded -- Copyright : (c) Kadena LLC, 2019 -- License : BSD3 -- Maintainer: Colin Woodbury -- -- This library provides a strict, immutable, thread-safe, single-ended, bounded -- queue. When the insert limit is reached and a `cons` is attempted, this -- `BQueue` automatically drops old entries off its end. Thus, writes always -- succeed and never block. -- -- This data structure is intended as a "sliding window" over some stream of -- data, where we wish old entries to be naturally forgotten. Since this is an -- immutable data structure and not a concurrent queue, we provide instances for -- the usual useful typeclasses with which one can perform analysis over the -- entire "window". -- -- This module is intended to be imported qualified: -- -- @ -- import qualified Data.Queue.Bounded as BQ -- @ module Data.Queue.Bounded ( -- * Type BQueue() -- * Construction , empty, singleton, fromList -- * Insertion / Removal , cons, uncons -- * Extra , average , reverse , take, drop ) where import Control.DeepSeq (NFData) import Data.Foldable (foldl', toList) import Data.Ratio ((%)) import Data.Sequence (Seq(..), (<|)) import qualified Data.Sequence as Seq import GHC.Generics (Generic) import Prelude hiding (drop, reverse, take) import qualified Prelude as P --- -- | A single-ended, bounded queue which keeps track of its size. data BQueue a = BQueue { _bqs :: !(Seq a) , _bqsLimit :: {-# UNPACK #-} !Int , _bqsSize :: {-# UNPACK #-} !Int } deriving (Eq, Generic, NFData) instance Show a => Show (BQueue a) where show (BQueue q _ _) = show $ toList q instance Functor BQueue where fmap f (BQueue q l s) = BQueue (f <$> q) l s {-# INLINE fmap #-} -- | \(\mathcal{O}(1)\) `length` implementation. instance Foldable BQueue where foldMap f (BQueue q _ _) = foldMap f q {-# INLINE foldMap #-} length (BQueue _ _ s) = s instance Traversable BQueue where traverse f (BQueue q l s) = (\q' -> BQueue q' l s) <$> traverse f q {-# INLINE traverse #-} instance Semigroup (BQueue a) where (BQueue q l s) <> (BQueue q' l' s') = BQueue (q <> q') (l + l') (s + s') {-# INLINE (<>) #-} -- | Given a limit value, yield an empty `BQueue`. empty :: Int -> BQueue a empty l = BQueue mempty l 0 -- | Given a limit value and an initial value, yield a singleton `BQueue`. singleton :: Int -> a -> BQueue a singleton l a = BQueue (Seq.singleton a) l 1 -- | \(\mathcal{O}(c)\). Naively keeps the first \(c\) values of the input list -- (as defined by the given limiting `Int` value) and does not attempt any -- elegant queue-like cycling. fromList :: Int -> [a] -> BQueue a fromList n list = BQueue (Seq.fromList list') n $ length list' where list' = P.take n list -- | \(\mathcal{O}(1)\). cons :: a -> BQueue a -> BQueue a cons a (BQueue Empty l _) = BQueue (Seq.singleton a) l 1 cons a (BQueue q@(rest :|> _) l s) | s == l = BQueue (a <| rest) l s | otherwise = BQueue (a <| q) l (succ s) -- | \(\mathcal{O}(1)\). uncons :: BQueue a -> Maybe (a, BQueue a) uncons (BQueue Empty _ _) = Nothing uncons (BQueue (h :<| t) l s) = Just (h, BQueue t l $ pred s) -- | \(\mathcal{O}(n)\). average :: Integral a => BQueue a -> a average (BQueue q _ s) = floor $ foldl' (+) 0 q % fromIntegral s {-# INLINE average #-} -- | \(\mathcal{O}(n)\). reverse :: BQueue a -> BQueue a reverse (BQueue q l s) = BQueue (Seq.reverse q) l s -- | \(\mathcal{O}(\log(\min(i,n-i)))\). take :: Int -> BQueue a -> BQueue a take n (BQueue q l s) = BQueue (Seq.take n q) l $ min n s -- | \(\mathcal{O}(\log(\min(i,n-i)))\). drop :: Int -> BQueue a -> BQueue a drop n (BQueue q l s) = BQueue (Seq.drop n q) l $ max 0 (s - n)