combobuffer-0.1: src/Data/RingBuffer/ComboBuffer.hs
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wall #-}
-- | Generally the best-performing implementation in this package.
--
-- 'ComboBuffer' supports amortized O(1) push and O(1) length. lookups at 'ix
-- >= n/2' and 'ix=0' are O(1), while lookups at '0 <= ix < n/2' are no more
-- than O(n). If you need to do many lookups in the first half, and
-- particularly around n/4, another structure may be a better choice.
module Data.RingBuffer.ComboBuffer (
ComboBuffer (..)
)
where
import Prelude hiding (length)
import Data.RingBuffer.Class
import Data.RingBuffer.Chord
import qualified Data.Vector.Unboxed as V
data ComboBuffer a =
CB {-# UNPACK #-} !Int -- Half Size
{-# UNPACK #-} !Int -- buffer position
!(V.Vector a) -- newer vector
!(V.Vector a) -- older vector
(Chord a) -- updates
| CBOdd {-# UNPACK #-} !Int -- Half (Size + 1)
{-# UNPACK #-} !Int -- buffer position
!(V.Vector a) -- newer vector
!(V.Vector a) -- older vector
(Chord a) -- updates
deriving (Eq, Ord, Show)
type instance El (ComboBuffer a) = a
instance V.Unbox a => Initializable (ComboBuffer a) where
{-# INLINE newInit #-}
newInit = newInit'
newInit' :: V.Unbox a => a -> Int -> ComboBuffer a
newInit' el sz
| sz <= 0 = error "Can't initialize ComboBuffer with size <= 0"
| sz `rem` 2 == 0 = let half = sz `div` 2
iv = V.replicate half el
in CB half 0 iv iv (emptyChord el)
| otherwise = let half = (1+sz) `div` 2
iv = V.replicate half el
in CBOdd half 0 iv iv (emptyChord el)
{-# INLINE newInit' #-}
instance V.Unbox a => RingBuffer (ComboBuffer a) where
{-# INLINE length #-}
length (CB sz _ _ _ _) = 2*sz
length (CBOdd sz _ _ _ _) = 2*sz-1
{-# INLINE (!) #-}
(!) = at
{-# INLINE push #-}
push = pushB
{-# INLINE slice #-}
slice = sliceB
at :: V.Unbox a => ComboBuffer a -> Int -> a
at (CB sz pos v1 v2 upds) ix
| ix < 0 = error $ "ComboBuffer: index out of range: " ++ show ix
| ix < pos = upds ! ix
| ix < 2*sz = let ix' = ix - pos in if ix' < sz
then v1 `V.unsafeIndex` ix'
else v2 `V.unsafeIndex` (ix' - sz)
| otherwise = error $ "ComboBuffer: index out of range: " ++ show (ix,2*sz)
at (CBOdd sz pos v1 v2 upds) ix
| ix < 0 = error $ "ComboBuffer: index out of range: " ++ show ix
| ix < pos = upds ! ix
| ix < 2*sz-1 = let ix' = ix - pos in if ix' < sz
then v1 `V.unsafeIndex` ix'
else v2 `V.unsafeIndex` (ix' - sz)
| otherwise = error $ "ComboBuffer: index out of range: " ++ show (ix,2*sz-1)
{-# INLINE at #-}
sliceB :: V.Unbox a => ComboBuffer a -> Int -> Int -> [a]
sliceB (CB sz pos v1 v2 upds) start num
= let ix' = start - pos
fstTake = sz - (ix'+num)
in if ix' < sz
then slice upds start num
++ V.toList (V.slice ix' (min 0 fstTake) v1)
++ V.toList (V.slice 0 (min 0 (negate fstTake)) v2)
else V.toList (V.slice (ix'-sz) num v2)
sliceB (CBOdd sz pos v1 v2 upds) start num
= let ix' = start - pos
fstTake = sz - (ix'+num)
in if ix' < sz
then slice upds start num
++ V.toList (V.slice ix' (min 0 fstTake) v1)
++ V.toList (V.slice 0 (min 0 (negate fstTake)) v2)
else V.toList (V.slice (ix'-sz) num v2)
{-# INLINE sliceB #-}
pushB :: V.Unbox a => ComboBuffer a -> a -> ComboBuffer a
pushB (CB sz pos v1 v2 upds) el
| pos == sz-1 = CB sz 0 (cToVec $ push upds el) v1
(emptyChord el)
| otherwise = CB sz (pos+1) v1 v2 (push upds el)
pushB (CBOdd sz pos v1 v2 upds) el
| pos == sz-1 = CBOdd sz 0 (cToVec $ push upds el) v1
(emptyChord el)
| otherwise = CBOdd sz (pos+1) v1 v2 (push upds el)
{-# INLINE pushB #-}