packages feed

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

-- |
-- Description : Fast, mutable sparse sets.
-- Copyright   : (c) Jeremy Nuttall, 2025
-- License     : BSD-3-Clause
-- Maintainer  : jeremy@jeremy-nuttall.com
-- Stability   : experimental
-- Portability : GHC
--
-- Generic mutable sparse sets, usable in any state transformer monad.
--
-- __This implementation is NOT thread-safe.__ Thread safety must be maintained by a whole-set
-- locking mechanism.
module Data.SparseSet.Generic.Mutable (
  MutableSparseSet,

  -- * Creation
  withCapacity,
  new,

  -- * Read
  length,
  contains,
  members,
  lookup,

  -- * Update
  insert,
  delete,
  clear,
  compact,

  -- * Iteration
  foldM,
  ifoldM,
  mapM_,
  imapM_,
  ifoldIntersectionM,
)
where

import Control.Monad hiding (foldM, foldM_, mapM_)
import Control.Monad.Primitive
import Data.Primitive
import Data.Typeable (Typeable)
import Data.Vector.Generic qualified as VG
import Data.Vector.Generic.Mutable qualified as MVG
import Data.Vector.Primitive.Mutable qualified as MVP
import GHC.Generics (Generic)
import Prelude hiding (length, lookup, mapM_)

import Data.SparseSet.Generic.Mutable.Internal.GrowVec (GrowVec)
import Data.SparseSet.Generic.Mutable.Internal.GrowVec qualified as GrowVec
import Data.SparseSet.Generic.Mutable.Internal.MutableSparseArray (MutableSparseArray)
import Data.SparseSet.Generic.Mutable.Internal.MutableSparseArray qualified as MSA
import Data.Vector.Internal.Check (HasCallStack)

data MutableSparseSet v s a = MutableSparseSet
  { ssDense :: {-# UNPACK #-} !(MutVar s (GrowVec v s a))
  , ssIndices :: {-# UNPACK #-} !(MutVar s (GrowVec MVP.MVector s Int))
  , ssSparse :: {-# UNPACK #-} !(MutVar s (MutableSparseArray s))
  }
  deriving (Generic, Typeable)

-- | Create a sparse set with a given dense and sparse capacity
--
-- It's a good idea to use this function if you have an estimate of your data requirements,
-- as it can prevent costly re-allocations as the set grows.
--
-- @since 0.1.0.0
withCapacity
  :: forall a v m
   . (PrimMonad m, MVG.MVector v a)
  => Int
  -- ^ Capacity for the dense set
  -> Int
  -- ^ Capacity for the sparse set
  -> m (MutableSparseSet v (PrimState m) a)
withCapacity dc sc =
  MutableSparseSet
    <$> (newMutVar =<< GrowVec.withCapacity dc)
    <*> (newMutVar =<< GrowVec.withCapacity dc)
    <*> (newMutVar =<< MSA.withCapacity sc)
{-# INLINE withCapacity #-}

-- | Create an empty sparse set with default capacities.
--
-- @since 0.1.0.0
new :: forall a v m. (PrimMonad m, MVG.MVector v a) => m (MutableSparseSet v (PrimState m) a)
new =
  MutableSparseSet
    <$> (newMutVar =<< GrowVec.new)
    <*> (newMutVar =<< GrowVec.new)
    <*> (newMutVar =<< MSA.new)
{-# INLINE new #-}

-- | O(1) Number of elements in the set (dense)
--
-- @since 0.1.0.0
length :: forall a v m. (PrimMonad m) => MutableSparseSet v (PrimState m) a -> m Int
length MutableSparseSet{..} = GrowVec.length <$> readMutVar ssDense
{-# INLINE length #-}

-- | O(1) Check whether an element is in the set
--
-- @since 0.1.0.0
contains :: forall a v m. (PrimMonad m) => MutableSparseSet v (PrimState m) a -> Int -> m Bool
contains MutableSparseSet{..} i = readMutVar ssSparse >>= (`MSA.contains` i)
{-# INLINE contains #-}

-- | O(n) The members of the set in an unspecified order.
--
-- @since 0.1.0.0
members
  :: forall w a v m. (VG.Vector v Int, PrimMonad m) => MutableSparseSet w (PrimState m) a -> m (v Int)
members MutableSparseSet{..} = fmap VG.convert . GrowVec.freeze =<< readMutVar ssIndices
{-# INLINE members #-}

-- | O(1) Look up an element in the set
--
-- @since 0.1.0.0
lookup
  :: forall a v m
   . (PrimMonad m, MVG.MVector v a)
  => MutableSparseSet v (PrimState m) a
  -> Int
  -> m (Maybe a)
lookup MutableSparseSet{..} i = do
  mSi <- (`MSA.lookup` i) =<< readMutVar ssSparse
  case mSi of
    Just si -> Just <$> (readMutVar ssDense >>= (`GrowVec.unsafeRead` si))
    Nothing -> pure Nothing
{-# INLINE lookup #-}

-- | O(1) amortized. Insert a value for a given key.
--
-- If the key is already in the set, its value is overwritten.
--
-- __INVARIANT__: Keys cannot be negative. An unchecked exception is
-- thrown if a negative key is added to the set.
--
-- @since 0.1.0.0
insert
  :: forall a v m
   . (HasCallStack, PrimMonad m, MVG.MVector v a)
  => MutableSparseSet v (PrimState m) a
  -> Int
  -> a
  -> m ()
insert MutableSparseSet{..} i v
  | i < 0 = error $ "Key cannot be negative, got: " <> show i
  | otherwise =
      readMutVar ssSparse >>= (`MSA.lookup` i) >>= \case
        Just di -> do
          dense <- readMutVar ssDense
          GrowVec.unsafeWrite dense di v
        Nothing -> do
          dense <- readMutVar ssDense
          writeMutVar ssDense =<< GrowVec.snoc dense v
          sparse <- readMutVar ssSparse >>= \arr -> MSA.unsafeInsert arr i (GrowVec.length dense)
          writeMutVar ssSparse sparse
          writeMutVar ssIndices =<< (`GrowVec.snoc` i) =<< readMutVar ssIndices
{-# INLINE insert #-}

-- | O(1) Delete an element from the set
--
-- @since 0.1.0.0
delete
  :: forall a v m
   . (PrimMonad m, MVG.MVector v a)
  => MutableSparseSet v (PrimState m) a
  -> Int
  -> m (Maybe a)
delete MutableSparseSet{..} i = do
  sparse <- readMutVar ssSparse
  MSA.delete sparse i >>= \case
    Just di -> do
      dense <- readMutVar ssDense
      indices <- readMutVar ssIndices
      (value, dense') <- GrowVec.unsafeSwapRemove dense di
      writeMutVar ssDense dense'
      (_, indices') <- GrowVec.unsafeSwapRemove indices di
      writeMutVar ssIndices indices'
      unless (di == GrowVec.length dense - 1) do
        swapped <- GrowVec.unsafeRead indices' di
        writeMutVar ssSparse =<< MSA.unsafeInsert sparse swapped di
      pure $ Just value
    Nothing -> pure Nothing
{-# INLINE delete #-}

-- | O(n) Clear all elements from the set.
--
-- @since 0.1.0.0
clear :: forall a v m. (PrimMonad m) => MutableSparseSet v (PrimState m) a -> m ()
clear MutableSparseSet{..} = do
  indices <- readMutVar ssIndices
  sparse <- readMutVar ssSparse
  GrowVec.mapM_ (MSA.unsafeDelete sparse) indices

  atomicModifyMutVar' ssDense ((,()) . GrowVec.cleared)
  atomicModifyMutVar' ssIndices ((,()) . GrowVec.cleared)
{-# INLINE clear #-}

-- | O(n) Shrink the capacity of the set to fit exactly the current number of elements.
--
-- @since 0.1.0.0
compact
  :: forall a v m. (PrimMonad m, MVG.MVector v a) => MutableSparseSet v (PrimState m) a -> m ()
compact MutableSparseSet{..} = do
  writeMutVar ssDense =<< GrowVec.compact =<< readMutVar ssDense
  indices <- readMutVar ssIndices
  indices' <- GrowVec.compact indices
  writeMutVar ssIndices indices'
  GrowVec.maximum indices >>= \case
    Nothing -> pure ()
    Just maxIndex -> do
      sparse <- readMutVar ssSparse
      writeMutVar ssSparse =<< MSA.unsafeCompactTo sparse (maxIndex + 1)
{-# INLINE compact #-}

-- | O(n) Iterate over the values of the set with an accumulator.
--
-- @since 0.1.0.0
foldM
  :: (PrimMonad m, MVG.MVector v a)
  => (b -> a -> m b)
  -> b
  -> MutableSparseSet v (PrimState m) a
  -> m b
foldM f initAcc MutableSparseSet{..} = do
  denseGV <- readMutVar ssDense
  let !len = GrowVec.length denseGV
      go !idx !acc
        | idx >= len = pure acc
        | otherwise = do
            component <- GrowVec.unsafeRead denseGV idx
            newAcc <- f acc component
            go (idx + 1) newAcc

  go 0 initAcc
{-# INLINE foldM #-}

-- | O(n) Iterate over the keys and values of the set with an accumulator.
--
-- @since 0.1.0.0
ifoldM
  :: (PrimMonad m, MVG.MVector v a)
  => (b -> (Int, a) -> m b)
  -> b
  -> MutableSparseSet v (PrimState m) a
  -> m b
ifoldM f initAcc MutableSparseSet{..} = do
  denseGV <- readMutVar ssDense
  indicesGV <- readMutVar ssIndices
  let !len = GrowVec.length denseGV
      go !idx !acc
        | idx >= len = pure acc
        | otherwise = do
            entity <- GrowVec.unsafeRead indicesGV idx
            component <- GrowVec.unsafeRead denseGV idx
            newAcc <- f acc (entity, component)
            go (idx + 1) newAcc

  go 0 initAcc
{-# INLINE ifoldM #-}

-- | O(n) Iterate over the values of the set.
--
-- @since 0.1.0.0
mapM_
  :: (PrimMonad m, MVG.MVector v a)
  => (a -> m ())
  -> MutableSparseSet v (PrimState m) a
  -> m ()
mapM_ f MutableSparseSet{..} = do
  denseGV <- readMutVar ssDense
  let !len = GrowVec.length denseGV
      go !idx
        | idx >= len = pure ()
        | otherwise = do
            component <- GrowVec.unsafeRead denseGV idx
            f component
            go (idx + 1)
  go 0
{-# INLINE mapM_ #-}

-- | O(n) Iterate over the keys and values of the set.
--
-- @since 0.1.0.0
imapM_
  :: (PrimMonad m, MVG.MVector v a)
  => ((Int, a) -> m ())
  -> MutableSparseSet v (PrimState m) a
  -> m ()
imapM_ f MutableSparseSet{..} = do
  denseGV <- readMutVar ssDense
  indicesGV <- readMutVar ssIndices
  let !len = GrowVec.length denseGV
      go !idx
        | idx >= len = pure ()
        | otherwise = do
            entity <- GrowVec.unsafeRead indicesGV idx
            component <- GrowVec.unsafeRead denseGV idx
            f (entity, component)
            go (idx + 1)
  go 0
{-# INLINE imapM_ #-}

-- | O(min(n, m)) Iterate over the intersection of two sets with an accumulator.
--
-- The order of the arguments does not matter - the smaller of the two sets is
-- selected as the iteratee.
--
-- @since 0.1.0.0
ifoldIntersectionM
  :: (PrimMonad m, MVG.MVector v a, MVG.MVector v b)
  => (c -> Int -> a -> b -> m c)
  -- ^ Accumulator
  -> c
  -- ^ Initial value
  -> MutableSparseSet v (PrimState m) a
  -- ^ Set A
  -> MutableSparseSet v (PrimState m) b
  -- ^ Set B
  -> m c
ifoldIntersectionM f c a b = do
  la <- length a
  lb <- length b

  if la <= lb
    then ifoldM (goLookupB b) c a
    else ifoldM (goLookupA a) c b
 where
  goLookupB otherSetB acc (entity, componentA) =
    lookup otherSetB entity >>= \case
      Nothing -> pure acc
      Just componentB -> f acc entity componentA componentB

  goLookupA otherSetA acc (entity, componentB) =
    lookup otherSetA entity >>= \case
      Nothing -> pure acc
      Just componentA -> f acc entity componentA componentB
{-# INLINE ifoldIntersectionM #-}