packages feed

heph-sparse-set-0.1.0.0: src/Data/SparseSet/Generic/Mutable/Internal/GrowVec.hs

-- |
-- Description : A generic growable mutable vector with O(1) amortized append.
-- Copyright   : (c) Jeremy Nuttall, 2025
-- License     : BSD-3-Clause
-- Maintainer  : jeremy@jeremy-nuttall.com
-- Stability   : experimental
-- Portability : GHC
--
-- __WARNING:__ The functions in this module are generally unchecked and unsafe. Be careful to understand
-- and maintain invariants if using them. Misuse may result in undefined behavior.
--
-- Internal modules can change without warning between minor versions.
module Data.SparseSet.Generic.Mutable.Internal.GrowVec (
  GrowVec,
  withCapacity,
  new,
  length,
  capacity,
  snoc,
  readMaybe,
  unsafeRead,
  maximum,
  unsafeWrite,
  unsafeSwapRemove,
  mapM_,
  cleared,
  compact,
  freeze,
  unsafeFreeze,
)
where

import Control.DeepSeq (NFData)
import Control.Monad.Primitive
import Data.Typeable (Typeable)
import Data.Vector.Generic qualified as VG
import Data.Vector.Generic.Mutable qualified as VGM
import GHC.Generics (Generic)
import Prelude hiding (length, mapM_, maximum)

data GrowVec v s a = GrowVec {-# UNPACK #-} !Int (v s a)
  deriving (Show, Generic, Typeable)

instance (NFData (v s a)) => NFData (GrowVec v s a)

-- | Create a new, empty vector with the given capacity
--
-- @since 0.1.0.0
withCapacity :: forall a v m. (PrimMonad m, VGM.MVector v a) => Int -> m (GrowVec v (PrimState m) a)
withCapacity c = GrowVec 0 <$> VGM.new (withMinCapacity c)
{-# INLINE withCapacity #-}

-- | Create a new, empty vector with a default capcity
--
-- @since 0.1.0.0
new :: forall a v m. (PrimMonad m, VGM.MVector v a) => m (GrowVec v (PrimState m) a)
new = GrowVec 0 <$> VGM.new 16
{-# INLINE new #-}

-- | O(1) The logical length of the vector.
length :: forall a v s. GrowVec v s a -> Int
length (GrowVec l _) = l
{-# INLINE length #-}

-- | O(1) The capacity of the vector.
capacity :: (VGM.MVector v a) => GrowVec v s a -> Int
capacity (GrowVec _ v) = VGM.length v
{-# INLINE capacity #-}

-- | Calculate the additional number of elements given the current length.
--
-- __INVARIANT__: length must be >= 2
--
-- @since 0.1.0.0
growthFactor :: Int -> Int
growthFactor l = (l `quot` 2) * 3
{-# INLINE growthFactor #-}

-- | O(1) amortized. Append to the vector, reallocating and copying if necessary.
--
-- Since this can't be done in-place, you must use the resulting vector in further computations.
--
-- @since 0.1.0.0
snoc
  :: (VGM.MVector v a, PrimMonad m) => GrowVec v (PrimState m) a -> a -> m (GrowVec v (PrimState m) a)
snoc gv a = do
  gv' <- grow gv
  unsafeWrite gv' (length gv) a
  pure gv'
 where
  grow (GrowVec l v)
    | capacity gv <= l = GrowVec (l + 1) <$> VGM.grow v (growthFactor l)
    | otherwise = pure $ GrowVec (l + 1) v
{-# INLINE snoc #-}

readMaybe
  :: forall a m v. (PrimMonad m, VGM.MVector v a) => GrowVec v (PrimState m) a -> Int -> m (Maybe a)
readMaybe (GrowVec l v) i
  | l < 0 || i >= l = pure Nothing
  | otherwise = Just <$> VGM.unsafeRead v i
{-# INLINE readMaybe #-}

-- | O(1) Read from a position in the vector. This position must be less than the length of the vector. This is not checked.
--
-- @since 0.1.0.0
unsafeRead
  :: forall a m v. (PrimMonad m, VGM.MVector v a) => GrowVec v (PrimState m) a -> Int -> m a
unsafeRead (GrowVec _ v) = VGM.unsafeRead v
{-# INLINE unsafeRead #-}

-- | O(n) The maximum value in the vector. Useful for compaction.
--
-- @since 0.1.0.0
maximum
  :: (PrimMonad m, VGM.MVector v a, Ord a, Bounded a) => GrowVec v (PrimState m) a -> m (Maybe a)
maximum (GrowVec l v)
  | l <= 0 = pure Nothing
  | otherwise = Just <$> VGM.foldl' max minBound (VGM.unsafeSlice 0 l v)
{-# INLINE maximum #-}

-- | O(1) Write to a position in the vector. This position must be less than the length of the vector. This is not checked.
--
-- @since 0.1.0.0
unsafeWrite
  :: forall a m v. (PrimMonad m, VGM.MVector v a) => GrowVec v (PrimState m) a -> Int -> a -> m ()
unsafeWrite (GrowVec _ v) = VGM.unsafeWrite v
{-# INLINE unsafeWrite #-}

-- | O(1) Swap-and-pop an element in the vector
--
-- @since 0.1.0.0
unsafeSwapRemove
  :: forall a m v
   . (PrimMonad m, VGM.MVector v a)
  => GrowVec v (PrimState m) a
  -> Int
  -> m (a, GrowVec v (PrimState m) a)
unsafeSwapRemove (GrowVec l v) i = do
  old <- VGM.read v i
  VGM.swap v i (l - 1)
  pure (old, GrowVec (l - 1) v)
{-# INLINE unsafeSwapRemove #-}

mapM_ :: (PrimMonad m, VGM.MVector v a) => (a -> m b) -> GrowVec v (PrimState m) a -> m ()
mapM_ f (GrowVec l v) = VGM.mapM_ f (VGM.unsafeSlice 0 l v)
{-# INLINE mapM_ #-}

-- | O(1) Create a new, empty vector by setting logical length to 0. This does not change the
-- underlying vector in any way.
--
-- @since 0.1.0.0
cleared :: forall a s v. GrowVec v s a -> GrowVec v s a
cleared (GrowVec _ v) = GrowVec 0 v
{-# INLINE cleared #-}

-- | O(n) Shrink the vector so that its capacity matches its current length
--
-- @since 0.1.0.0
compact
  :: (VGM.MVector v a, PrimMonad m) => GrowVec v (PrimState m) a -> m (GrowVec v (PrimState m) a)
compact gv@(GrowVec l v)
  | capacity gv == l = pure gv
  | otherwise = do
      let l' = withMinCapacity l
      v' <- VGM.clone (VGM.unsafeSlice 0 l' v)
      pure (GrowVec l v')

freeze :: (PrimMonad m, VG.Vector v a) => GrowVec (VG.Mutable v) (PrimState m) a -> m (v a)
freeze (GrowVec l v) = VG.freeze (VGM.unsafeSlice 0 l v)
{-# INLINE freeze #-}

unsafeFreeze :: (PrimMonad m, VG.Vector v a) => GrowVec (VG.Mutable v) (PrimState m) a -> m (v a)
unsafeFreeze (GrowVec l v) = VG.unsafeFreeze (VGM.unsafeSlice 0 l v)
{-# INLINE unsafeFreeze #-}

--------------------------------------------------------------------------------
-- Utilities
--------------------------------------------------------------------------------
withMinCapacity :: Int -> Int
withMinCapacity c = max c 4
{-# INLINE withMinCapacity #-}