mutable-fenwick-0.1.0.0: src/Data/Fenwick/Array.hs
{-# LANGUAGE BangPatterns #-}
-- | Module : Data.Fenwick.Array
--
-- This modulo provides mutable [Fenwick Trees](https://en.wikipedia.org/wiki/Fenwick_tree), using
-- arrays as the underlying data structure. The algebraic structure is given by 'Semigroup' and
-- 'Monoid' instances. Some of the functions require the structure to be
-- 'Data.Semigroup.Commutative.Commutative'. See @monoid-subclasses@ and @commutative-semigroups@.
-- If you want mark @Sum@ or @Product@ as @Commutative@, see @SumCommutative@ and
-- @ProductCommutative@ in the mentioned packages.
module Data.Fenwick.Array
( FenMArray
, newFen
, newAccumFen
, newListFen
, getSizeFen
, addFen
, sumPrefixFen
, lowerBoundFen
) where
import Data.Array.MArray
import Control.Monad
import Data.Bits
import Data.Semigroup.Cancellative
import Data.Monoid.Cancellative
import Data.Array.Base
-- | Fenwick tree datatype. @array@ must be a type such that @array 'Int' elem@ refers to a valid
-- mutable array. Fenwick tree is assumed to be data structure over a 1-based array of size @n@.
data FenMArray array elem = FenMArray !Int !(array Int elem)
-- | least significant bit
lsb :: Int -> Int
lsb node = node .&. (-node)
{-# INLINE lsb #-}
modifyArray' :: (MArray a e m, Ix i) => a i e -> i -> (e -> e) -> m ()
modifyArray' arr i f = do
x <- readArray arr i
let !x' = f x
writeArray arr i x'
{-# INLINABLE modifyArray' #-}
unsafeModifyArray' :: (MArray a e m, Ix i) => a i e -> Int -> (e -> e) -> m ()
unsafeModifyArray' arr i f = do
x <- unsafeRead arr i
let !x' = f x
unsafeWrite arr i x'
{-# INLINABLE unsafeModifyArray' #-}
-- | Creates a Fenwick tree of size @n@ over 1-based array. All elements of the array are initially
-- @'mempty'@. \( O(n) \)
--
-- Using type application extension, you can specify the array and element type as the first and
-- second type argument.
--
-- @
-- fen <- 'newFen' \@('Data.Array.ArrayC.ArrayC' 'Data.Array.IO.IOUArray' 'Int') \@('Data.Monoid.Sum' 'Int')
-- @
newFen :: (MArray array elem m, Monoid elem) => Int -> m (FenMArray array elem)
newFen n = do
arr <- newArray (0, n) mempty
pure (FenMArray n arr)
{-# INLINABLE newFen #-}
-- | @newAccumFen n xs@ creates a Fenwick tree of size @n@ over 1-based array. Every item of @xs@ is
-- pair of an index and an element. The elements of the array are initialized by @'<>'@ing elements
-- for each index. This functions is faster than creating empty array using 'newFen' and adding
-- elements individually using 'addFen'. \( O(n + m) \) where \( m \) is @'Data.Foldable.length' xs@
newAccumFen :: (MArray array elem m, CommutativeMonoid elem, Foldable t) => Int -> t (Int, elem) -> m (FenMArray array elem)
newAccumFen n xs = do
arr <- newArray (0, n) mempty
forM_ xs $ \(i, e) -> do
modifyArray' arr i (<> e)
forM_ [1..n] $ \i -> do
let j = i + lsb i
when (j <= n) $ do
e <- readArray arr i
unsafeModifyArray' arr j (e <>)
pure (FenMArray n arr)
{-# INLINABLE newAccumFen #-}
-- | Creates a Fenwick tree and initialize the array based on the elements of the list. \( O(n) \)
newListFen :: (MArray array elem m, CommutativeMonoid elem) => Int -> [elem] -> m (FenMArray array elem)
newListFen n xs = newAccumFen n $ zip [1..n] xs
{-# INLINABLE newListFen #-}
-- | Get the size of underlying array. \( O(1) \)
getSizeFen :: FenMArray array elem -> Int
getSizeFen (FenMArray n _) = n
{-# INLINE getSizeFen #-}
-- | Add a value to a cell of the array. The index must be in the range @[1, n]@. \( O(\log n) \)
addFen :: (MArray array elem m, Commutative elem) => FenMArray array elem -> Int -> elem -> m ()
addFen (FenMArray n arr) r a = go (check r) where
check i
| i < 1 || i > n = error "index out of range"
| otherwise = i
go i = when (i <= n) $ do
unsafeModifyArray' arr i (a <>)
go (i + lsb i)
{-# INLINABLE addFen #-}
-- | Given index @r@, get the prefix sum of elements of the array in the range @[1, r]@. It accepts
-- values of @r@ out of the range of indices, assuming that every element out of the range of array
-- is 'mempty'. \( O(\log n) \)
sumPrefixFen :: (MArray array elem m, Monoid elem) => FenMArray array elem -> Int -> m elem
sumPrefixFen (FenMArray n arr) = go mempty . min n where
-- prefix (0, r], for 1 <= r <= n
go !s i
| i <= 0 = pure s
| otherwise = do
x <- unsafeRead arr i
go (x <> s) (i - lsb i)
{-# INLINABLE sumPrefixFen #-}
-- | Given a prefix sum @q@, find the least index @r@ such that the prefix sum of @[1, r]@ is at
-- least @q@. Only applicable when partial sums are ordered. If the sum of the whole array is
-- less than @query@, it returns @n+1@. This functions is faster than binary search over
-- 'sumPrefixFen'. \( O(\log n) \)
lowerBoundFen :: (MArray array elem m, Monoid elem, Ord elem) => FenMArray array elem -> elem -> m Int
lowerBoundFen (FenMArray n arr) query = go root (n + 1) mempty where
root = bit (finiteBitSize n - countLeadingZeros n - 1)
go node
| odd node = leaf node
| otherwise = nonleaf node
leaf node fallback prepend
| node > n = pure fallback
| otherwise = do
nodeval <- unsafeRead arr node
pure $ if prepend <> nodeval < query
then fallback
else node
nonleaf node fallback prepend
| node > n = go (leftOf node) fallback prepend
| otherwise = do
nodeval <- unsafeRead arr node
if prepend <> nodeval < query
then go (rightOf node) fallback (prepend <> nodeval)
else go (leftOf node) node prepend
leftOf node = node - (lsb node `unsafeShiftR` 1)
rightOf node = node + (lsb node `unsafeShiftR` 1)
{-# INLINABLE lowerBoundFen #-}