cleff-0.3.3.0: src/Cleff/Internal/Vec.hs
{-# OPTIONS_HADDOCK not-home #-}
-- |
-- Copyright: (c) 2021 Xy Ren
-- License: BSD3
-- Maintainer: xy.r@outlook.com
-- Stability: unstable
-- Portability: non-portable (GHC only)
--
-- This module contains an efficient vector datatype that is implemented as a radix tree.
--
-- __This is an /internal/ module and its API may change even between minor versions.__ Therefore you should be
-- extra careful if you're to depend on this module.
module Cleff.Internal.Vec (Vec, empty, lookup, update, snoc) where
import Control.Monad.ST (ST)
import Data.Bits (Bits (unsafeShiftL, unsafeShiftR, (.&.)), FiniteBits (countTrailingZeros))
import Data.Primitive.MachDeps (sIZEOF_INT)
import Data.Primitive.SmallArray (SmallArray, SmallMutableArray, copySmallArray, indexSmallArray,
newSmallArray, readSmallArray, runSmallArray, sizeofSmallArray,
thawSmallArray, writeSmallArray)
import Prelude hiding (lookup)
-- | An efficient vector type, implemented as a radix tree. It has the following time complexities:
--
-- * Lookup: \( O(\log n) \)
-- * Update: \( O(\log n) \)
-- * Append: \( O(\log n) \)
--
-- The branching factor (base of log) is 32 therefore the time is close to constant in most cases. Note that in
-- practice, lookup is faster than update, and update is faster than append.
data Vec a = Vec !Int !(Tree a)
type Shift = Int
-- | The \( \log_2 \) of the branching factor. The branching factor is set to be 32 for now but may change in the
-- future.
factor :: Int
factor = 5
-- | A mask covering one chunk of an index.
initialMask :: Int
initialMask = (1 `unsafeShiftL` factor) - 1
-- | A radix tree. The tree is always left-leaning.
data Tree a
= Tip
{-# UNPACK #-} !(SmallArray a)
| Node
{-# UNPACK #-} !Shift
{-# UNPACK #-} !(SmallArray (Tree a))
-- | Mask a portion of an index.
mask :: Shift -> Int -> Int
mask s x = initialMask .&. (x `unsafeShiftR` s)
-- | Mask the zeroth portion of the index.
mask0 :: Int -> Int
mask0 x = initialMask .&. x
-- | Alter an element in a 'SmallMutableArray' by a function.
alterSmallArray :: SmallMutableArray s a -> Int -> (a -> a) -> ST s ()
alterSmallArray marr ix f = do
x <- readSmallArray marr ix
writeSmallArray marr ix $! f x
-- | The empty 'Vec'.
empty :: Vec a
empty = Vec 0 $ Tip $ runSmallArray $ newSmallArray 0 $ error
"Cleff.Internal.Vec: Encountered an element in an empty Vec. Please report this as a bug."
-- | Lookup in a 'Vec' by an index. This does not perform any bounds check.
lookup :: Int -> Vec a -> a
lookup ix (Vec _ tree) = go tree
where
go (Tip arr) = indexSmallArray arr (initialMask .&. ix)
go (Node s arr) = go (indexSmallArray arr (mask s ix))
-- | Update a value in a 'Vec' by an index. The value will be forced before installing. This does not perform any
-- bounds check.
update :: Int -> a -> Vec a -> Vec a
update ix x (Vec len tree) = Vec len (go tree)
where
go (Tip arr) = Tip $ runSmallArray do
marr <- thawSmallArray arr 0 (sizeofSmallArray arr)
writeSmallArray marr (mask0 ix) $! x
pure marr
go (Node s arr) = Node s $ runSmallArray do
marr <- thawSmallArray arr 0 (sizeofSmallArray arr)
alterSmallArray marr (mask s ix) go
pure marr
-- | Append a value to a 'Vec'. The value will be forced before installing. This does not perform any bounds check.
snoc :: Vec a -> a -> Vec a
snoc (Vec len tree) x
| ins <= topShift = Vec (len + 1) (go tree)
| otherwise = Vec (len + 1) $ Node (topShift + factor) $ runSmallArray $ do
marr <- newSmallArray 2 $! tree
writeSmallArray marr 1 $! branch topShift
pure marr
where
topShift = case tree of
Tip _ -> 0
Node s _ -> s
ins = (countTrailingZeros len `mod` sIZEOF_INT `div` factor) * factor
branch 0 = Tip $ runSmallArray $ newSmallArray 1 $! x
branch s = Node s $ runSmallArray $ newSmallArray 1 $! branch (s - factor)
enlarge arr new = runSmallArray do
let sz = sizeofSmallArray arr
marr <- newSmallArray (sz + 1) $! new
copySmallArray marr 0 arr 0 sz
pure marr
go (Tip arr) = Tip $ enlarge arr x
go (Node s arr)
| ins == s = Node s $ enlarge arr $ branch (s - factor)
| otherwise = Node s $ runSmallArray do
marr <- thawSmallArray arr 0 (sizeofSmallArray arr)
alterSmallArray marr (mask s len) go
pure marr